Compiler phases and structure

Compiler Phases and Structure

A compiler reads a program in a source language and translates it into an equivalent program in a target language, reporting the errors it finds in the source. An interpreter instead executes the source operations directly on the inputs, without producing a separate target program.

9 min readQuick revision

Lexical analysis

Lexical Analysis

The lexical analyser (scanner, lexer) is the first phase of a compiler. It reads the source text character by character, groups the characters into meaningful units called lexemes, and passes one token per lexeme to the parser.

12 min readQuick revision

Top-down parsing: FIRST, FOLLOW and LL(1)

Grammar Transformations for Parsing

A context-free grammar written to describe a language is often unsuitable for a parser. An ambiguous grammar gives a parser two trees to choose from. A left-recursive grammar sends a top-down parser into an infinite loop.

8 min readQuick revision

FIRST and FOLLOW Sets

A predictive parser must decide which production to apply by looking only at the next input token. A bottom-up SLR parser must decide whether to reduce by looking at the same token.

8 min readQuick revision

Top-Down Parsing and LL(1)

A top-down parser builds the parse tree from the root down, in preorder, and in doing so produces a leftmost derivation of the input. At each step it holds a leftmost nonterminal and must choose one of its productions.

10 min readQuick revision

Bottom-up parsing: LR(0), SLR, LALR and CLR

Bottom-Up Parsing and Shift-Reduce

A bottom-up parser builds the parse tree from the leaves up to the root. It starts from the input string and repeatedly replaces a substring that matches the body of a production by the production's head, a step called a reduction, until only the start symbol remains.

10 min readQuick revision

LR(0) and SLR(1) Parsing

An LR parser is a shift-reduce parser that decides each action from a table indexed by a state on top of its stack and the next input token. The states are the states of a DFA that recognises viable prefixes, so the parser always knows which productions it might be in the middle of.

10 min readQuick revision

CLR(1) and LALR(1) Parsing

SLR places a reduction A → α on every token in FOLLOW(A). FOLLOW is a global set: it collects every token that can follow A anywhere in the grammar, even when, in the particular state the parser is in, only a few of those tokens can actually come next.

10 min readQuick revision

Syntax-directed translation

Syntax-Directed Definitions

Parsing tells the compiler how a program is structured; it does not compute anything about the program. Syntax-directed translation attaches information to grammar symbols, called attributes, and attaches rules to productions that compute those attributes.

9 min readQuick revision

Syntax-Directed Translation Schemes

A syntax-directed definition says what each attribute equals but leaves the order of evaluation to the dependency graph.

9 min readQuick revision

Runtime environments

Parameter Passing and Static vs Dynamic Scope

Two questions decide what a procedure call does with names. Parameter passing answers how an actual parameter in the call is connected to the formal parameter in the procedure: by copying its value, by sharing its location, by copying in and out, or by re-evaluating the argument text.

9 min readQuick revision

Intermediate code generation

Basic blocks, DAGs and local optimisation

Basic Blocks and Control-Flow Graphs

Optimisation and code generation work on three-address code organised into basic blocks: maximal straight-line sequences that control enters only at the top and leaves only at the bottom.

8 min readQuick revision

Data-flow analysis: constant propagation, liveness and CSE

Data-Flow Analysis Framework

Global optimisations need facts that hold at a program point on every execution, or on some execution: "this definition of x may reach here", "y may be used later", "a + b has already been computed on every path to here", "z is always 3 here".

8 min readQuick revision

Constant Propagation

If a variable holds the same known constant on every path to a use, the compiler can replace the use by the constant. That enables constant folding (computing 4 * 2 at compile time), which can make more variables constant, and can make branch conditions known so that whole paths become dead.

8 min readQuick revision

Liveness Analysis

A variable is live at a program point if its current value may still be read later. A register holding a dead variable can be reused; an assignment to a variable that is dead immediately afterwards is useless and can be deleted.

9 min readQuick revision

Common Subexpression Elimination

An expression is a common subexpression if it was computed earlier and its operands have not changed since. Reusing the earlier value is common subexpression elimination; across blocks it relies on the available expressions analysis.

7 min readQuick revision