In our last session, you accomplished the critical first step of debugging Clang: you successfully launched the compiler in a debugger and paused its execution inside the C++ parser. This set the stage for today's lesson, where we will move from simply stopping the compiler to actively tracing its work.
Our objective is to follow the entire lifecycle of a simple C++ expression, from the moment the parser recognizes its syntax to the point where semantic analysis constructs the corresponding Abstract Syntax Tree (AST) node. This process of stepping through code is not just an academic exercise; it is the single most powerful technique you will use to understand complex code flows, diagnose bugs, and ultimately, contribute effectively to LLVM.
The Path from Parser to AST
Before we dive into the debugger, let's solidify our mental model of what we expect to see. As we've discussed, the Clang frontend is a collaboration primarily between the Parser and the Sema (Semantic Analysis) libraries.
- The Parser consumes tokens and recognizes the grammatical structure of the code. For example, it sees
identifier,equals,literal,plus,literal,semicolon. - When it recognizes a complete grammatical construct (like a binary expression), it calls an
ActOn...method in Sema. For a+operation, this would beSema::ActOnBinOp. - Sema then performs semantic checks (e.g., "Are these two operands compatible with addition?") and, if everything is valid, it creates the final AST node that represents the operation.
This entire workflow is a core part of Clang's design.
“Clang” CFE Internals Manual — Clang 23.0.0git documentation
The Clang Internals Manual provides a detailed guide written for developers. It's an essential piece of documentation for anyone looking to modify the compiler.
Please read the section titled "How to add an expression or statement." Don't worry about memorizing every detail. Instead, focus on the high-level sequence it describes: Parsing actions: The parser recognizes the syntax. Semantic analysis actions: The parser calls ActOnXXX in Sema, which in turn may call a BuildXXX function. AST node introduction: Sema constructs the actual AST node object. This is the exact path we are about to trace in the debugger.
To visualize this, imagine the call stack deepening as the compiler processes your code. The following video gives an excellent animated overview of this process.
2019 LLVM Developers’ Meeting: S. Haastregt & A. Stulova “An overview of Clang ”
This presentation from an LLVM Developers' Meeting gives a fantastic tour of the Clang frontend's architecture.
First, watch the segment that shows a call stack during parsing. Notice how the functions get more specific as they descend, from ParseTopLevelDecl down to ParseRelationalExpression. This is the recursive-descent parsing we'll be stepping through. Next, watch the explanation of Sema's role. Pay close attention to the moment the speaker describes the hand-off from the parser (ActOn...) and the final creation of the BinaryOperator AST node. This is the moment we're aiming to witness.
The Debugging Journey: From Code to AST Node
Let's begin. We'll use a slightly more interesting test case than last time to give us a binary operation to trace.
1. The Test Case
Create a file named test.cpp with the following content:
int main() {
int x = 42 + 10;
return 0;
}
Our target is the expression 42 + 10 and the BinaryOperator AST node that will represent it.
2. Launch the Debugger
Following the procedure from the last lesson:
- Generate the
cc1command:/path/to/your/build/bin/clang -### test.cpp - Launch LLDB (or GDB) with the full
cc1command:lldb -- "<full cc1 command>" - Set the initial breakpoint:
(lldb) b clang::Parser::ParseTopLevelDecl - Run the program:
(lldb) run
You should now be paused at the entry point of ParseTopLevelDecl, ready to begin our trace.

