1.2 Phases of a Compiler: Analysis vs Synthesis, Front-End vs Back-End
π‘ Core Intuition: The 3-Second Mental Modelβ
π³ The Everyday Analogy: The International Publishing Houseβ
Imagine translating an ancient Sanskrit legal manuscript into modern Japanese engineering specifications:
- The Analytical Linguist (Front-End / Analysis):
- Reads the raw characters and groups them into meaningful words (Lexical Analysis).
- Verifies grammatical sentence structure (Syntax Analysis).
- Checks the legal and contextual sanity of clauses (Semantic Analysis).
- Condenses the text into a neutral, unambiguous intermediate outline (Intermediate Code Generation).
- The Target Fabricator (Back-End / Synthesis):
- Eliminates redundant clauses and streamlines sentence flow for conciseness (Code Optimization).
- Translates the streamlined outline into idiomatic modern Japanese utilizing available technical terminology and page formatting (Target Code Generation).
π» Bridging to Computer Science: Analysis vs Synthesis Architectureβ
This two-part division is the cornerstone of modern compiler design: The Front-End understands the source language, while the Back-End understands the target silicon. By inserting a machine-independent Intermediate Representation (IR) between them, we decouple language semantics from hardware constraints, enabling the legendary compiler scalability.
πTable of Contents
- ποΈ The 6 Canonical Compiler Phases Pipeline
- π Formal Mathematical & Architectural Foundations
- βοΈ Concrete Step-by-Step Execution Trace
- ποΈ Front-End vs Back-End: The Retargetability Invariant
- π Passes vs Phases & Cross-Compilers
- π οΈ Compiler Construction Tools
- π Real-World Production Context:
- π Numerical Applications & Worked Examples
- π― Exam & Interview Pitfall Check
ποΈ The 6 Canonical Compiler Phases Pipelineβ
Every production compiler processes source text across six sequential phases:
The 6 Canonical Compiler Phases Pipeline
Decoupling Front-End (Analysis) from Back-End (Synthesis) via Intermediate Representation
Analysis Phase (Source-Language Dependent)
Lexical Analyzer
Scans characters into tokens; strips comments and whitespace.
Syntax Analyzer
Constructs hierarchical parse tree verifying grammar rules.
Semantic Analyzer
Enforces static type checking, scope rules, and array bounds.
Intermediate Code Gen
Generates linear machine-independent three-address code (TAC).
Synthesis Phase (Target-Hardware Dependent)
Code Optimizer
Eliminates dead code, folds constants, and optimizes loop invariants.
Target Code Generator
Allocates registers and generates target assembly or machine opcodes.
π Formal Mathematical & Architectural Foundationsβ
A compiler can be formally specified as a composition of mathematical mapping functions operating across intermediate abstract domains:
π Phase 1: Lexical Analysis (Scanner)β
Transforms a continuous stream of source characters into a stream of structured tokens:
Operates via Deterministic Finite Automata (DFA) derived from regular expressions. Strips redundant whitespace and comments.
π² Phase 2: Syntax Analysis (Parser)β
Verifies whether the sequence of tokens forms a valid sentence generated by a Context-Free Grammar :
Detects grammatical syntax violations such as mismatched parentheses or missing delimiters. Emits an Abstract Syntax Tree (AST).
π― Phase 3: Semantic Analysis (Type Checker)β
Decorates the AST with type attributes and validates contextual invariants that Context-Free Grammars cannot express:
Validations include:
- Type Checking: Ensuring operand types match operator domain requirements (e.g., float float).
- Type Coercion: Inserting implicit type-cast nodes (e.g.,
int_to_float). - Scope Checking: Ensuring identifiers are declared before use.
π Phase 4: Intermediate Code Generation (ICG)β
Constructs a low-level, machine-independent representation of the program:
Typically realized as Three-Address Code (TAC), where each instruction has at most one operator and three operands:
β‘ Phase 5: Machine-Independent Code Optimizationβ
Transforms the intermediate representation to improve time complexity, decrease binary size, and minimize energy consumption without altering program semantics:
Applies constant propagation, dead-code elimination, common subexpression elimination, and loop-invariant hoisting.
πΎ Phase 6: Target Code Generationβ
Maps the optimized intermediate representation into machine-specific assembly mnemonics or binary opcodes:
Solves two computationally hard problems:
- Instruction Selection: Choosing optimal machine opcodes.
- Register Allocation: Mapping an unbounded set of symbolic variables into a small, fixed set of physical CPU registers (NP-complete, solved via Graph Coloring heuristics).
βοΈ Concrete Step-by-Step Execution Traceβ
Let us trace the famous canonical expression through all six phases:
Execution Trace: position = initial + rate * 60 Across Compiler Phases
End-to-end transformation of the canonical expression from raw source text to x86-64 assembly
Character Scanning & Tokenization
Hierarchical Parse Tree Construction
Static Type Checking & Type Coercion
Linear Three-Address Code (TAC) Generation
Constant Folding & Temporary Pruning
Register Allocation & x86-64 Assembly Emission
ποΈ Front-End vs Back-End: The Retargetability Invariantβ
Why are compilers decomposed into Front-End and Back-End?
Front-End vs Back-End Architecture
The M + N modularity advantage over M Γ N monolithic compilers
Compiler Front-End (Analysis)
- β’Completely dependent on the source language specification (C, Rust, Go).
- β’Completely independent of the target CPU architecture.
- β’Produces machine-independent Intermediate Representation (IR).
- β’Encapsulates language grammar rules, syntax error diagnostics, and AST creation.
Compiler Back-End (Synthesis)
- β’Completely dependent on target CPU instruction set (x86, ARM64, RISC-V).
- β’Completely independent of the source language.
- β’Consumes machine-independent Intermediate Representation (IR).
- β’Performs peephole tuning, register assignment, and pipeline hazard avoidance.
The M Γ N vs M + N Scalability Lawβ
Suppose we wish to support different programming languages (e.g., C, C++, Rust, Fortran) across distinct hardware architectures (e.g., x86-64, ARM64, RISC-V, WebAssembly):
- Monolithic Compilers: Require building completely separate monolithic compilers.
- IR-Decoupled Compilers (e.g., LLVM): Require building only front-ends and back-ends, yielding an engineering complexity of:
π Passes vs Phases & Cross-Compilersβ
Passes vs Phasesβ
- Phase: A logical stage in the translation process (e.g., Lexical, Semantic).
- Pass: A complete traversal of the source program or intermediate representation stored on disk or in memory.
- Single-Pass Compiler: Interweaves lexical, syntax, semantic analysis, and target code generation in one pass. Faster compilation, small memory footprint, but cannot perform aggressive whole-program optimizations. Requires forward declarations for symbols.
- Multi-Pass Compiler: Traverses the program representation multiple times. Enables global optimizations, loop transformations, and register allocation across whole functions.
Cross-Compilers & T-Diagramsβ
A native compiler runs on machine and produces code for machine .
A cross-compiler runs on host machine and produces executable machine code for target machine ().
In compiler literature, a compiler is formalized using a T-Diagram:
Meaning: A compiler written in language that translates programs written in source language into target language .
Rule of T-Diagram Combination:β
Two compilers can be combined if the implementation language of the first matches the target language of the second:
π οΈ Compiler Construction Toolsβ
Modern compilers are rarely written entirely by hand. Standard automated generators construct phases directly from declarative formal specifications:
The Modern Compiler Construction Toolkit
Specialized generator engines automating compiler phase construction
Scanner Generators
Phase 1: Lexical Analysis- Takes regular expressions as input and produces high-speed DFA state-machine scanners in C/C++.
- Automatically handles buffering, line numbering, and token classifications.
Parser Generators
Phase 2: Syntax Analysis- Consumes Context-Free Grammars written in BNF and emits LALR(1) or LL(k) pushdown automata parsers.
- Resolves shift/reduce ambiguities using precedence and associativity declarations.
Syntax-Directed Translators
Phase 3: Semantic Analysis- Associates semantic action code blocks with grammar productions.
- Evaluates attribute values across tree nodes to perform type checking.
Optimizer & Target Engines
Phases 4-6: Synthesis- Modular SSA-based intermediate representation optimization pipeline.
- Pluggable target back-ends supporting instruction selection and register coloring.
π Real-World Production Context:β
The modern gold standard of decoupled compiler architecture is LLVM (Low Level Virtual Machine):
- Front-Ends:
clang(C/C++),rustc(Rust), andswiftc(Swift) compile diverse language grammars down into a standardized, strongly typed LLVM Intermediate Representation (LLVM IR). - Middle-End (
opt): Over 100 machine-independent transformation passes (dead code elimination, loop vectorization, constant folding) operate purely on LLVM IR. - Back-End (
llc): A single shared code generator translates LLVM IR into optimized machine assembly targeting x86-64, ARM64, RISC-V, and WebAssembly.
π Numerical Applications & Worked Examplesβ
Example 1: Compiler Phase Error Classificationβ
Classify each of the following programming errors into the exact earliest compiler phase responsible for detecting it:
int 2x = 50;x = y + z$(where$is not an allowed character in the language)if (a > b { return a; }float y = "hello" * 3.14;int x = 10 / 0;(in a static const expression context)int arr[10]; int val = arr[20];(at runtime)
Step-by-Step Solution:
-
int 2x = 50;:- The scanner encounters
2followed byx. In standard identifier lexical rules, an identifier cannot start with a digit. - Detected in: Lexical Analysis.
- The scanner encounters
-
x = y + z$:- The symbol
$does not belong to the alphabet of the language. The scanner cannot match any valid token pattern. - Detected in: Lexical Analysis.
- The symbol
-
if (a > b { return a; }:- The token stream is
IF,LPAREN,ID,GT,ID,LBRACE. The grammar production forifexpects a closing parenthesisRPARENbefore the body block: . - Detected in: Syntax Analysis.
- The token stream is
-
float y = "hello" * 3.14;:- Grammatically,
Expr * Expris syntactically valid. However, the multiplication operator is not defined between typestringand typefloat. - Detected in: Semantic Analysis (Type Checking).
- Grammatically,
-
int x = 10 / 0;(Static Constant Folding):- Evaluated during compile-time constant folding.
- Detected in: Semantic Analysis / Code Optimization.
-
int arr[10]; int val = arr[20];:- Non-constant or unchecked index access cannot be verified statically by standard parsers.
- Detected in: Runtime / Operating System (Segmentation Fault).
Example 2: T-Diagram Cross-Compiler Bootstrappingβ
A research lab has:
- A computer architecture with a native C compiler: .
- A new hardware architecture with zero software available.
- A C compiler written in C that targets architecture : .
Derive the exact sequence of compilation steps using T-diagram algebra to produce a native compiler running on that compiles C programs to :
Step-by-Step Derivation:
Step 1: Produce a Cross-Compiler Running on
Compile the source code of the B-targeting compiler () using the native compiler on ():
We now have a cross-compiler that runs on host machine and produces binary code for target machine .
Step 2: Produce the Native Compiler for
Feed the source code of the B-targeting compiler () into the newly built cross-compiler ():
The resulting binary runs natively on architecture and generates code for architecture . We copy this binary to machine . Bootstrapping complete.
π― Exam & Interview Pitfall Checkβ
Question 1: "Why is Code Optimization considered an optional phase in compilers? What invariant must the optimizer never violate?"
Key Focus Points:
- Semantic Equivalence Invariant: The optimizer can only transform code if . It must produce functionally identical output for all possible inputs.
- Optionality: A compiler must generate functionally correct machine code even at
-O0(zero optimizations). If turning off optimizations fixes a bug, the optimizer is defective!
Question 2: "Explain why Lexical and Syntax analysis cannot detect undeclared variables or type mismatches."
Key Focus Points:
- Context-Free Limits: Regular expressions (Scanner) and Context-Free Grammars (Parser) only verify lexical tokens and grammatical tree shape ().
- Contextual Requirement: Checking whether an identifier exists in the symbol table or whether
float * stringis valid requires context-sensitive Semantic Analysis.
Trap 1: Is a compiler phase the same as a compiler pass?
Answer: No, they are fundamentally distinct. A phase is a logical step of transformation (e.g. Lexical, Syntax, Semantic, ICG, Optimization, Code Gen). A pass is a physical traversal of the AST or source file representation on disk or in RAM. Multiple phases can be combined into a single pass (e.g. in single-pass compilers).
Trap 2: Is the Symbol Table discarded once Lexical Analysis finishes?
Answer: No. The symbol table persists across the entire compilation pipeline. It is created during Lexical Analysis, decorated with types during Semantic Analysis, queried for memory offsets during ICG, and updated with physical register allocations during Target Code Generation.