BUILD
YOUR
COMPILER
From source code to machine instructions
What You’ll Do Here
Build a tiny language end‑to‑end
Start by setting the target in S0 with a minimal x86‑64 program, then grow S1–S4 (variables, control flow, functions, I/O), and finish self‑hosting in S5.
- •Type basic S expressions and see the tokens, AST, and assembly update live.
- •Follow the stages S0 → S5: expressions, variables, control flow, functions, runtime, bootstrapping.
- •Use the “Running Sx on Your Machine” cards to copy, assemble, link, and run each step locally.
- •Hover dotted terms — tooltips explain key ideas without breaking the flow.
How to follow along
- 1.Skim the “Why a Compiler?” primer below, then hover the pipeline cards.
- 2.Use the interactive playgrounds (lexer/AST/assembly) to connect concepts to concrete outputs.
- 3.Try the commands in the final Tooling & Build section to assemble and run examples locally.
- 4.Finish with the quiz to check your understanding.
Why a Compiler?
A compiler is not just a translator. It's a multi-stage pipeline that gradually transforms human-readable code into machine instructions.
Each stage adds structure, meaning, and optimization.
Interactive Compiler Pipeline
Hover each stage to see the magic unfold — from source code to machine instructions
Lexing (Scanning)
Your code is broken into tokens, the alphabet of the language.
On hover, you’ll see: tokens start glowing, hinting at lexing
Parsing → AST
Tokens are rearranged into a tree structure that reflects meaning.
On hover, you’ll see: see tree nodes light up as checks are applied
Semantic Analysis
The AST is checked for correctness (types, variables, functions).
On hover, you’ll see: show type checking and scope validation
IR (Intermediate)
Code is lowered into a neutral, machine-independent form for optimization.
On hover, you’ll see: show optimization passes (dead code elimination, constant folding)
Code Generation
The verified IR/AST is turned into assembly for the CPU.
On hover, you’ll see: registers blink (RAX, RDI) showing 'this is hardware-level'
Assembly & Linking
Assembly is converted to opcodes and combined with runtime/libraries.
On hover, you’ll see: tooltips explain sections, linked with libc/syscalls
Execution
At runtime the CPU executes instructions on registers, memory, and I/O.
On hover, you’ll see: CPU chip lights up, instructions execute step by step
- • Lexing + Parsing: Interactive Lexer Playground
- • Codegen, Assembling, Linking: S1 / S2 / S3 / S4 “Running on your machine” steps
- • Build the compiler: s4c (Stage 4)
- • Calling convention (ABI): System V ABI Call Sequence in S3
- • I/O runtime helpers: S4 Running (itoa + write syscall)
- • Self‑hosting / Bootstrapping: Stage 5
Key Insights
Frontend vs Backend
Frontend understands the language. Backend generates machine code.
CPU Specific
Each CPU (x86-64, ARM, RISC-V) needs a different backend.
Early Error Detection
Compilers catch errors before execution, making them your debugging ally.
The S Language
S is tiny on purpose. Its syntax is simple, slightly quirky, and easy to parse.
We'll grow S in stages
One expression per program; compute it and exit with that value.
Variables, multiple statements, affiche (print), arithmetic precedence.
if/else (si/sinon), while (tantque), comparisons.
fonction definitions/calls, retourne; System V ABI call sequence.
itoa + write syscall; minimal print runtime.
Compiler written in S; compiles itself (bootstrap).
S leans playful with French-ish keywords
Semicolons are optional; newlines end statements. Blocks use { ... }.
Syntax & Grammar
The grammar file defines the rules of the language. It describes how tokens (like numbers, identifiers, operators) combine into valid expressions and statements. Think of it as the blueprint for what programs in S are allowed to look like.
Code Sample
1soit n = 52fonction fact(x) {3 si x <= 1 {4 retourne 15 } sinon {6 retourne x * fact(x - 1)7 }8}9affiche fact(n)
Interactive Lexer Playground
Type basic S expressions (numbers with +, -, *, /) and watch the magic in real time — see tokens, the AST tree, and the generated x86‑64 assembly.No variables, control flow, or functions in this mini playground.
This playground runs a tiny lexer, parser and x86‑64 code generator in your browser — no servers.
Try: 2 + 3 * 4, 5 * (7 - 2), or -3 + 12 / 3 and compare the AST vs. assembly.
Input Code
Tip: this playground accepts numeric expressions (no variables/functions).
Tokens
AST
Assembly
Stage 0 — Hello Assembly
Goal
Take a single integer expression, generate a tiny x86-64 program that exits with that value as its exit code.
Why start with assembly before S?
Because S doesn’t exist yet. Stage 0 gives us a concrete target and a working toolchain. By hand‑writing a minimal program that computes an expression and calls sys_exit, we learn the exact shape our future S compiler must emit to produce a real executable.
- Concrete target: Linux x86‑64 SysV ABI (we know how to pass arguments and which syscall number to use).
- Toolchain check:
nasm→ldworks on your machine. - Golden template: the minimal assembly our S compiler will generate in later stages.
- Demystify: see how
2 + 3 * 4turns into a few CPU instructions and an exit code.
Where does the assembly come from?
We mentally (or with a tiny script) turn the expression into a tree:
- •Computes it in a register (we'll use RAX)
- •Calls the Linux syscall exit(status)
From Expression to Assembly
; s0.asm — compute 2 + 3*4 and exit with the result (14)
; Linux x86-64 SysV, NASM syntax
global _start
section .text
_start:
; --- compute 3*4 into RAX ---
mov rax, 3 ; RAX = 3
imul rax, 4 ; RAX = RAX * 4 => 12
; --- add 2 ---
add rax, 2 ; RAX = 12 + 2 => 14
; --- exit(status = RAX) ---
mov rdi, rax ; First arg (status) goes in RDI (SysV ABI)
mov rax, 60 ; Syscall number for exit on Linux x86-64
syscall ; exit(14)How this works (line by line)
Complete Build Process
⚠️Prerequisites (pick your platform)
Linux / WSL
sudo apt-get update sudo apt-get install -y python3 nasm binutils
macOS (Docker)
docker run --platform=linux/amd64 -it --rm -v "$PWD":/work \ -w /work debian:stable-slim bash apt-get update apt-get install -y python3 nasm binutils
Note: We target Linux x86-64 SysV ABI. On macOS natively, syscalls differ; use Docker (add --platform=linux/amd64) or a Linux VM.
Write the assembly
Create s0.asm and paste the assembly above.
Assemble & link
Convert assembly to machine code and create executable.
Run & verify the exit code
Execute and check the result.
Quiz Time!
🚨Quick Troubleshooting
🚀What to tweak next
Stage 1 — Variables & Operators
Goal
At Stage 0 we could only evaluate a single arithmetic expression. Now we add variables and multiple statements.
Key concepts in Stage 1:
- Variables are stored in memory (on the stack).
- Each
soitdeclaration introduces a new slot in a symbol table. - Assignments update these slots.
- At the end, the last expression's value becomes the program's exit code (for now).
This prepares us for control flow in Stage 2 and functions in Stage 3.
Operator Precedence
Operator precedence decides which operations group first; associativity breaks ties. The AST shows 2 + 3 * 4 as 2 + (3 * 4), not (2 + 3) * 4.
In Stage 1 we add variables and multiple statements, but every assignment still relies on expressions. Getting precedence right ensures a = a + b * 3 means a + (b * 3), not (a + b) * 3. We also need this foundation for Stage 2 conditions (if/while) and Stage 3 function calls where argument expressions must evaluate in the correct order.
- •Precedence:
* /before+ -. So2 + 3 * 4=2 + (3 * 4). - •Associativity:
+and-are left-associative.10 - 3 - 2=(10 - 3) - 2= 5. - •Parentheses: they force grouping.
(2 + 3) * 4= 20. - •Why it matters: the parser builds the AST from these rules; the evaluation order and generated assembly depend on it.
S1 Assembly Implementation
Conceptual snippet showing stack-allocated locals using a base pointer (RBP):
1. Stack frame setup
- •
push rbp / mov rbp, rsp: creates a base pointer for local variables. - •
sub rsp, 16: reserves 16 bytes (two 8-byte slots).
a lives at [rbp-8], b lives at [rbp-16]2. Initialize variables
- •
mov [rbp-8], 10→ a = 10 - •
mov [rbp-16], 2→ b = 2
3. Assignment with arithmetic
4. Result & Exit
Running S1 on Your Machine
Write the assembly
Create the s1.asm file with our stack-based variable implementation.
Assemble & link
Convert assembly to object file, then link into executable.
Run & check exit code
Execute the program and verify the result matches our expectation.
💡Why stack allocation?
Even though Stage 1 could technically keep everything in registers, we force variables onto the stack.
- Stage 2 needs block scopes (if/while).
- Stage 3 needs function calls (with stack frames).
- Debugging is easier when each variable has a clear address.
Quiz Time!
⚙️S1 Compiler Tasks
Example: first local = [rbp-8], second = [rbp-16].
Later: we'll use affiche to print results explicitly.
🚀Toward Stage 2
Stage 2 — Control Flow
Goal
At Stage 1 we had variables and arithmetic. Now we add decision-making:
Stage 2 introduces:
si / sinon: conditional execution, like if / else.tantque: looping, like while.- Comparisons like
<=, <, ==produce a Boolean (0 = false, 1 = true).
Assembly branching: Evaluate condition → Emit cmp → Jump to right label → Execute block.
Control Flow Graph
Control Flow Graph for a while loop:
S2 Assembly with Labels & Jumps
Here's how the compiler lowers the tantque loop into labels and jumps:
1. Setup & Initialization
sum = 0 → [rbp-8], i = 1 → [rbp-16].2. Condition check
cmp rax, 5 compares i to 5. jg .L_loop_end → if i > 5, exit loop.3. Body execution
4. Loop back
jmp .L_loop_cond returns to condition check.5. Exit
.L_loop_end. Load sum into rax as exit code.Running S2 on Your Machine
Write the program
Create s2.asm with our loop implementation using labels and conditional jumps.
Assemble & link
Convert assembly with labels to executable binary.
Run & check result
Execute and verify the loop computed the sum correctly.
Quiz Time!
tantque i <= 5 become assembly?- • Compute i, compare with 5.
- • If i > 5, jump to end.
- • Otherwise, run loop body and jump back to condition.
⚙️S2 Compiler Tasks
cmp left, right.Conditional jump (jg, jl, je, etc.) moves execution flow.
Jump to end if false. Body executes. Jump back to .L_cond.
🚀Toward Stage 3
Stage 3 — Functions
Goal
Until now, everything was "inlined" in _start. With functions we can define reusable blocks of code with arguments and return values.
System V ABI rules (Linux, x86-64):
- First 6 integer args:
RDI, RSI, RDX, RCX, R8, R9 - Return value:
RAX - Caller must save caller-saved registers if needed.
- Callee must preserve callee-saved registers (RBX, RBP, R12–R15).
System V ABI Call Sequence
Call sequence showing caller and callee responsibilities:
In Stage 3 we make real function calls. To interoperate with any code we (or the OS/runtime) might call, both sides must agree on a contract: where arguments live, where the return value comes back, and which registers must be preserved. That contract is the System V AMD64 ABI on Linux.
- • Argument registers: RDI, RSI, RDX, RCX, R8, R9
- • Return value in RAX
- • Caller-saved vs callee-saved registers
- • Stack frame shape and alignment
- • Your compiled S code can call helpers and be called back
- • No clobbered state: respect saved registers
- • Predictable return results in RAX
- • Easier debugging and future interop (C, libc)
S3 Assembly with Function Calls
Function add3 with prologue, body, and epilogue:
1. Caller prepares arguments
2. Function call
call add3 pushes return address and jumps to function.3. Function prologue
4. Function body
5. Return
leave resets stack frame. ret returns to caller.Running S3 on Your Machine
Write the assembly
Create s3.asm with function definition and calling code.
Assemble & link
Convert assembly with function calls to executable binary.
Run & check result
Execute and verify the function returned the sum correctly.
💡Why functions matter?
Quiz Time!
RAX?⚙️S3 Compiler Tasks
Callee preserves callee-saved if touched.
🚀Toward Stage 4
Stage 4 — Tiny Runtime & I/O
Goal
We add output: affiche expr → compute expr → itoa → write(1, buf, len) → optionally exit(0).
Stage 4 introduces I/O system:
- Runtime helpers:
itoaconverts integers to ASCII strings. - Linux syscalls:
writeandexitfor I/O and termination. - Output pipeline: Expression evaluation → String conversion → System output → Clean exit.
- Foundation for: Error messages, logs, and future standard library.
Integer to ASCII Helper (itoa)
The itoa_rax_to_str helper handles zero and negatives, converting a signed integer in RAX to decimal ASCII:
1. Setup stack buffer
2. Handle special cases
3. Extract digits
4. Add sign & finalize
Complete S4 Program with I/O
Complete program calculating 42 - 5*2 = 32, calling itoa, printing via write syscall, and exiting cleanly:
System call parameters
Output process
Expected output
Running S4 on Your Machine
Create the file
Write the complete S4 assembly with itoa helper and main program.
Assemble & link
Convert assembly with runtime helpers to executable binary.
Run & verify output
Execute and check both the printed output and exit code.
Build Your Compiler (s4c)
s4c is your bootstrap compiler written in a host language (Python). It reads S source and emits x86-64 assembly. Start with a minimalist MVP (numeric expressions), then extend to variables, control flow, functions, and affiche.
#!/usr/bin/env python3
"""
s4c.py - A minimal compiler from S expressions to x86-64 assembly
Demonstrates the 4 phases of compilation:
1. Lexical Analysis (tokenize)
2. Syntax Analysis (parse)
3. Code Generation (gen)
4. Assembly Output (compile_expr_to_asm)
"""
import sys, argparse
# =============================================================================
# TOKEN TYPES - The vocabulary of our language
# =============================================================================
TOK_NUM, TOK_OP, TOK_LP, TOK_RP, TOK_EOF = 'NUM','OP','LP','RP','EOF'
def tokenize(s):
"""
PHASE 1: LEXICAL ANALYSIS
Converts source text into tokens (lexemes)
Example: "2 + 3 * 4" -> [NUM(2), OP(+), NUM(3), OP(*), NUM(4), EOF]
"""
i, n, toks = 0, len(s), [] # Current position, length, token list
while i < n:
c = s[i]
# Skip whitespace
if c.isspace():
i += 1
continue
# Parse multi-digit numbers
if c.isdigit():
j = i # Remember start position
while j < n and s[j].isdigit():
j += 1 # Find end of number
toks.append((TOK_NUM, int(s[i:j]))) # Convert to integer
i = j
continue
# Single-character operators
if c in '+-*/':
toks.append((TOK_OP, c))
i += 1
continue
# Parentheses for grouping
if c == '(':
toks.append((TOK_LP, c))
i += 1
continue
if c == ')':
toks.append((TOK_RP, c))
i += 1
continue
# Unknown character - compilation error
raise SystemExit(f'Unsupported char: {c}')
# Always end with EOF token
toks.append((TOK_EOF, ''))
return toks
class Parser:
"""
PHASE 2: SYNTAX ANALYSIS
Builds Abstract Syntax Tree (AST) with correct operator precedence
Uses recursive descent parsing with these grammar rules:
expr -> term (('+' | '-') term)* # Lowest precedence
term -> fact (('*' | '/') fact)* # Higher precedence
fact -> number | '(' expr ')' | unary_op fact # Highest precedence
"""
def __init__(self, toks):
self.toks = toks # Token stream
self.i = 0 # Current token index
def cur(self):
"""Get current token"""
return self.toks[self.i]
def eat(self, expected_type):
"""Consume token of expected type, or error"""
if self.cur()[0] == expected_type:
self.i += 1
else:
raise SystemExit(f'Expected {expected_type}, got {self.cur()}')
def parse(self):
"""Entry point - parse expression"""
return self.expr()
def expr(self):
"""Parse addition/subtraction (lowest precedence)"""
node = self.term() # Parse left operand
# Handle left-associative operators: a+b+c = ((a+b)+c)
while self.cur()[0] == TOK_OP and self.cur()[1] in '+-':
op = self.cur()[1]
self.eat(TOK_OP)
# Create binary operation node: ('bin', operator, left, right)
node = ('bin', op, node, self.term())
return node
def term(self):
"""Parse multiplication/division (higher precedence)"""
node = self.fact()
# Same pattern as expr(), but for */ operators
while self.cur()[0] == TOK_OP and self.cur()[1] in '*/':
op = self.cur()[1]
self.eat(TOK_OP)
node = ('bin', op, node, self.fact())
return node
def fact(self):
"""Parse factors: numbers, parentheses, unary operators (highest precedence)"""
tok = self.cur()
# Unary plus/minus: +5, -3
if tok[0] == TOK_OP and tok[1] in '+-':
op = tok[1]
self.eat(TOK_OP)
return ('un', op, self.fact()) # Unary operation node
# Number literal
if tok[0] == TOK_NUM:
self.eat(TOK_NUM)
return ('num', tok[1]) # Number node
# Parenthesized expression
if tok[0] == TOK_LP:
self.eat(TOK_LP)
node = self.expr() # Parse inner expression
self.eat(TOK_RP) # Expect closing paren
return node
# Syntax error
raise SystemExit(f'Unexpected token: {tok}')
# =============================================================================
# PHASE 3: CODE GENERATION
# =============================================================================
reg_cycle = ['rax', 'rbx', 'rcx', 'rdx'] # Available x86-64 registers
def gen(node, code, it=[0]):
"""
CODE GENERATION - Tree walking code generator
Recursively traverses AST and emits x86-64 assembly instructions
Uses register allocation with cycling through available registers
"""
def nextreg():
"""Get next available register (cycles through rax->rbx->rcx->rdx->rax...)"""
r = reg_cycle[it[0] % len(reg_cycle)]
it[0] += 1
return r
# Number literal: load immediate value
if node[0] == 'num':
r = nextreg()
code.append(f'mov {r}, {node[1]}') # mov rax, 42
return r
# Unary operation: +expr or -expr
if node[0] == 'un':
r = gen(node[2], code) # Generate code for operand
if node[1] == '+':
return r # Unary + is no-op
if node[1] == '-':
code.append(f'neg {r}') # Two's complement negation
return r
# Binary operation: left op right
if node[0] == 'bin':
# Generate code for both operands (left-to-right evaluation)
l = gen(node[2], code) # Left operand in register l
r = gen(node[3], code) # Right operand in register r
# Emit operation instruction
if node[1] == '+':
code.append(f'add {l}, {r}') # l += r
if node[1] == '-':
code.append(f'sub {l}, {r}') # l -= r
if node[1] == '*':
code.append(f'imul {l}, {r}') # l *= r (signed multiply)
if node[1] == '/':
# Division is complex in x86-64: requires rax/rdx register pair
code += [
f'mov rax, {l}', # Move dividend to rax
'cqo', # Sign-extend rax into rdx:rax
f'idiv {r}', # Signed divide rdx:rax by r
f'mov {l}, rax' # Move quotient back to result register
]
return l # Result is in left register
# Unknown AST node type
raise SystemExit('Unknown node')
# =============================================================================
# PHASE 4: ASSEMBLY OUTPUT
# =============================================================================
# Program wrapper - creates complete Linux executable
HEAD = 'global _start\nsection .text\n_start:' # ELF entry point
TAIL = '\n mov rdi, rax\n mov rax, 60\n syscall\n' # Linux exit syscall
def compile_expr_to_asm(expr):
"""
MAIN COMPILATION PIPELINE
Takes S expression string, returns complete x86-64 assembly program
"""
# Phase 1: Tokenize source code
toks = tokenize(expr)
# Phase 2: Parse tokens into AST
ast = Parser(toks).parse()
# Phase 3: Generate assembly code
body = [] # Assembly instruction list
it = [0] # Register counter (mutable for nested calls)
result_reg = gen(ast, body, it)
# Ensure final result is in rax (required for exit code)
if result_reg != 'rax':
body.append(f'mov rax, {result_reg}')
# Phase 4: Wrap in complete program
# Indent body instructions and combine with header/footer
asm = [HEAD] + [' ' + line for line in body] + [TAIL]
return '\n'.join(asm)
def main():
"""Command-line interface - reads S file, writes assembly file"""
ap = argparse.ArgumentParser(description='S4C: S to x86-64 Compiler')
ap.add_argument('src', help='S source file (MVP: a single expression)')
ap.add_argument('-o', '--out', default='out.asm', help='Output assembly file')
args = ap.parse_args()
# Read source expression from file
expr = open(args.src).read().strip()
# Compile to assembly
asm = compile_expr_to_asm(expr)
# Write assembly output
open(args.out, 'w').write(asm)
print(f'[ok] wrote {args.out}')
if __name__ == '__main__':
main()
itoa + write.How the Compiler Works
1. Lexical Analysis (Tokenizing)
tokenize() converts source text into a stream of tokens:"2 + 3 * 4" → [NUM(2), OP(+), NUM(3), OP(*), NUM(4), EOF]+ - * /( )2. Syntax Analysis (Parsing)
Parser builds an Abstract Syntax Tree (AST) with correct operator precedence:expr() handles + and - (lowest precedence)term() handles * and / (higher precedence)fact() handles numbers, unary operators, parentheses (highest precedence)('bin', '+', ('num', 2), ('bin', '*', ('num', 3), ('num', 4)))3 * 4 binds tighter than 2 +3. Code Generation (Tree Walk)
gen() walks the AST recursively and emits x86-64 assembly instructions:('num', 42) → mov rax, 42('bin', '+', l, r) → add left_reg, right_reg['rax', 'rbx', 'rcx', 'rdx']mov rax, dividend; cqo; idiv divisorneg register4. Assembly Output & Wrapper
compile_expr_to_asm() wraps generated instructions in a complete program:HEAD: global _start; section .text; _start:TAIL: mov rdi, rax; mov rax, 60; syscall (Linux exit)💡Complete Example: "2 + 3 * 4" Compilation
[NUM(2), OP(+), NUM(3), OP(*), NUM(4), EOF]('bin', '+', ('num', 2), ('bin', '*', ('num', 3), ('num', 4)))14 ✅./out; echo $? to see the result!Quiz Time!
⚙️S4 Compiler Tasks
• Call itoa_rax_to_str
• Syscall write(1, RSI, RDX) + optional newline
• Optionally exit(0) for this stage's demo
🚀Toward Stage 5
Stage 5 — Bootstrapping
Goal
The S compiler is now written in S itself — the language becomes self‑hosting. Use s4c from Stage 4 to cross‑compile sc.s into sc0, then rebuild sc1 with sc0 and verify.
What does that mean?
- Stage 4: you had a compiler (
s4c) in Python/C that compiled S to x86‑64. - Stage 5: write the compiler source
sc.sin S. - Cross‑compile once:
s4c sc.s → sc0. - Self‑rebuild:
sc0 sc.s → sc1. - Fixed point:
sha256sum sc0 sc1should match (or converge in 1–2 iterations).
From now on, the compiler evolves in its own language.
Compile the compiler with itself and ensure binaries stabilize — this proves correctness and reproducibility.
Bootstrap Steps
Write s4c in host language
Create bootstrap compiler in Python/C supporting Stage 4 S features: expressions, variables, functions, control flow, and affiche. Must output working x86-64 assembly.
Author sc.s (compiler in S)
Implement lexer, parser, and codegen entirely in S language. Keep minimal: integers, identifiers (strings for names), loops, conditionals, functions.
Cross-compile once
Use bootstrap compiler to compile the S-written compiler into binary form.
Self-rebuild
Use the compiled S compiler to recompile itself from source.
Compare outputs
Verify that both compiler binaries are identical (or converge after 1–2 iterations).
Victory! 🎉
You are now fully self-hosting. Drop the bootstrap compiler. S is self-sustaining and can evolve in its own language.
Trust Chain Checklist
Reproducible builds are critical for trust chain verification:
No randomness, no hidden timestamps in output.
Always initialize locals, handle edge cases.
Rebuilding sc.s always gives same binary.
Use exact same NASM, ld versions.
Why self‑hosting matters
Quiz
Quiz Time!
What’s next
⚙️S5 Compiler Tasks
• Support all Stage 4 features: expressions, variables, functions, I/O
• Keep implementation minimal but complete
• Self-rebuild and verify binary reproducibility
• Achieve fixed point convergence
🚀Beyond Bootstrapping
Tooling & Build
Environment
Linux x86-64 with NASM + LD.
- NASM — assembler
- LD — linker (GNU binutils)
You can also use as and gcc -nostdlib, but we stick with NASM for clarity.
Steps
sudo apt-get install nasm binutils
nasm -felf64 in.asm -o out.o
ld -o out out.o
./out; echo $?
Platform notes
macOS (Apple Silicon)
Use Docker or a VM. Native syscalls differ — this tutorial assumes Linux SysV ABI.
Windows
Use WSL (Ubuntu). Then follow the Linux steps exactly.
Playground & Exercises
Lexer
Type S code → view tokens. Toggles: show positions, show lines.
Parser
See AST structure. Click nodes to highlight spans.
Codegen
See emitted assembly and dry‑run register allocation.
Quiz
- What’s the register order for integer args (SysV AMD64)?
- What does
leavedo? - Why are labels required for control flow?
- Which register holds the return value?
- Name the callee‑saved registers (SysV AMD64).
- What does
itoa_rax_to_strreturn in RSI and RDX? - Linux x86‑64 syscall numbers for
writeandexit? - How do you verify self‑hosting reached a fixed point?