UNIT 5: Compiler Design – Advanced Topics & Optimization
I. Lexical Analysis
Input Buffering
-
Purpose: To efficiently read the input stream character-by-character without making system calls for each character.
-
Schemes:
-
Two-Buffer Scheme: Input is divided into two equal-sized buffers. The lexer reads from one while the other is being filled. A sentinel character (e.g.,
EOF) marks the buffer's end to avoid constant boundary checks. -
Sentinel Implementation: Appending a special character (e.g.,
eof) at the end of each buffer simplifies thelexemerecognition loop by eliminating explicitifchecks for buffer limits.
-
-
Key Concept: Reduces I/O overhead, crucial for large source files.
Recognition of Identifiers and Keywords
-
Process:
-
Pattern Matching: Lexer uses a ** Deterministic Finite Automaton (DFA)** derived from regular expressions to recognize the pattern
letter (letter | digit)*for identifiers. -
Token Identification: The lexeme is first matched against the identifier pattern.
-
Symbol Table Check: The lexeme is then looked up in the symbol table. If found as a reserved keyword (e.g.,
if,while), the token type is set to that keyword. Otherwise, it's anIDtoken, and a new entry may be created.
-
-
Example: Lexeme
"int"matches identifier pattern but is found in keyword table → Token:KEYWORD_int.
Regular Expressions for Language Specification
-
Definition: A compact notation to describe a set of strings (a language).
-
Common Operators:
|(alternation),*(Kleene star/closure),+(positive closure),?(optional),.(any character),[ ](character class),^(beginning),$(end). -
Example (Odd a's and odd b's):
(b*ab*a)*b*ab*a(One of many valid expressions).
Lexical Phase Errors
-
Definition: Errors detected during scanning, before parsing.
-
Common Types:
-
Illegal Character: Character not in the language's character set (e.g.,
@in C). -
Unterminated Comment/String Literal: EOF encountered before closing delimiter.
-
Overflow: Numeric constant too large for its intended type.
-
-
Handling: Lexer reports error with line/column number and attempts recovery (e.g., skip to next whitespace).
II. Syntax Analysis
Predictive Parsing
-
Type: Top-down, non-backtracking parsing using a parsing table.
-
Requirements: Grammar must be LL(1) (Left-to-right scan, Leftmost derivation, 1 token lookahead). Requires elimination of left-recursion and left-factoring.
-
Mechanism: Uses a stack and a parsing table
M[A, a](whereAis non-terminal,ais terminal). Based on current stack top and input token, it decides to expand or match/report error.
LR Parsing (SLR, LALR, LR)
-
Core: Bottom-up parsing using a shift-reduce algorithm with a state machine (constructed from an LR(0) or LR(1) automaton).
-
Parsing Table: Consists of
ACTION(shift/reduce/accept/error) andGOTO(state transition) functions.
Differences among SLR, LALR, and LR Parsers
| Feature | SLR(1) | LALR(1) | LR(1)/Canonical LR(1) |
|---|---|---|---|
| Items in State | LR(0) items | Merged LR(1) items with same core | Full, distinct LR(1) items |
| Lookahead Info | Uses FOLLOW(A) for reductions | Uses lookaheads from merged states | Uses precise lookaheads per item |
| Parsing Table Size | Smallest | Medium | Largest |
| Power (Grammar Class) | Weakest (subset of LALR) | More powerful than SLR | Most powerful (all LR(k)) |
| Conflict Resolution | May have spurious conflicts | Fewer conflicts than SLR | No conflicts if grammar is LR(k) |
| Construction Cost | Low | Moderate | High |
Construction of LR Parsing Tables (General Steps)
-
Augment Grammar: Add
S' -> S. -
Build LR(0)/LR(1) Automaton: Create items (productions with
•), compute closure, and goto transitions. -
Define States: Each state is a set of items.
-
Fill
ACTIONTable:-
Shift: On
a(terminal),ACTION[s, a] = shift tifgoto(s, a)=t. -
Reduce: On
a(terminal),ACTION[s, a] = reduce A->βifA->βis a completed item insanda∈ lookahead set. -
Accept: On
$, ifS'->S•is in states. -
Error: Otherwise.
-
-
Fill
GOTOTable:GOTO[s, A] = tfor non-terminalA.
Syntactic Phase Errors
-
Definition: Errors due to violation of grammar rules (missing/extra/misplaced tokens).
-
Common Types:
-
Missing Symbol:
if (x) y++;(missing;). -
Extra Symbol:
int int x;. -
Mismatched Delimiters:
{ ... ( ... } ... ). -
Unexpected Token:
switch (x) { case 1: ... default: ... }(missingcasekeyword).
-
-
Handling: Parser reports error at synchronization point (often the unexpected token) and attempts recovery (e.g., panic mode: skip tokens until a synchronizing token like
;,}).
III. Semantic Analysis
Type Conversion
-
Definition: Implicit or explicit conversion of one data type to another.
-
Categories:
-
Widening (Promotion):
int→float(safe, preserves value). -
Narrowing (Casting):
float→int(may lose precision).
-
-
Implementation: Semantic actions in syntax-directed definitions. Type rules are applied during parsing/translation. Type checking ensures operands of operators are compatible.
Polymorphic Functions
-
Definition: A single function name that can be applied to arguments of different types.
-
Mechanism: The specific function to call is determined at compile-time (overloading) or run-time (overriding/generics).
-
Example (C++):
max(int a, int b)andmax(float a, float b).
Function Overloading
-
Definition: Defining multiple functions with the same name but different parameter types/lists in the same scope.
-
Resolution: Compiler selects the best match based on argument types at the call site.
-
Example:
print(int),print(float),print(string).
L-Attributed Definitions
-
Definition: A class of syntax-directed definitions (SDDs) where attributes can be evaluated in a single left-to-right pass of a parse tree.
-
Rule: For a production
A -> X1 X2 ... Xn, each semantic rule forA's synthesized attributes can only depend on:-
Attributes of
X1...Xn. -
Inherited attributes of
A(passed from parent). -
Inherited attributes of
X1(passed from left siblingsX1...Xi-1).
-
-
Use: Enables translation during top-down (predictive) or bottom-up (LR) parsing without building the full parse tree.
IV. Intermediate Code Generation
Three-Address Code (TAC)
-
Definition: A sequence of statements of the form
x = y op z(at most three addresses/operands per statement). It is a linearized representation of the syntax tree. -
Properties: Simple to generate and optimize. Uses temporary variables (
t1,t2, ...). -
Example:
a = b * -c + d→t1 = -c; t2 = b * t1; a = t2 + d;
Quadruples and Triples
-
Quadruple: A 4-tuple
(op, arg1, arg2, result). Result field holds address of result.- Example:
(+, b, c, t1)
- Example:
-
Triple: A 3-tuple
(op, arg1, arg2). No result field; result is referred to by its position in the triple array.-
Example:
(+, b, c)→ result is triple(1). -
Advantage: Avoids temporary names. Disadvantage: Hard to optimize (can't move statements easily).
-
Directed Acyclic Graphs (DAGs)
-
Definition: A graph representation of a basic block where common subexpressions are shared (nodes represent values, edges represent computations).
-
Construction for Expressions:
-
Create a leaf node for each variable/constant.
-
For an operator
opwith operandsarg1,arg2:-
If a node
(op, arg1, arg2)already exists, use its existing node. -
Else, create a new node
(op, arg1, arg2).
-
-
The final node represents the expression's value.
-
-
Applications in Optimization:
-
Common Subexpression Elimination: Sharing nodes eliminates redundant calculations.
-
Constant Folding: If both children of an operator are constants, compute and replace with a constant node.
-
Dead Code Elimination: Nodes not reachable from the final output can be removed.
-
[!TIP] DAG Construction Example (Dec 2024):
Expression:
a + a*(b-c) + (b-c)*d
- Nodes:
a,b,c,d.
- Compute
t1 = b - c→ Node(-, b, c).
- Compute
t2 = a * t1→ Node(*, a, t1).
- Compute
t3 = t1 * d→ Node(*, t1, d).
- Compute
t4 = a + t2→ Node(+, a, t2).
- Final:
t5 = t4 + t3→ Node(+, t4, t3).
Backpatching for Control Flow Statements
-
Problem: Generating TAC for conditional/unconditional jumps (
if,goto,? :) where the target label is unknown until later. -
Solution: Use lists of unfilled jumps (
true-list,false-list,next-list).-
For
if (B) S1 else S2:-
Evaluate
B→B.true,B.falselists. -
Backpatch
B.truetoS1.code. -
Emit
goto→ add toS1.nextlist. -
Backpatch
B.falsetoS2.code. -
S.nextlist = merge(S1.nextlist, S2.nextlist).
-
-
Backpatch(list, target): Fills all
(j____, _, _, _)inlistwithtarget.
-
Translation of Switch Statements
-
Approach: Translate into a sequence of
if-else ifor a jump table. -
TAC Example:
switch p+q { case 1: x=x+1; break; case 2: y=y+2; break; case 3: z=z+3; break; default: c=c-1; }-
Compute
t1 = p + q. -
Emit
if t1 == 1 goto L1 -
Emit
if t1 == 2 goto L2 -
Emit
if t1 == 3 goto L3 -
Emit
goto Ldefault -
L1: x = x + 1; goto Lend -
L2: y = y + 2; goto Lend -
L3: z = z + 3; goto Lend -
Ldefault: c = c - 1 -
Lend:
-
V. Optimization
Basic Blocks and Flow Graphs
-
Basic Block: A sequence of consecutive statements with:
-
Single Entry: Control enters only at the first statement.
-
Single Exit: Control leaves only after the last statement.
-
-
Construction:
-
Identify leaders (first statement, target of jump, statement after jump).
-
Each leader starts a new block; block ends at next leader - 1.
-
-
Flow Graph: Nodes = basic blocks. Edges = possible control flow (fall-through and jump edges).
Peephole Optimization
-
Definition: Local optimization on a small sliding window (peephole) of target code.
-
Common Patterns:
| Inefficient Pattern | Optimized Replacement | | :--- | :--- | |
MOV R1, R1| (delete) | |MOV R1, c1; MOV R1, c2|MOV R1, c2| |ADD R1, 0| (delete) | |MUL R1, 1| (delete) | |LOAD R1, a; STORE a, R1|*(delete)*| |JMP L1; L1: ...|*(delete JMP)*| |L1: ...; JMP L2; L2: ...|*(delete JMP L2)*|
Loop Optimizations
-
Goal: Reduce execution time of loops (most critical for performance).
-
Key Techniques:
-
Loop Invariant Code Motion: Move calculations that produce the same result in every iteration outside the loop.
- Example:
for(i=0;i<n;i++) x = y*z + a;→ Movey*zout.
- Example:
-
Induction Variable Elimination: Replace variables that change by a constant amount (induction variables) with a single variable and stronger loop exit condition.
-
Strength Reduction: Replace expensive operations (
*,/) with cheaper ones (+,-).- Example:
x = i * 4→x = x + 4(ifxstarts at 0).
- Example:
-
Loop Unrolling: Replicate loop body multiple times to reduce branch overhead.
-
Loop Fusion/Fission: Combine/split loops for better cache locality.
-
Constant Folding and Propagation
-
Constant Folding: Evaluate constant expressions at compile-time.
- Example:
3 * 4 + 5→17.
- Example:
-
Constant Propagation: Replace variables known to hold constant values with the constants themselves.
- Example:
a = 5; b = a + 3;→b = 5 + 3→ (folding)b = 8.
- Example:
-
Application: Primarily performed on Three-Address Code or DAGs.
VI. Runtime Environment
Activation Records and Procedure Calls
-
Activation Record (AR) / Stack Frame: Data structure for a single procedure invocation. Contains:
-
Return Address
-
Actual Parameters (or pointer to them)
-
Control Link (static link - pointer to caller's AR)
-
Access Link (non-local variable access)
-
Saved Machine Registers
-
Local Variables & Temporaries
-
-
Procedure Call Steps:
-
Caller pushes arguments and return address.
-
Control transfers to callee.
-
Callee allocates space for locals (adjusts stack pointer).
-
Callee executes; on return, it deallocates locals, restores registers, jumps to return address.
-
Caller may pop arguments.
-
Storage Allocation Strategies
-
Stack Allocation:
-
Mechanism: LIFO allocation. ARs are pushed/popped with procedure calls/returns.
-
Use Case: Local variables in procedural languages (C, Pascal). Size known at compile-time.
-
Advantages: Efficient, automatic deallocation.
-
Disadvantages: No
free(); cannot support arbitraryfree().
-
-
Heap Allocation:
-
Mechanism: Dynamic, non-LIFO. Memory managed by
malloc/free(C) ornew/delete(C++). -
Use Case: Dynamic data structures (linked lists, trees), objects with lifetime beyond scope.
-
Management: Requires a memory manager (free lists, garbage collection).
-
Advantages: Flexible lifetime.
-
Disadvantages: Fragmentation, overhead, potential memory leaks.
-
| Aspect | Stack Allocation | Heap Allocation |
|---|---|---|
| Allocation/Deallocation | Automatic (on call/return) | Manual (malloc/free) or GC |
| Speed | Very Fast (pointer bump) | Slower (search free lists) |
| Fragmentation | None (LIFO) | Possible (external/internal) |
| Size Known? | Yes (at compile-time) | No (runtime) |
| Primary Use | Local variables, ARs | Dynamic objects, long-lived data |
Symbol Tables and Data Structures
-
Purpose: Store information (attributes) about each identifier (name, type, scope, address, etc.).
-
Required Operations:
insert(name, attributes),lookup(name),delete(scope). -
Data Structures:
-
Linear List (Unordered/Ordered): Simple, but
lookupis O(n). -
Binary Search Tree (BST):
lookupO(log n). Does not handle scopes well. -
Hash Table: Most common.
lookup/insertaverage O(1). Uses hash function on identifier name. Collision handling via chaining (linked lists) or open addressing. -
Lexical Scoping: Implemented via symbol table stack (push on block entry, pop on exit). Each block/function has its own hash table/scope.
-
VII. Error Handling
Error Handling Phase
-
Not a separate phase! Error detection/recovery is integrated into lexical, syntax, and semantic analysis phases.
-
Goals: Report error clearly (location, type), recover to find more errors (avoid "cascade").
-
Strategies:
-
Panic Mode: Skip tokens until a synchronizing token (
;,}). -
Phrase-Level Recovery: Insert/delete tokens to allow parsing to continue (requires knowledge of common errors).
-
Error Productions: Add erroneous productions to grammar (e.g.,
missing_semi -> /* empty */). -
Global Correction: Find minimal sequence of changes (insert/delete/replace) to make program valid (expensive, rarely used).
-
Types of Compiler Errors
| Phase | Error Type | Example |
|---|---|---|
| Lexical | Illegal character, unterminated comment | int x = @5; |
| Syntax | Missing ;, mismatched {} |
if (x) y++ (no ;) |
| Semantic | Type mismatch, undeclared variable | int x = "hello"; |
| Runtime | Division by zero, null pointer | 1/0 |
VIII. Compiler Phases Overview
| Phase | Input | Output | Primary Data Structure |
|---|---|---|---|
| 1. Lexical Analysis | Character stream | Token stream | DFA, token buffer |
| 2. Syntax Analysis | Token stream | Parse tree / Syntax tree | Parsing table, stack |
| 3. Semantic Analysis | Parse tree | Annotated parse tree / IR | Symbol table |
| 4. Intermediate Code Gen | Annotated tree | Three-address code / DAG | Quadruples/Triples |
| 5. Optimization | Intermediate code | Optimized intermediate code | Flow graph, DAG |
| 6. Code Generation | Optimized IR | Target machine code | Register descriptors, address descriptors |
| 7. Symbol Table | (Throughout) | (Updated entries) | Hash table, tree, stack |
| 8. Error Handling | (Integrated) | Error messages | Error recovery routines |
[!TIP] Exam Focus: Be prepared to trace the output of each phase for a given small code snippet (as in Jun 2025). Know the exact structure of activation records, quadruples, and DAGs. Distinguish clearly between SLR, LALR, LR parsing. Explain loop optimization with a concrete example (e.g., invariant code motion). Contrast stack vs. heap allocation with use cases.