Compilers Roadmap
Ten stages that follow the pipeline, starting at What Happens to My Code. Every stage names what it needs first and what you should be able to do before moving on. Progress is stored locally in your browser.
Where to start
Compilers & Programming Languages
10 stages · 0/284 lessonsHow source code becomes executable behavior, and how language and compiler design choices shape correctness, performance and tooling.
- What Happens to My Code
- Syntax: Grammar, Lexing, Parsing, Diagnostics
- Meaning: Trees, Names and Scopes
- Types: What the Language Can Prove
- The Middle End: IR, CFG, SSA, Data Flow
- Optimization, Loops and Legality
- The Back End: Code Generation, Registers, ABI
- Linking, Runtimes and Compiling at Run Time
- Real Pipelines, Infrastructure and DSLs
- Correctness, Tooling, Builds and AtlasLang
- 10/17
What Happens to My Code
Start hereThe map of the whole pipeline before any single stage: what happens between the characters you typed and the moment something executes, which representation each stage hands to the next, and what is lost at every handover. The design lessons sit here too, because who a language is for decides its execution model, memory model and concurrency model — every later stage is a consequence of those choices.
Before moving on: Trace
x = a + bfrom source to behavior, name which stage would report a given error, and pick interpreter, bytecode VM or ahead-of-time native for a new language from its constraints.- From Source to Behavior
- The Phases, and Why Each One Exists
- Frontend, Middle-End, Backend
- Compiler versus Interpreter Is Not a Binary
- What Seven Real Languages Actually Do
- Ahead-of-Time Compilation
- What Dies at Each Stage
- One Line, All the Way Down
- Who Is the Language For?
- The Questions a Language Definition Must Answer
- Syntax versus Semantics
- Ergonomics Is a Compiler Feature
- Choosing an Execution Model
- Choosing How Memory Is Managed
- Choosing a Concurrency Model
- Interoperability Is a Language Design Decision
- The Trade Nobody Escapes
- 20/31
Syntax: Grammar, Lexing, Parsing, Diagnostics
The front of the pipeline: grammars that settle precedence and associativity, lexers built from regular languages and automata, and parsers — recursive descent, Pratt, LL and LR — that turn tokens into a tree. Diagnostics live here because a parser that stops at the first error, or reports it without a location, is only half a parser.
Before moving on: Write a grammar without ambiguity, hand-write a lexer and a recursive-descent or Pratt parser for it, and recover from a syntax error well enough to report the next one with a message that names what was expected.
Needs first:What Happens to My Code- Formal Grammars
- Productions & Derivations
- BNF & EBNF
- Context-Free Grammars
- Ambiguous Grammars
- Operator Precedence
- Associativity
- Left Recursion
- Lexical Analysis
- Token Kinds
- Token Metadata
- Implementing a Lexer
- Regular Languages
- Finite Automata
- NFA vs DFA
- Lexer Hazards
- What a Parser Actually Does
- Parse Tree vs Abstract Syntax Tree
- Recursive Descent
- Pratt Parsing
- LL Parsing, FIRST and FOLLOW
- LR Parsing
- Shift and Reduce, Step by Step
- LL vs LR: Why Production Compilers Chose the Weaker One
- Parser Generators
- Source Locations
- Spans and Ranges
- Error Recovery
- Parser Synchronization
- Diagnostic Quality
- Suggested Fixes
- 30/14
Meaning: Trees, Names and Scopes
The tree the parser produced still says nothing about what a name refers to. This stage designs AST nodes that later passes can pattern-match on, walks them with visitors, and builds the symbol table that resolves every identifier to a declaration under Lexical Scope and Shadowing. Semantic analysis comes before types because the type checker needs resolved names to work on.
Before moving on: Design AST nodes later passes can match on, build a symbol table that resolves every identifier under shadowing, and say for any error whether it belongs to the parser, the resolver or the type checker.
Needs first:Syntax: Grammar, Lexing, Parsing, Diagnostics - 40/26
Types: What the Language Can Prove
What the language can prove about a program before running it. Typing rules and environments come first, then inference through Unification, and Why `T = List<T>` Must Fail and Hindley–Milner: Inference Without a Single Annotation, then the design space — unions, ADTs, pattern matching, nullability, gradual typing — and finally how the checker's promises are kept at run time: erasure, monomorphization, ownership and lifetimes. It follows names and scopes because a type judgement is made in an environment.
Before moving on: Apply a typing rule by hand, run unification far enough to infer a type nobody wrote, choose between erasure, reification and monomorphization from their run-time costs, and read an ownership or lifetime error as the proof obligation it is.
Needs first:Meaning: Trees, Names and Scopes- What a Type System Actually Proves
- Static and Dynamic Typing, Compared Honestly
- Type Checking: Is This Operation Defined for These Operands?
- Typing Rules: Reading the Notation With the Line Through It
- The Type Environment: What Γ Is, and Where the Compiler Keeps It
- Type Inference: Leaving the Type Off
- Hindley–Milner: Inference Without a Single Annotation
- Unification, and Why `T = List<T>` Must Fail
- Parametric Polymorphism and the Theorems You Get Free
- Ad-Hoc Polymorphism: One Name, Different Code
- Subtyping: What `Dog <: Animal` Licenses
- Variance: Why `List<Dog>` Is Not a `List<Animal>`
- Union Types
- Intersection Types
- Algebraic Data Types
- Pattern Matching
- Exhaustiveness Checking
- Nullability and Optional Types
- Gradual Typing
- Structural vs Nominal Typing
- Type Soundness
- Type Erasure and Reification
- Monomorphization
- Ownership Types
- Lifetime Analysis
- Effect Systems
- 50/28
The Middle End: IR, CFG, SSA, Data Flow
Where the tree becomes something an optimizer can reason about. Lowering to Three-Address Code, splitting it into basic blocks, building the control-flow graph and its dominator tree, then Static Single Assignment and the data-flow framework that runs analyses to a fixed point. Everything in the next two stages assumes these representations, so they come before any transformation.
Before moving on: Lower an AST to three-address code, build its CFG and dominator tree, insert phi nodes and leave SSA without breaking the program, and run a data-flow analysis to a fixed point on paper.
Needs first:Meaning: Trees, Names and Scopes- What an Intermediate Representation Is
- Why an IR Exists: M x N Becomes M + N
- Levels of IR: High, Mid and Low
- Three-Address Code
- Lowering
- Designing an IR: The Four Decisions
- IR Verification
- Many Frontends, One Backend
- The Control-Flow Graph
- Basic Blocks
- Building the CFG
- Natural Loops
- Dominators
- The Dominator Tree
- The Dominance Frontier
- Static Single Assignment
- Phi Functions
- Constructing SSA
- Why SSA Helps
- Leaving SSA
- SSA Variants
- The Data-Flow Framework
- Iterating to a Fixed Point
- Forward and Backward Analysis
- Reaching Definitions
- Liveness Analysis
- Available Expressions
- Constant Propagation
- 60/24
Optimization, Loops and Legality
The classic transformations — folding, dead-code elimination, CSE, inlining, loop-invariant code motion, vectorization — and the legality that governs each one: the The As-If Rule, observable behavior and Undefined Behavior. Legality is taught alongside the passes rather than after them, because a transformation you cannot state the precondition for is one you will misapply.
Before moving on: Apply the classic transformations by hand, state the precondition that makes each one legal, predict which ones a compiler will decline on your code, and explain a surprising result through undefined behavior or the as-if rule.
Needs first:The Middle End: IR, CFG, SSA, Data Flow- Constant Folding
- Dead Code Elimination
- Common Subexpression Elimination
- Copy Propagation
- Strength Reduction and Algebraic Identities
- Inlining
- Devirtualization
- Partial Evaluation and Specialization
- Loop-Invariant Code Motion
- Loop Unrolling
- Fusion, Fission, Interchange and Tiling
- Automatic Vectorization
- Alias Analysis
- Escape Analysis
- Bounds Check Elimination
- Optimization Legality
- Observable Behavior
- The As-If Rule
- Undefined Behavior
- How Undefined Behavior Becomes Faster Code
- Semantics Decide, Not Cleverness
- Optimization Levels
- Pass Pipelines
- Phase Ordering
- 70/22
The Back End: Code Generation, Registers, ABI
IR to machine code: instruction selection, scheduling and encoding, then Register Allocation by graph coloring or linear scan and what happens when values spill. The ABI lessons close the stage because calling conventions, stack frames and name mangling are the contract the generated code must keep with everything it links against.
Before moving on: Follow IR through instruction selection and scheduling to encoded bytes, allocate registers under pressure and predict which value spills, and read an ABI closely enough to explain why two compiled objects will not link.
Needs first:The Middle End: IR, CFG, SSA, Data Flow- Code Generation
- Instruction Selection
- Tree Pattern Matching
- Instruction Scheduling
- Peephole Optimization
- Reading Assembly Output
- Machine Code Encoding
- What a Backend Must Know About Its Target
- Register Allocation
- Live Ranges
- The Interference Graph
- Graph Colouring Allocation
- Linear Scan Allocation
- Spilling
- Coalescing and Rematerialization
- Calling Conventions
- Stack Frame Layout
- What an ABI Actually Is
- Name Mangling
- ABI Stability
- Cross-Compilation
- Target Triples
- 80/43
Linking, Runtimes and Compiling at Run Time
Everything that happens after code generation: the linker and loader resolving symbols and relocations, bootstrapping and trusting the toolchain, bytecode VMs and their dispatch loops, Just-in-Time Compilation with guards and Deoptimization, and the lowering of closures, coroutines, async and exceptions to plain control flow. It needs the back end for object files and the optimization stage for what a JIT speculates on.
Before moving on: Diagnose a link failure from the symbol it names, design a bytecode instruction set and its dispatch loop, explain a JIT deoptimization from the guard that failed, and lower a closure, an exception or an async function to ordinary control flow by hand.
- What a Linker Does
- Object Files
- Symbols and References
- Relocations
- Static Linking
- Dynamic Linking
- Shared Libraries
- Symbol Resolution Order
- The Loader
- Bootstrapping a Compiler
- Self-Hosting and the Three-Stage Build
- Reflections on Trusting Trust
- The Toolchain Is Your Trusted Computing Base
- Reproducible Compilation
- Bytecode
- Stack-Based Virtual Machines
- Register-Based Virtual Machines
- Stack VM vs Register VM
- Tree-Walking Interpreters
- Compiling to Bytecode
- The Dispatch Loop
- Where an Interpreter's Time Actually Goes
- What a Virtual Machine Has to Hold
- Just-in-Time Compilation
- Why Runtime Information Helps
- Tiered Compilation
- Profiling and Hotness
- On-Stack Replacement
- Speculative Optimization
- Guards
- Deoptimization
- Inline Caches
- What a JIT Costs
- Syntactic Sugar
- Desugaring
- Closures
- Closure Conversion
- Lambda Lifting
- Lowering Coroutines
- Lowering Async and Await
- Exception Handling
- Stack Unwinding
- Compiling Pattern Matching
- 90/26
Real Pipelines, Infrastructure and DSLs
The stages so far, seen in the toolchains you actually use: the routes Python, JavaScript, TypeScript, C++, Rust and Go take from source to behavior, What LLVM Actually Is and WebAssembly as a Compilation Target as infrastructure rather than brand names, and DSLs as languages you might build. It comes after the full pipeline because each real toolchain is a different arrangement of the same stages.
Before moving on: Describe the real route from source to behavior for six languages without flattening them into one story, use LLVM and WebAssembly for what they are, and turn down a DSL proposal for reasons you can defend.
- The CPython Pipeline
- The JavaScript Pipeline
- The TypeScript Pipeline
- The C++ Pipeline
- The Preprocessor
- Templates
- Template Instantiation
- Compile-Time Evaluation
- The Rust Pipeline
- The Go Pipeline
- Four Languages, One Program
- What LLVM Actually Is
- The Three-Phase Architecture
- Reading LLVM IR
- Clang
- GCC
- What a Toolchain Actually Contains
- WebAssembly as a Compilation Target
- The WebAssembly Execution Model
- WebAssembly Versus Native
- Domain-Specific Languages
- Internal versus External DSLs
- Should I Build a DSL?
- Implementing a DSL
- Configuration Languages
- The Tooling Cost of a DSL
- 100/53
Correctness, Tooling, Builds and AtlasLang
How a compiler is kept honest and kept usable: testing and fuzzing against Miscompilation, static analysis and language servers, debug information for optimized builds, separate and incremental compilation, whole-program and profile-guided optimization, and validating a model-generated plan as the untrusted program it is. AtlasLang closes the domain because it asks you to build every earlier stage end to end.
Before moving on: Test a compiler the way its authors do, debug an optimized build using its debug info, keep a large build proportional to the change, validate a generated plan as untrusted input, and finish AtlasLang end to end.
- Miscompilation
- Testing a Compiler
- Golden Tests
- Differential Testing
- Compiler Fuzzing
- Translation Validation
- Verified Compilers
- Compilers and Security
- Static Analysis
- Abstract Interpretation
- Control-Flow Analysis
- Interprocedural Analysis
- Linters
- Formatters
- The Concrete Syntax Tree
- The Language Server
- The Language Server Protocol
- Semantic Refactoring
- Debug Information
- Source Maps
- Debug and Release Builds
- Debugging Optimized Code
- Symbolication
- Reading What the Compiler Produced
- Compilation Units
- Separate Compilation
- Modules as Units of Separate Compilation
- Interface Files
- Incremental Compilation
- The Build Dependency Graph
- Where the Compiler Ends and the Build System Begins
- Hermetic Compilation
- Compile Time versus Runtime
- Whole-Program Optimization
- Link-Time Optimization
- Profile-Guided Optimization
- What a Profile Costs You
- Feedback-Directed Optimization
- The Compiler Is Also a Program With Performance Requirements
- A Plan Is a Program
- Agent DSLs and the Plan AST
- Typed Tool Calls
- Parse, Validate, Authorize, Execute
- Recovering Structure From Model Output
- AtlasLang: The Whole Thing
- AtlasLang: The Lexer
- AtlasLang: The Parser
- AtlasLang: Evaluating the Tree Directly
- AtlasLang: Scopes, Shadowing and a Real Bug
- AtlasLang: Three Types and One Honest Limitation
- AtlasLang: Bytecode and the Stack Machine
- AtlasLang: Eight Passes and Two Guards
- AtlasLang: What a Language Owes Its Users