Lowering: AST to Stack Bytecode
Work through examples, try the reasoning yourself, then put it into practice.
View SlidesBefore you start
- Build an expression AST with correct precedence.
- Trace a LIFO stack.
What you will practice
- Derive postfix emission from recursive tree shape.
- Preserve operand and argument order for noncommutative operations.
- Distinguish the tiny Lower API from v2 program bytecode.
In this lesson
Lesson
3 min readLowering: AST to Stack Bytecode
Lower(expr) emits instructions for the expression-only AST. Numbers become
PUSH_NUM, identifiers become PUSH_IDENT, unary operators follow their
operand, and binary operators follow both operands. Calls emit arguments
left-to-right followed by CALL name arity. Do not substitute v2 LOAD,
closure calls, scopes, strings, or BC1 serialization into this exercise.
Worked reconstruction
Lower 20 - 3 * 4 by recursively finishing children before their parent:
| Instruction | Hand-execution stack |
|---|---|
PUSH_NUM 20 |
[20] |
PUSH_NUM 3 |
[20,3] |
PUSH_NUM 4 |
[20,3,4] |
MUL |
[20,12] |
SUB |
[8] |
The compiler visits left then right; the consumer pops right then left. Those orders agree because the stack is LIFO. Reversing either subtraction operand would change 8 into -8 even though addition-based tests might still pass.
For mix(7, 2+3), the sequence is PUSH_NUM 7; PUSH_NUM 2; PUSH_NUM 3; ADD; CALL mix 2. The arity is two expressions, not three literal pushes. mix is
held in the call instruction's name field at this stage, not pushed as a value.
Stack effect as a local proof
Each successfully lowered expression leaves one additional value in the conceptual consumer stack. A literal adds one; a unary operator consumes one and produces one; a binary operator consumes two and produces one. A call consumes its argument count and produces one result. These effects let you check the emitted list without implementing a complete runtime here.
Append each child's instructions into one output sequence and preserve source order. The input AST is already the structure to follow; lowering must not re-parse text or re-decide precedence. Later program compilation introduces statements and control-flow paths, where stack effects also have to agree at branch joins.
Debugging lab
A lowerer emits PUSH_NUM 20; PUSH_NUM 3; SUB; PUSH_NUM 4; MUL for the worked source. Is the arithmetic opcode implementation the first place to fix? Identify the required instruction movement.
Compare your diagnosis after attempting the lab
The opcode list encodes (20-3)*4, so lowering ignored the existing multiplication subtree. Finish 3*4 before emitting the parent SUB. No arithmetic opcode needs changing.
Transfer task
Without the trace open, lower -f(8,3-1) / 2. Annotate each instruction with its stack effect and the call arity. Give a second expression that would expose reversing argument order.
Carry forward
Carry the postfix invariant into the v2 compiler. The expression artifact is complete when you can derive and diagnose its instruction sequence, not merely copy an opcode mapping.
Build ast-to-bytecode. Keep a before/after trace of one failing test. Use the checkpoint to diagnose specific skills; passing it does not replace implementing and testing the artifact.
Practice sequence
Use the order below for a guided path, or choose an item to revisit. A pass records a successful attempt; try a fresh example later to check what you can reconstruct.