UNIT 2: COMPILER DESIGN - SHORT NOTES
I. COMPILER STRUCTURE & OVERVIEW
Phases of a Compiler
A compiler transforms source code into target code through a sequence of analysis (frontend) and synthesis (backend) phases.
| Phase | Input | Output | Main Task |
|---|---|---|---|
| 1. Lexical Analysis | Source characters | Tokens | Scan input, recognize tokens using regex/FSM. |
| 2. Syntax Analysis | Tokens | Parse Tree | Check grammar structure using CFG, build parse tree. |
| 3. Semantic Analysis | Parse Tree | Annotated Tree | Type checking, enforce semantic rules. |
| 4. Intermediate Code Gen | AST/Annotated Tree | Intermediate Code (e.g., TAC) | Generate machine-independent representation. |
| 5. Code Optimization | Intermediate Code | Optimized Intermediate Code | Improve efficiency (speed, size). |
| 6. Target Code Gen | Optimized IR | Target Machine Code | Map to specific architecture (registers, instructions). |
Supporting Phases:
-
Symbol Table Manager: Stores identifier info (type, scope, address).
-
Error Handler: Detects & reports errors at each phase.
-
Pre-processor & Input Buffering: Handles macros, file inclusion, and efficient character reading.
DiagramSEARCH: "compiler phases diagram six phases"
Compiler vs. Interpreter
| Feature | Compiler | Interpreter |
|---|---|---|
| Translation | Entire program → object code | Statement-by-statement → execution |
| Memory | Needs memory for object code | Needs memory for interpreter & source |
| Speed | Faster execution (after compilation) | Slower execution (repeated analysis) |
| Error Detection | All errors at compile time | Errors detected during execution |
| Example | C, C++ | Python, JavaScript (typically) |
Frontend vs. Backend Compiler Model
| Aspect | Frontend | Backend |
|---|---|---|
| Phases | Lexical, Syntax, Semantic Analysis, IR Gen | Optimization, Target Code Gen |
| Machine Dependent | No (source language specific) | Yes (target architecture specific) |
| Advantage | Reusable for multiple targets | Reusable for multiple source languages |
| Disadvantage | Interface complexity | Interface complexity |
Cross Compiler
A compiler running on machine A that generates code for machine B (where A ≠ B). Used in embedded systems development.
Pre-processing & Input Buffering
-
Pre-processing: Macro expansion, file inclusion (
#include), conditional compilation (#ifdef). -
Input Buffering: Reads source characters in blocks (e.g., 2-buffer scheme with sentinel
#to avoid boundary checks). Reduces I/O overhead.
II. LEXICAL ANALYSIS
Role & Token Specification
-
Role: First phase. Reads source characters, groups them into lexemes, and produces a stream of tokens (token-name, attribute-pointer).
-
Token Specification: Defined by regular expressions (e.g.,
id = letter(letter|digit)*,num = digit+).
Lexical Errors
Invalid characters, unterminated comments/strings, invalid numeric constants. Often handled by error token and recovery (e.g., skip to next whitespace).
Recognition of Identifiers & Keywords
-
Lexer matches longest possible lexeme.
-
If lexeme matches a keyword pattern (e.g.,
if,while), returns keyword token. -
If lexeme matches
idpattern, checks symbol table:-
If present → returns existing entry.
-
If new → inserts into symbol table, returns pointer.
-
Finite State Machines (FSM)
-
Definition: Abstract machine (states, transitions) recognizing regular languages.
-
Applications: Token recognition (each token = FSM), pattern matching.
-
Limitations: Cannot count (e.g.,
a^n b^n), cannot handle nested/recursive structures (needs PDA).
LEX Tool
-
Purpose: Lexical analyzer generator.
-
Input: Specification file with patterns (regex) and actions (C code).
-
Output:
lex.yy.c→ ayylex()function that returns tokens. -
Usage:
lex prog.l→gcc lex.yy.c -ll→a.out.
Regular Expressions for Tokens
-
digit = [0-9] -
letter = [a-zA-Z] -
id = letter(letter|digit)* -
num = digit+(.digit+)?(E[+-]?digit+)? -
comment = "/*"(.|\n)*"*/"
III. SYNTAX ANALYSIS (PARSING)
Parser Types & Comparison
| Type | Approach | Examples | Power |
|---|---|---|---|
| Top-down | Start from start symbol, derive string. | Recursive Descent, Predictive (LL) | LL(k) grammars |
| Bottom-up | Start from string, reduce to start symbol. | LR, SLR, LALR, Operator-precedence | LR(k) grammars (most powerful CFGs) |
| Backtracking | Tries alternatives on failure. | Simple recursive descent (unbounded lookahead) | Can handle any CFG, but inefficient. |
| Non-backtracking | Uses lookahead to choose production deterministically. | LL(1), LR(1) | Requires grammar to be suitable (no left recursion, left-factored for LL). |
LL(1) Parsing
-
LL(1) Grammar: For any two productions
A → α | β:-
FIRST(α) ∩ FIRST(β) = ∅ -
If
ε ∈ FIRST(α), thenFIRST(β) ∩ FOLLOW(A) = ∅.
-
-
Predictive Parsing Table:
M[A, a]contains productionA → αif:-
a ∈ FIRST(α), or -
ε ∈ FIRST(α)anda ∈ FOLLOW(A).
-
-
Recursive Descent Parser: A set of recursive procedures, one per non-terminal. Without backtracking requires LL(1) grammar.
-
Left Factoring: Factor common prefixes.
A → αβ1 | αβ2becomesA → αA',A' → β1 | β2.
-
Left Recursion Elimination:
-
Immediate:
A → Aα | βbecomesA → βA',A' → αA' | ε. -
Indirect: Reorder productions or introduce new non-terminals.
-
LR Parsing
-
LR Parsers: Use shift-reduce parsing. Most powerful (handle all deterministic CFGs).
-
LR(0) Items: Productions with a dot
•showing progress:A → α•β. -
SLR Parsing:
-
Build canonical collection of LR(0) items.
-
Construct ACTION and GOTO tables.
-
Use FOLLOW sets to resolve conflicts on
reduceactions forA → α•.
-
-
LALR Parsing: States with identical cores (items without lookahead) are merged. More powerful than SLR, less than canonical LR(1).
-
Comparison:
| Parser | States (approx.) | Power | Conflict Resolution | | :--- | :--- | :--- | :--- | | SLR(1) | Smallest | Weakest | Uses FOLLOW(A) | | LALR(1) | Medium | Medium | Merges cores, uses lookahead | | LR(1) | Largest | Most powerful | Full lookahead per item |
Grammar Analysis
-
FIRST(X): Set of terminals that can begin strings derived from X.
-
FIRST(a) = {a} -
FIRST(αβ) = FIRST(α) ∪ (FIRST(β) if ε∈FIRST(α)) -
FIRST(A)for non-terminal:FIRST(α)for allA→α.
-
-
FOLLOW(A): Set of terminals that can appear immediately after A in some sentential form.
-
$ ∈ FOLLOW(S) -
If
A → αBβ, thenFIRST(β) \ {ε} ⊆ FOLLOW(B). -
If
A → αBorA → αBβwithε∈FIRST(β), thenFOLLOW(A) ⊆ FOLLOW(B).
-
-
Ambiguity in CFG: A grammar is ambiguous if a string has >1 parse tree or >1 leftmost/rightmost derivation. Example:
E → E + E | E * E | idforid+id*id. -
Closure Properties of CFLs: CFLs are closed under union, concatenation, Kleene star, substitution, reversal. NOT closed under intersection, complement.
IV. SYNTAX-DIRECTED TRANSLATION & SEMANTIC ANALYSIS
Syntax-Directed Definitions (SDD) & Translations (SDT)
-
SDD: CFG + semantic rules (actions) on productions. Evaluates attributes.
-
SDT: SDD with semantic actions embedded in productions.
-
Synthesized Attributes: Value computed from children's attributes. Flows up the parse tree.
- Example:
E.value = E1.value + T.valueinE → E1 + T.
- Example:
-
Inherited Attributes: Value computed from parent/siblings. Flows down or across the tree.
- Example:
S.in = trueinS → if (E) S1 else S2to propagate condition.
- Example:
-
S-attributed SDT: All attributes are synthesized. Evaluated in bottom-up order (e.g., LR parsing).
-
L-attributed SDT: Inherited attributes defined by:
-
Parent's inherited attributes.
-
Siblings' synthesized attributes to the left.
- Can be evaluated in top-down (LL) or bottom-up order.
-
-
Annotated Parse Tree: Parse tree with attribute values at each node.
-
Dependency Graph: Directed graph of attribute dependencies. Cycle = circular definition (error).
Type Checking & Type Systems
-
Role of Type Checker: Ensures operator-operand type compatibility, enforces type rules, infers types.
-
Type Expressions: Built from basic types using type constructors (array, pointer, function).
int,float,array(3, int),ptr(int),func(int)→float.
-
Type Equivalence:
-
Name equivalence: Two types are same if declared with same name (e.g.,
typedef). -
Structural equivalence: Types are same if their structures are identical (e.g.,
array(10, int)vsarray(5, array(2, int))).
-
-
Type Conversion:
-
Implicit (coercion): Automatic by compiler (e.g.,
int→floatinfloat f = 5). -
Explicit (cast): Programmer-specified (e.g.,
(int)3.14).
-
Intermediate Representations (IR)
-
Three-Address Code (TAC):
x = y op z(at most 3 addresses per instruction).-
Quadruples:
(op, arg1, arg2, result). Easy to optimize (arguments immutable). -
Triples:
(op, arg1, arg2). Result referenced by position. No temporary names, but harder to optimize (shared references). -
Indirect Triples: Quadruples stored in an array; triples point to array indices. Allows easy reordering.
-
-
Why Quadruples Preferred in Optimizing Compilers?
\boxed{\text{Quadruples allow easy access to operands and results, facilitating optimizations like constant propagation and common subexpression elimination without modifying original structure.}}
-
TAC Generation:
-
Expressions: Use translation rules (e.g.,
E → E1 + Tgeneratest = E1.code || T.code || gen(E.val = t1 + t2)). -
Control Flow:
-
if E then S1 else S2:E.true,E.falselabels. -
while E do S:begin: E.code,S.code,goto begin.
-
-
V. INTERMEDIATE CODE & BASIC BLOCKS
Flow Graph Construction
-
Definition: Directed graph where nodes = basic blocks, edges = possible control flow.
-
Construction:
-
Partition TAC into basic blocks.
-
Leader statements (first statement, target of jump, after jump) start new blocks.
-
Add edges: fall-through from block
itoi+1; jump edges fromgoto/ifstatements.
-
Basic Blocks
-
Characteristics:
-
Single entry (first statement executed once).
-
Single exit (last statement controls exit).
-
Linear sequence (no jumps inside except at end).
-
-
Partitioning Algorithm:
-
Mark first statement as leader.
-
Statement following a
goto/ifis leader. -
Statement immediately after leader is leader.
-
Each leader starts a new block; block ends before next leader.
-
Directed Acyclic Graph (DAG)
-
Definition: Directed graph with no cycles. Nodes = identifiers/constants, edges = computations.
-
Construction Algorithm for Basic Block:
-
For each statement
x = y op z:-
If
yorznot yet in DAG → create leaf node. -
If node for
y op zexists → reuse it. -
Create node for
x(or reuse if already exists).
-
-
Nodes with no children are common subexpressions.
-
-
Applications:
-
Common Subexpression Elimination: Reuse existing node.
-
Dead Code Elimination: Nodes not used later (no path to output).
-
Code Generation: Order nodes for efficient evaluation (e.g., register allocation).
-
VI. CODE OPTIMIZATION
Principle Sources
| Scope | Description | Examples |
|---|---|---|
| Local | Within a basic block. | CSE, constant folding, copy propagation. |
| Global | Across basic blocks (flow graph). | Dead code elimination, loop-invariant code motion. |
| Loop | Within loops (most impactful). | Loop unrolling, invariant code motion, induction variable elimination. |
Optimization Techniques
-
Common Subexpression Elimination (CSE): Recompute value only once. Use DAG to identify.
-
Copy Propagation: Replace
x = ywith uses ofxbyy(may enable CSE). -
Dead Code Elimination: Remove statements whose results are never used.
-
Loop-Invariant Code Motion: Move computations that produce same value in each iteration outside the loop.
- Example:
t = a * binside loop wherea,bunchanged → move before loop.
- Example:
-
Peephole Optimization: Local, window-based (2-4 instructions) improvements:
-
Redundant load/store elimination.
-
Algebraic simplifications (
x*1 → x). -
Sequence replacement (
MOV R1,R2; ADD R1,R3→ADD R2,R3).
-
Loop Optimization (Detailed)
-
Loop Invariant Code Motion:
-
Identify invariant expressions (all definitions of variables used in expression are outside loop).
-
Move to pre-header (new block before loop).
-
Ensure safety (no side effects, no exceptions).
-
-
Induction Variable Elimination: Replace variables that change linearly (
i = i + 1) with loop index.
Local vs. Global Transformations
| Local | Global |
|---|---|
| Within a single basic block. | Across multiple blocks (requires flow graph). |
| Simpler, faster. | More complex, requires data flow analysis. |
| E.g., constant folding, peephole. | E.g., global CSE, dead code elimination. |
Three Areas of Optimization
-
Machine-Independent: IR-level (TAC). CSE, DAG, loop transformations.
-
Machine-Dependent: Target-specific. Register allocation, instruction selection, peephole.
-
Architecture-Specific: Exploits special hardware (e.g., vector instructions, cache lines).
VII. CODE GENERATION
Backpatching
-
Concept: Maintain lists of unfilled jumps (for boolean expressions,
if,while). Fill addresses when target known. -
Boolean Expressions: Generate code with
true-listandfalse-list.-
E → E1 or E2:E1.trueandE2.true→E.true;E1.false+E2.false→E.false. -
Backpatch
E1.falseto start ofE2.code.
-
-
Control Statements:
-
if E then S1:E.true→S1.code;E.false→ next. -
while E do S:begin: E.code;S.code;goto begin; backpatchE.truetoS,E.falseto after.
-
Register Allocation & Assignment
-
Strategies:
-
Register Descriptor: Tracks which variables are in which registers.
-
Address Descriptor: Tracks where each variable can be found (register/memory).
-
-
Allocation: Assign variables to registers (ideally keep frequently used in registers).
-
Example: Use graph coloring:
-
Build interference graph (nodes = variables, edge = live together).
-
Color graph with
kcolors (registers). -
Spill if >k colors (store excess to memory).
-
Code Generation for Expressions & Statements
-
Expressions: Generate TAC, then map to machine instructions (e.g.,
x = y + z→LOAD y, R1; ADD z, R1; STORE R1, x). -
Statements: Use backpatching for jumps, manage control flow.
VIII. STORAGE ALLOCATION & SYMBOL TABLE
Storage Allocation Strategies
| Strategy | Mechanism | Use Case | Merits | Demerits |
|---|---|---|---|---|
| Stack Allocation | LIFO (activation records on stack). | Local variables, function calls. | Fast, simple, supports recursion. | Fixed size per activation, no sharing. |
| Heap Allocation | Dynamic (malloc/free). |
Dynamic data structures (linked lists). | Flexible, arbitrary size/lifetime. | Fragmentation, slower, manual management. |
Activation Record (AR) Model
Components (typical):
-
Return address (where to go after return).
-
Control link (dynamic link: pointer to caller's AR).
-
Access link (static link: for non-local vars in nested scopes).
-
Saved machine state (registers).
-
Parameters (passed by caller).
-
Local variables.
-
Temporaries.
DiagramCANVAS: Activation record stack with fields: return addr, control link, access link, saved regs, params, locals, temporaries
Symbol Table
-
Purpose: Store identifier info (name, type, scope, address, line number).
-
Data Structures:
-
Linear List: Simple, slow search (O(n)).
-
Hash Table: Fast (O(1) avg), handles collisions.
-
Binary Search Tree: Ordered, O(log n) search.
-
-
Operations:
insert(name, info),lookup(name),delete(name).
Polymorphic Functions & Overloading
-
Polymorphic: Function works with multiple types (e.g.,
print(int),print(float)). -
Overloading: Same name, different parameter types.
-
Resolved at compile-time (static binding) by type checking and signature matching.
-
Example:
sqrt(double),sqrt(complex).
-
IX. ERROR HANDLING & AUXILIARY TOPICS
Error Handling Phase
| Phase | Error Examples |
|---|---|
| Lexical | Invalid character (@), unterminated string. |
| Syntax | Missing ;, mismatched parentheses, id + (expecting operand). |
| Semantic | Type mismatch (int = float), undeclared variable, break outside loop. |
- Strategy: ** panic mode** (skip to synchronizing token), error productions, local correction (insert/delete token).
Dynamic Storage Allocation
-
Heap Management:
malloc(size),free(ptr). -
Issues: Fragmentation (external/internal), garbage collection (automatic reclamation).
Input Buffering (Sentinel Method)
-
Two buffers (
lexemeBegin,forwardpointers). -
Sentinel (
#) placed at end of each buffer to avoid boundary checks. -
When
forwardreaches sentinel, refill half-empty buffer.
EXAM TIPS & COMMON PITFALLS
[!TIP] Parsing Tables (SLR/LALR)
- SLR: Use
FOLLOW(A)for reductions → may have spurious conflicts.
- LALR: Merge states with same core → fewer conflicts than SLR, same as LR(1) for common grammars.
- Always compute canonical collection first. State
ihas item[A → α•Bβ, a]→goto(i, B)includes[A → αB•β, a].
[!TIP] FIRST/FOLLOW Sets
FIRST(ε) = {ε}.
- For
FOLLOW(A), include$ifAis start symbol.
- If
A → αBβ, addFIRST(β) \ {ε}toFOLLOW(B); ifε∈FIRST(β), also addFOLLOW(A).
[!TIP] DAG Construction
- Process statements in order.
- Reuse node if
y op zalready exists (same operator, same operands).
- Final node for each variable = last assignment in block.
[!TIP] Backpatching
- Maintain lists (linked lists of pending jumps).
makelist(i)returns list withi.
merge(p1, p2)concatenates lists.
backpatch(list, addr)fills alladdrin list.
[!TIP] Activation Record
- Static link (access link) for non-local variables in nested procedures.
- Dynamic link (control link) to restore caller's AR.
- Parameters usually placed above return address (in caller's AR).
[!TIP] Storage Allocation
- Stack: Fast (pointer adjustment), supports recursion, but no explicit deallocation.
- Heap: Flexible, but fragmentation and overhead.
[!TIP] Common Mistakes
- Confusing synthesized (up) vs inherited (down/across) attributes.
- Forgetting to eliminate left recursion for top-down parsers.
- Miscomputing
FIRSTfor nullable productions.
- Not partitioning TAC correctly into basic blocks (leaders).
- Assuming DAG nodes are only for expressions—they represent values of variables.