Type a branch, a subject or a topic — “round robin”, “paging”, “civil”.

The Phases of a Compiler

One assignment statement carried from source text to assembly, rewritten at every stage.

Skip to the animation

A 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.

PhaseTakesProducesCatches
Lexical analysischaracterstokensillegal characters
Syntax analysistokenssyntax treemalformed structure
Semantic analysissyntax treeannotated treetype errors, undeclared names
Intermediate codeannotated treethree-address code
OptimisationIRbetter IR
Code generationIRtarget 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. rate is a float and 60 is 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 + nm 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

loading visualisation…

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.

int x = "hello"; is grammatically fine. Which phase rejects it?
Why are the symbol table and error handler not counted as phases?
Where is the front end / back end split, and why does it matter?
Which phase catches an unterminated string literal?

0 / 4

4 still unanswered — the dots above jump straight to them.