The Phases of a Compiler
One assignment statement carried from source text to assembly, rewritten at every stage.
Skip to the animationA compiler is a pipeline: each phase takes one representation of the program and produces a slightly more structured, slightly more machine-like one, until what is left is instructions.
Six phases, one program
It is tempting to think of a compiler as a single large translation. It is not — it is six small ones, each with a job you can state in a sentence, and each handing its output to the next.
| Phase | Takes | Produces | Catches |
|---|---|---|---|
| Lexical analysis | characters | tokens | illegal characters |
| Syntax analysis | tokens | syntax tree | malformed structure |
| Semantic analysis | syntax tree | annotated tree | type errors, undeclared names |
| Intermediate code | annotated tree | three-address code | — |
| Optimisation | IR | better IR | — |
| Code generation | IR | target assembly | — |
The symbol table and the error handler are not phases. They sit alongside all six, because every phase both reads from and reports into them.
Following one statement down
The animation carries position = initial + rate * 60 all the way through. Two moments are worth pausing on:
- Syntax analysis is where precedence stops being a convention and becomes a fact. In the token list
id + id * num, that*binds tighter is something you know. In the tree, the multiplication is physically inside the addition — no reader can get it wrong. - Semantic analysis inserts something nobody wrote.
rateis a float and60is an integer, so a conversion appears in the tree. Much of what a compiler does is making implicit language rules explicit.
Front end and back end
The first three phases depend only on the source language. The last two depend only on the target machine. Intermediate code is the seam, and it is a load-bearing design decision rather than an implementation detail.
With m languages and n machines, a compiler per pair costs m × n compilers. Sharing an intermediate representation costs m + n — m front ends and n back ends. That arithmetic is why LLVM exists, and why adding a processor to a mature toolchain means writing a back end rather than a compiler.
Analysis and synthesis
- Analysis (the front end)
- Breaks the source up and works out what it means, building the symbol table and reporting every error the programmer will ever see.
- Synthesis (the back end)
- Builds the target program from that analysis. By this point the program is assumed correct — the back end's job is to be fast, not to complain.
- Single pass vs multi pass
- A single-pass compiler runs the phases interleaved over one read of the source, which is fast but blocks most optimisation. Multi-pass compilers materialise each representation, which is what makes global optimisation possible.
Watch it work
Check yourself
question 1 / 4
One question at a time. Pick an answer to see why it is right or wrong, then move on — there is no score to keep and nothing is saved.