-
Notifications
You must be signed in to change notification settings - Fork 0
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.
-
ASTis a node in the tree. A node is one of three kinds: acategorynode (an internal node with a tag likefunctionand a list of children), atokennode (a piece of source text), or awhitespacenode. 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. -
srcMLwraps a whole file: a header string and a pointer to the rootAST. 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.
flowchart LR
A[foo.cpp.xml<br/>srcML input] --> B[operator>><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]
-
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. -
getChildandgetName. Navigation helpers.getChildfinds a child by tag;getNamereads a<name>node and handles both simple names likemainand scoped names likestack::push. -
mainHeader/fileHeader. Insert#include "profile.hpp"and the profile object declarations. The main file gets the object definitions; the other files getexterndeclarations. -
functionCount. For each function, constructor, and destructor, push aprofile.count(__LINE__, "name")call to the front of its body. -
lineCount. Walk the tree and insert aprofile.count(__LINE__)after each expression statement, skipping categories that should not be counted (theisStopTaglist: conditions, types, declarations, returns, and so on). -
mainReport. Findmain, walk back to itsreturn, and insert the code that prints each profile object's totals before the function returns.
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.
cd profiler
make profiler
./profiler foo.cpp.xml # writes p-foo.cppThe 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.
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.
Projects
Reference