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

0 / 284 lessons masteredNot started 284Learning 0Practicing 0Mastered 0
  1. 1

    What Happens to My Code

    Start here
    0/17

    The 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 + b from 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.

  2. 2

    Syntax: Grammar, Lexing, Parsing, Diagnostics

    0/31

    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.

  3. 3

    Meaning: Trees, Names and Scopes

    0/14

    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.

  4. 4

    Types: What the Language Can Prove

    0/26

    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.

  5. 5

    The Middle End: IR, CFG, SSA, Data Flow

    0/28

    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.

  6. 6

    Optimization, Loops and Legality

    0/24

    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.

  7. 7

    The Back End: Code Generation, Registers, ABI

    0/22

    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.

  8. 8

    Linking, Runtimes and Compiling at Run Time

    0/43

    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.

  9. 9

    Real Pipelines, Infrastructure and DSLs

    0/26

    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.

  10. 10

    Correctness, Tooling, Builds and AtlasLang

    0/53

    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.