Skip to content

AST Profiler

BrandonRobare edited this page Jun 2, 2026 · 1 revision

AST Profiler

The biggest project of the five. It reads the srcML XML representation of a C++ file, builds an abstract syntax tree, and rewrites that tree to inject profiling counters. The output is an instrumented copy of the source that counts how many times each function and statement runs.

srcML is a tool that wraps source code in XML tags marking its syntactic structure (<function>, <block>, <expr_stmt>, and so on) while keeping the original text intact. That format is what lets a program treat code as a tree and then print it back out as code.

The two classes

  • AST is a node in the tree. A node is one of three kinds: a category node (an internal node with a tag like function and a list of children), a token node (a piece of source text), or a whitespace node. Category nodes hold children; token and whitespace nodes hold text. The class has the full set of value semantics: copy constructor that deep-copies the child list, destructor that deletes children, swap, and copy-and-swap assignment.
  • srcML wraps a whole file: a header string and a pointer to the root AST. It forwards the transformation calls down to the tree.

The instructor provided the AST/srcML skeleton, the XML reader, and the tokenizer. I implemented the transformation methods that walk and rewrite the tree.

The pipeline

flowchart LR
    A[foo.cpp.xml<br/>srcML input] --> B[operator&gt;&gt;<br/>read header + tree]
    B --> C[AST::read<br/>recursive parse into<br/>category / token / whitespace nodes]
    C --> D[mainHeader / fileHeader<br/>insert includes + profile objects]
    D --> E[functionCount<br/>inject count per function]
    E --> F[lineCount<br/>inject count per statement]
    F --> G[mainReport<br/>insert report before return]
    G --> H[print tree to<br/>p-foo.cpp]
Loading

Key methods

  • read. Recursive descent over the XML. An open tag starts a new category subtree and recurses; a close tag ends it; anything else is run through the tokenizer and added as token or whitespace children.
  • print. Preorder traversal that emits only the leaf text. Walking the tree and printing the tokens reproduces the original source, which is what makes round-tripping possible.
  • getChild and getName. Navigation helpers. getChild finds a child by tag; getName reads a <name> node and handles both simple names like main and scoped names like stack::push.
  • mainHeader / fileHeader. Insert #include "profile.hpp" and the profile object declarations. The main file gets the object definitions; the other files get extern declarations.
  • functionCount. For each function, constructor, and destructor, push a profile.count(__LINE__, "name") call to the front of its body.
  • lineCount. Walk the tree and insert a profile.count(__LINE__) after each expression statement, skipping categories that should not be counted (the isStopTag list: conditions, types, declarations, returns, and so on).
  • mainReport. Find main, walk back to its return, and insert the code that prints each profile object's totals before the function returns.

The runtime

profile.hpp / profile.cpp is the small counting library that the instrumented code links against. A profile object holds a std::map keyed by line number (or line plus function name) and bumps the count each time count is called. Printing a profile dumps the map. So the profiler injects the calls, and this class tallies them at runtime.

Build and run

cd profiler
make profiler
./profiler foo.cpp.xml          # writes p-foo.cpp

The first argument must be the file containing main; any additional .cpp.xml files follow. The repo ships foo.cpp.xml, sort.cpp.xml, and a few others to try. Under CI the tool builds with g++ -std=c++11.

Notes and limits

This is the project where the value-semantics work pays off, since the tree owns a lot of heap nodes and gets copied in the driver's testCopyAssign. The transformations make assumptions the comments spell out: one return in main, no nested functions, and explicit { } blocks on every construct. Those hold for the sample inputs.

Clone this wiki locally