UNIT 3: Compiler Design – Exam-Focused Short Notes
1. Compiler Phases Overview
A compiler transforms source code into target code through sequential phases:
| Phase | Functional Role |
|---|---|
| Lexical Analysis | Scans source characters, groups into tokens (identifiers, keywords, etc.), removes whitespace/comments. |
| Syntax Analysis | Parses token stream into hierarchical structure (parse tree/AST) using CFG. |
| Semantic Analysis | Checks semantic consistency (type checking, scope resolution), annotates AST. |
| Intermediate Code Generation | Produces platform-independent representation (e.g., three-address code). |
| Optimization | Improves intermediate code for efficiency (speed, size). |
| Code Generation | Maps optimized intermediate code to target machine code. |
| Symbol Table Management | Centralized data structure storing identifier attributes throughout phases. |
| Error Handling | Detects and reports errors at various stages, attempts recovery. |
Single-pass vs. Multi-pass Compilation:
-
Single-pass: Combines phases in one traversal; limited optimization; used for simple languages (e.g., early Pascal).
-
Multi-pass: Separate passes for each phase; enables extensive optimization; standard for modern compilers.
[!TIP]
Past papers frequently ask for phase roles (DEC 2024, MAY 2023). Remember: lexical analysis is separated from syntax analysis for simplicity and efficiency.
2. Lexical Analysis
Role: First phase; converts raw character stream into meaningful tokens.
Token Specification:
-
Identifiers:
[a-zA-Z_][a-zA-Z0-9_]* -
Keywords: Reserved words (e.g.,
if,while). -
Operators:
+,-,*,/,=, etc. -
Constants: Integer, float, character, string literals.
-
Strings:
"([^"\\]|\\.)*"(handles escape sequences).
Regular Expressions for Complex Patterns:
- Odd number of
aand odd number ofb:
$$\boxed{(a(ba)^*b(ab)^*) + (b(ab)^*a(ba)^*)}$$
This regex generates strings with odd counts of both
aandb(though it implies equal counts; a more complex DFA-based regex exists).
Finite Automata:
-
NFA → DFA conversion via subset construction.
-
DFA used for efficient token recognition (each state represents a set of NFA states).
LEX/Flex Structure:
%{
// Declarations (C code)
%}
%%
// Rules: pattern { action }
[a-zA-Z_][a-zA-Z0-9_]* { return IDENTIFIER; }
[0-9]+ { return INTEGER; }
"+"|"-"|"*"|"/" { return OPERATOR; }
%%
// User code
Input Buffering:
-
Sentinel method: Use a sentinel character (e.g.,
\0) at buffer end to avoid bounds checks. -
Two-buffer scheme: Split input into two halves; allows lookahead for multi-character tokens (e.g.,
>=).
Separation from Syntax Analysis:
- Advantages: Simpler lexer (regex vs. CFG), efficiency (lexer can be hand-optimized), portability (tokenization independent of language syntax).
Lexical Errors:
-
Invalid characters (e.g.,
@in C). -
Unclosed strings/comments.
-
Handling: Skip invalid character, report error with line number; often panic mode (discard until whitespace).
[!TIP]
DEC 2024 asked for lexical vs. syntactic errors. Lexical errors are character-level (invalid tokens); syntactic errors are structure-level (grammar violations).
3. Syntax Analysis
Context-Free Grammars (CFGs):
-
Production rules:
A → αwhereAis non-terminal,αis string of terminals/non-terminals. -
Parse Tree: Hierarchical representation of derivation.
-
Ambiguity: Multiple parse trees for same string (e.g.,
E → E+E | E*E | idforid+id*id).
Parsing Techniques:
-
Top-down: Start from start symbol; expand non-terminals.
-
Recursive Descent: Direct coding of productions; may require left-factoring.
-
Predictive Parsing: Use
FIRST/FOLLOWsets to select production without backtracking (LL(1)).
-
-
Bottom-up: Start from tokens; reduce to start symbol.
-
Shift-reduce: General framework; LR parsers are efficient implementations.
-
LR Parsing: Uses parsing table; most powerful (LR(1)/LALR(1)/SLR(1)).
-
FIRST and FOLLOW Sets:
-
FIRST(X): Set of terminals that can begin string derived from
X. -
FOLLOW(X): Set of terminals that can appear immediately after
Xin a sentential form. -
Computation:
-
FIRST(A):-
If
A → aα, adda. -
If
A → ε, addε. -
If
A → Bα, addFIRST(B)(excludingε); ifε ∈ FIRST(B), addFIRST(α).
-
-
FOLLOW(A):-
If
S → αAβ, addFIRST(β)(excludingε). -
If
S → αAorFIRST(β)containsε, addFOLLOW(S). -
For start symbol
S, add$(end marker).
-
-
Parsing Table Construction:
-
SLR(1): Use
FOLLOW(A)for reductions onA → α. May have conflicts ifFOLLOW(A)contains multiple lookaheads. -
LALR(1): Merge LR(1) states with identical
core(sameitemset without lookahead). Smaller tables than LR(1), fewer conflicts than SLR. -
LR(1)/CLR(1): Each item includes lookahead; largest tables, most powerful.
Comparison of SLR, LALR, LR:
| Parser | Power | Table Size | Conflicts |
|---|---|---|---|
| SLR | Weakest | Smallest | More (uses FOLLOW) |
| LALR | Intermediate | Medium | Fewer (merged states) |
| LR(1) | Strongest | Largest | None (full lookahead) |
Operator Precedence Parsing:
-
Define precedence relations:
a < b(a has lower precedence),a = b,a > b. -
Construct parse table from relations; shift-reduce based on precedence.
-
Limitation: Only for operator grammars (no two adjacent non-terminals).
Ambiguity Resolution:
-
Dangling else: Associate
elsewith nearestif(precedence rule:elsebinds tighter). -
Grammar refactoring: Left-factoring, left-recursion elimination for predictive parsing.
[!TIP]
JUN 2025 & MAY 2023 frequently ask for parsing table construction (SLR/LR). Remember: SLR uses
FOLLOWfor reductions; LR(1) uses precise lookaheads.
4. Symbol Table Management
Role: Stores identifier attributes (name, type, scope, memory location, etc.) for all phases.
Attributes:
-
Name: Identifier string.
-
Type:
int,float,array[10], etc. -
Scope: Global, local, block-nested.
-
Memory: Offset in activation record, register allocation.
-
Other: Line number, parameter count, etc.
Data Structures:
| Structure | Description | Pros | Cons |
|---|---|---|---|
| Linear List | Unordered/sorted array of entries. | Simple, easy to implement. | Slow lookup (O(n)). |
| Hash Table | Hash function maps name to bucket. | Fast average lookup (O(1)). | Collisions; need handling. |
| Binary Search Tree | Ordered tree; O(log n) lookup. | Efficient for range queries. | Overhead for balancing. |
| Patricia Tree | Compact trie for strings. | Space-efficient; fast. | Complex implementation. |
Collision Handling in Hash Tables:
-
Chaining: Buckets as linked lists.
-
Open Addressing: Linear probing, quadratic probing, double hashing.
Scope Management:
-
Block-structured languages (e.g., C, Pascal): Use symbol table stack.
-
Enter new scope → push new table.
-
Exit scope → pop table; references resolve by searching from top down.
-
-
Nested procedures: Use static links (access links) in activation records to reach non-local variables.
Operations:
-
insert(name, attributes): Add new entry (check for duplicates in current scope). -
lookup(name): Search from current scope outward. -
delete(scope): Remove all entries for a scope.
[!TIP]
DEC 2024 asked for symbol table data structures. Hash tables are most common due to speed; collisions handled via chaining.
5. Syntax-Directed Translation and Semantic Analysis
Attribute Grammars:
-
Synthesized Attributes: Computed from children’s attributes (bottom-up).
-
Inherited Attributes: Computed from parent/siblings (top-down).
-
S-attributed: Only synthesized attributes (e.g., expression evaluation).
-
L-attributed: Synthesized + inherited with restrictions (inherited from left siblings only). Allows translation during top-down parsing.
Bottom-up Evaluation of S-Attributes:
-
Postorder traversal of parse tree.
-
Example: For
E → E1 + T,E.val = E1.val + T.val.
Converting L-attributed to Translation Scheme:
-
Inherited attributes passed as parameters.
-
Example: For
A → B Cwith inheritedA.i, setB.i = A.i,C.i = f(B.s).
Dependency Graphs:
-
Nodes: attribute occurrences.
-
Edges:
X.f → Y.gifgdepends onf. -
Acyclic → evaluation order exists; cyclic → undefined semantics.
Syntax-Directed Translation Schemes:
-
Expressions:
E → E1 + T { E.val = E1.val + T.val } -
Assignments:
S → id = E { gen(id.place = E.place) } -
Control Statements:
-
if: Use backpatching for boolean expression true/false lists. -
while:S → while (B) S1; backpatchB.truetoS1,B.falseto next. -
Switch-case: Generate jump table; each case label points to corresponding code.
-
Type Checking and Conversion:
-
Implicit conversion (coercion): e.g.,
inttofloatinfloat f = 5;. -
Explicit conversion: Cast operators (e.g.,
(int)x). -
Overloading resolution: Select function/operator based on argument types during semantic analysis.
Backpatching:
-
For boolean expressions and control flow:
-
True list: Jump targets when condition true.
-
False list: Jump targets when condition false.
-
-
Process:
-
Generate code with placeholders (
_). -
Maintain lists for each boolean subexpression.
-
For
if (B) S: backpatchB.truetoS;B.falseto next. -
For
while (B) S: backpatchB.truetoS,B.falseto after loop; loop backpatch.
-
[!TIP]
JUN 2025 asked for backpatching with flow-of-control statements. Remember:
ifuses two lists;whileuses backpatch to loop start.
6. Intermediate Code Generation
Three-Address Code (TAC):
-
Characteristics: At most three operands per instruction; temporary variables.
-
Example:
a = b + c * d→t1 = c * d; t2 = b + t1; a = t2.
Representations:
| Representation | Structure | Pros | Cons |
|---|---|---|---|
| Quadruples | (op, arg1, arg2, result) |
Simple; easy to optimize. | Extra fields; temp names. |
| Triples | (op, arg1, arg2) (implicit result) |
No temp names; compact. | Hard to update (no pointers). |
| Indirect Triples | Array of pointers to triples. | Efficient reordering. | Indirect access overhead. |
Translation Examples:
-
Arithmetic:
d = (a-b) + (a-c) + (a-c)→ quadruples with common subexpression elimination. -
Boolean:
if (a < b || c < d)→B1: a < b? → true_list1, false_list1; B2: c < d? → true_list2, false_list2; merge true lists; false list = union. -
Control Structures:
-
if (B) S1 else S2:B.code → ... with true/false lists backpatch(B.true, S1.code) backpatch(B.false, S2.code) -
switch p+q { case 1: x=x+1; case 2: y=y+2; ... }→ generate jump table based onp+qvalue.
-
-
Procedure Calls:
-
Parameter passing:
call p(a,b)→param a; param b; call p. -
Return value:
return e→t = e; return t.
-
Directed Acyclic Graphs (DAGs):
-
Definition: Directed graph with no cycles; nodes represent operations/values; leaves are variables/constants.
-
Properties: Common subexpressions share nodes; enables optimization (e.g., CSE).
-
Construction:
-
Leaf nodes for variables/constants.
-
Internal nodes for operators; if node exists, reuse it.
-
Example:
-
$$a + a \times (b - c) + (b - c) \times d$$
- Nodes: `a`, `b`, `c`, `d`, `t1=b-c`, `t2=a*t1`, `t3=a+t2`, `t4=t1*d`, `t5=t3+t4`.
- DAG: `a` and `t1` reused; `t1` node used in `t2` and `t4`.
[!TIP]
DEC 2024 & JUN 2025 asked for DAG construction. Remember: reuse existing nodes for identical subexpressions; leaves are identifiers/constants.
7. Runtime Environments
Storage Allocation Strategies:
| Strategy | When Allocated | Deallocation | Use Case |
|---|---|---|---|
| Static | Compile-time | Never (program lifetime) | Global variables, code. |
| Stack | Procedure entry | Procedure exit | Local variables, parameters. |
| Heap | Dynamic (malloc/new) | Explicit (free/delete) or GC | Dynamic data structures. |
Activation Record (AR) Structure:
|------------------------|
| Actual parameters | ← Caller pushes
|------------------------|
| Return address |
|------------------------|
| Control link (dynamic) | → Caller’s AR
|------------------------|
| Access link (static) | → Parent’s AR (for nested procs)
|------------------------|
| Local variables |
|------------------------|
| Temporaries |
|------------------------|
- Example: Nested procedure
PinsideQ;PaccessesQ’s locals via static link.
Procedure Calls and Returns:
-
Caller actions: Push args, return addr, old FP; set new FP.
-
Callee actions: Save registers, allocate local vars.
-
Return: Restore registers, pop AR, jump to return addr.
Parameter Passing Mechanisms:
-
Call-by-value: Copy argument value; callee modifications invisible.
-
Call-by-reference: Pass address; callee modifies original.
-
Copy-restore (call-by-value-result): Copy in on entry, copy out on exit; issues with aliasing.
Non-local Variable Access:
-
Static links: Each AR points to immediate enclosing scope’s AR; traverse links to find variable.
-
Displays: Array of pointers to ARs of each nesting level; direct access.
Polymorphic Functions:
-
Functions that work with multiple types (e.g., generics in Java).
-
Implemented via type descriptors passed as hidden parameters.
Memory Management:
-
Garbage Collection: Reclaim unreachable heap objects.
-
Mark-and-sweep: Mark reachable objects, sweep unmarked.
-
Copying: Divide heap, copy live objects to other half.
-
[!TIP]
DEC 2024 asked for activation records and stack vs. heap. Stack for procedures (LIFO); heap for dynamic objects (arbitrary order).
8. Code Optimization
Goals: Improve execution speed, reduce size/power without changing behavior.
Basic Blocks:
-
Definition: Sequence of statements with single entry (first) and single exit (last); no jumps inside.
-
Construction: Partition code at leaders (first statement, target of jump, after jump).
-
Flow Graphs: Nodes = basic blocks; edges = control flow between exits/entries.
-
Reducible Flow Graphs: Only loops with single entry (natural for structured programs); important for optimization (e.g., loop detection).
Optimization Techniques:
| Technique | Description | Example (from a + a*(b-c) + (b-c)*d) |
|---|---|---|
| Constant Folding | Evaluate constant expressions at compile time. | 3+5 → 8. (DEC 2024) |
| Common Subexpression Elimination (CSE) | Reuse computed values for identical expressions. | b-c computed once → t1 = b-c. (MAY 2023) |
| Dead Code Elimination | Remove unreachable or unused statements. | x = y; if x never used. |
| Code Motion | Move loop-invariant code outside loop. | t = a+b outside for if a,b unchanged. (MAY 2023) |
| Variable Propagation | Replace variable with its known value. | x=5; y=x+2 → y=7. (MAY 2023) |
| Strength Reduction | Replace expensive ops with cheaper (e.g., * → +). |
x*8 → x<<3. (MAY 2023) |
| Peephole Optimization | Local window (2-4 instructions) improvement. | Remove MOV R1,R1; replace LDA R1,0 with CLR R1. (JUN 2025) |
Loop Optimizations:
-
Unrolling: Duplicate loop body to reduce branch overhead.
-
Fusion: Combine adjacent loops with same bounds.
-
Fission: Split loop to improve cache locality.
-
Induction Variable Elimination: Replace variables that change linearly (e.g.,
i=i+1) with canonical form.
DAGs in Optimization:
-
Construct DAG for basic block; eliminate common subexpressions (shared nodes).
-
Example: For
a + a*(b-c) + (b-c)*d, DAG showsb-ccomputed once.
Dependency Graphs:
-
Data Dependencies: Flow (true), anti, output.
-
Control Dependencies: Statement execution depends on predicate.
-
Used to guide reordering safely.
[!TIP]
JUN 2025 asked for peephole optimization; MAY 2023 asked for loop optimization. Remember: constant folding is a form of local optimization; loop unrolling reduces branch cost.
9. Error Handling
Error Types:
-
Lexical: Invalid characters, malformed numbers.
-
Syntactic: Missing
;, mismatched parentheses. -
Semantic: Type mismatch, undeclared variable.
-
Logical: Infinite loop (not caught by compiler).
Detection Points:
-
Lexical: During tokenization.
-
Syntactic: During parsing (parse error).
-
Semantic: During semantic analysis (type checking, scope resolution).
Recovery Strategies:
| Strategy | Method | Example |
|---|---|---|
| Panic Mode | Discard tokens until synchronizing token (e.g., ;). |
Skip until next } after { missing. |
| Phrase-level | Insert/delete tokens to continue parsing. | Insert missing ); delete extra token. |
| Error Productions | Add grammar rules for common errors. | S → error ; to recover after missing ;. |
| Global Correction | Minimum edits to make program valid (expensive). | Dynamic programming (not common). |
Error Reporting:
-
Meaningful messages:
"Unexpected token '+' at line 15". -
Include line number, column, error type.
-
Avoid cascading errors (recover quickly).
Dedicated Error Handling Phase?
-
Not a separate phase; integrated throughout compilation.
-
Error recovery often in parser (syntax-directed).
[!TIP]
DEC 2024 asked about error handling phase. Emphasize: recovery strategies are embedded in parser; panic mode is simplest.
10. Additional Specialized Topics
Bootstrapping:
-
Concept: Self-compilation; compiler written in its own language.
-
Stages:
-
Write compiler
C1in machine code (or another language). -
Use
C1to compileC2(written in source language) →C2in machine code. -
Now
C2can compile itself (C2compilesC2).
-
-
Cross-compiler: Runs on machine A, generates code for machine B.
Operator Precedence Parsers:
-
Define precedence relations:
a < b(a has lower precedence),a = b,a > b. -
Parse table: Rows/columns are terminals; entries: shift (
<,=), reduce (>), blank (error). -
Example: For
E → E+E | E*E | id,*>+, soid * id + idreducesid*idfirst.
Type Conversion:
-
Implicit (coercion): Compiler inserts conversions (e.g.,
int→floatinfloat f = 5;). -
Explicit: Cast operators (e.g.,
(int)3.14). -
Example: In
int i; float f; i = f;→ implicit conversionfloat→int(may lose precision).
Function Overloading:
-
Multiple functions with same name but different parameter types.
-
Resolution: During semantic analysis, select best match based on argument types (e.g.,
print(int)vsprint(float)). -
Ambiguity: If no unique best match, error.
Reducible Flow Graphs:
-
Definition: Flow graph where every cycle has a single entry node (loop header).
-
Properties: Natural for structured programs (no
gotointo loops). -
Importance: Enables loop optimization (e.g., code motion, induction variables).
-
Comparison: Non-reducible graphs (from arbitrary
goto) hinder optimization.
Input Buffering:
-
Beyond sentinel method: Two-buffer scheme with lexeme pointer and forward pointer.
-
Allows lookahead for multi-character tokens (e.g.,
>=,>>=). -
Sentinel avoids bounds check but requires copying when buffer wraps.
[!TIP]
JUN 2025 asked for reducible flow graphs and input buffering. Reducible graphs have single-entry loops; input buffering uses two buffers for efficient lookahead.