Phase 1: Navigating to the Statement
Our breakpoint is at the function that parses any top-level declaration. Our file contains one: the main function. We need to step through the parsing of this function to get to its body, where our target expression lives.
n(next): Use thenextcommand several times to step over local variable initializations at the start ofParseTopLevelDecl.- You will eventually reach a call to
ParseExternalDeclaration. This is the main dispatcher for parsing things in the global scope. s(step): Use thestepcommand to enterParseExternalDeclaration.- Again, use a combination of
nandsto follow the logic. You will descend through a series of calls likeParseDeclaration,ParseDeclGroup, and finallyParseFunctionDefinition. Each of these functions is responsible for a more specific part of the grammar. - Inside
ParseFunctionDefinition, you will find a call toParseCompoundStatementBody. This is the function that parses the code inside the{...}of ourmainfunction. Usesto step into it.
You are now inside the part of the parser that handles the body of the main function.
Phase 2: Parsing the Declaration
The first thing inside main is the statement int x = 42 + 10;.
- Inside
ParseCompoundStatementBody, the parser will loop to consume statements. The first call in this loop will likely be toParseStatementOrDeclaration. Step into it withs. - This function is complex, as it can handle many kinds of statements. It will parse the type
int, then the declarator which includes the variable namexand its initializer. The key function for parsing the... = initializer;part isParseDeclarator. - As you step through, you'll see the parser consume the
=token and then call a function to parse the expression on the right-hand side. This is oftenParseAssignmentExpression. Step into it.
Phase 3: Parsing the Binary Expression and the Hand-off to Sema
You are now at the heart of expression parsing. Clang's parser is a recursive-descent parser that respects C++ operator precedence.
- The function
ParseAssignmentExpressionwill in turn call other functions for expressions with higher precedence. You'll descend through a chain likeParseConditionalExpression,ParseBinaryExpression, and so on, until you reachParseAdditiveExpression. - Inside
ParseAdditiveExpression, the parser will see the+token. After parsing the left-hand side (42) and the right-hand side (10), it will make the crucial call to Sema. - Keep stepping until you see a line that looks like this:
Result = SemaRef.ActOnBinOp(getCurScope(), Tok.getLocation(), Tok.getKind(), LHS, RHS); - This is the hand-off. The Parser has done its job of recognizing the syntax of a binary operation. It now passes the operands (
LHSandRHS) to Sema viaActOnBinOp. - Use
sto step intoSema::ActOnBinOp.
Phase 4: Witnessing AST Node Creation
You are now inside the semantic analysis engine.
ActOnBinOpwill perform type checking and other semantic validation. It often delegates the actual AST creation to a helper function. In this case, it will likely callCreateBuiltinBinOp. Step into it.- Inside
CreateBuiltinBinOp, you will finally find the line of code that allocates and constructs the AST node. It will look something like this:
return new (Context) BinaryOperator(LHS, RHS, Opcode, ResultTy, ...); - This is the moment of creation. Let's inspect the result.
- Use
nto step over this line. TheBinaryOperatorhas now been created, and a pointer to it is stored in a local variable (often calledResultor something similar). - Use the
printcommand to inspect this variable.
(lldb) print Result
The debugger will print the memory address and type of the object:(clang::BinaryOperator *) $0 = 0x... - Now, let's look inside it. You can dereference the pointer to see its members.
(lldb) print *Result
This will show you the contents of theBinaryOperatorobject, including pointers to its left-hand side (LHS) and right-hand side (RHS), and its opcode (e.g.,BO_Add). - For an even better view, you can ask the debugger to execute the node's
dump()method, which is designed for this purpose.
(lldb) expr Result->dump()
- Use
You have successfully traced an expression from raw text to a fully-formed, in-memory AST node and inspected its state.
Verifying with High-Level Tools
This low-level debugging view is incredibly powerful, but it's also useful to verify your understanding with higher-level tools that show the final result.
Using -ast-dump
You can ask Clang to print its entire AST for a given file. This shows you the final structure that your step-by-step process helped to build.
/path/to/your/build/bin/clang -Xclang -ast-dump -fsyntax-only test.cpp
The output for 42 + 10 will look something like this, clearly showing the BinaryOperator with its opcode and children:
`-BinaryOperator 0x... <col:13, col:20> 'int' '+'
|-IntegerLiteral 0x... <col:13> 'int' 42
`-IntegerLiteral 0x... <col:20> 'int' 10
Using Compiler Explorer
For quick experiments, the online Compiler Explorer tool is invaluable. It lets you type code and instantly see the corresponding AST.

Conclusion
In this lesson, you moved beyond pausing the compiler and learned how to actively trace its execution. By stepping through the code, you have witnessed the intricate dance between the Parser and Sema and pinpointed the exact moment an AST node is born. This ability to follow the flow of data and control through the codebase is the foundational skill for diagnosing bugs and understanding how to implement new features.
Key Takeaways:
- The path to creating an expression's AST node typically flows from
Parser::Parse...functions to aSema::ActOn...call. - Inside Sema, a
Build...orCreate...helper function performs semantic checks and allocates the new AST node (e.g.,new (Context) BinaryOperator(...)). - The debugger's
step,next, andprintcommands are your essential tools for tracing this flow. - You can use
expr Node->dump()in the debugger to get a nicely formatted view of an AST node you've paused on. - Higher-level tools like
clang -Xclang -ast-dumpand Compiler Explorer are excellent for verifying the final structure of the AST.
You now have a solid understanding of Clang's AST and the process that creates it. In our next module, we will build on this foundation. We will shift from just observing the AST to actively using it, as you learn to write your own standalone Clang tools for code analysis and transformation.