Quick Review — All Units at a Glance
Compiler vs Interpreter · Structure · Phases · Error Handling
Lex · Token · Regex · DFA · NFA · Minimisation
CFG · LL(1) · LR · SLR · LALR · Parsing Tables
SDT · Three-Address Code · Quadruples · Run-Time Env
DAG · Data Flow · Basic Blocks · Register Allocation
Introduction to Compilers
Compiler vs Interpreter · Structure Overview · Phases · Error Handling
1.1 Definitions & Core Concepts
Compiler: A program that translates source code (high-level) into target code (machine code/object code) entirely before execution.
Interpreter: A program that translates and executes source code line by line without producing a separate object file.
Assembler: Translates assembly language to machine code.
Preprocessor: Processes directives (macros, includes) before actual compilation.
Cross-compiler: Runs on one machine (host) but produces code for another (target).
Bootstrapper: A compiler written in its own language (self-hosting).
Source Program: The input high-level code written by the programmer.
Target Program: The output machine/object code produced by the compiler.
1.2 Comparison: Compiler vs Interpreter
| Feature | Compiler | Interpreter |
|---|---|---|
| Translation | Entire program → object code | Line by line |
| Execution | After compilation | During translation |
| Speed | Fast execution (no re-translation) | Slower execution |
| Error reporting | All errors after compilation | Error at the specific line |
| Memory | Needs memory for object code | No separate object file |
| Debugging | Harder | Easier |
| Example | C, C++, Java (javac) | Python, Ruby, JavaScript |
1.3 Phases of a Compiler
Source Code
│
▼
┌─────────────────────────────────────────┐
│ ANALYSIS PHASES │
│ │
│ ┌──────────┐ ┌──────────┐ ┌─────┐ │
│ │ Lexical │──▶│ Syntax │──▶│ Sem │ │
│ │ Analyzer │ │ Analyzer │ │ Analy│ │
│ │ (Scanner)│ │ (Parser) │ │ tics │ │
│ └──────────┘ └──────────┘ └─────┘ │
│ │
│ SYNTHESIS PHASES │
│ │
│ ┌──────────┐ ┌──────────┐ ┌─────┐ │
│ │Intermed. │──▶│ Code │──▶│ Code│ │
│ │ Code │ │ Optimizer│ │ Gen │ │
│ │ Gen │ │ │ │ │ │
│ └──────────┘ └──────────┘ └─────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ Symbol Table Management │ │
│ │ Error Handling (all phases) │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────┘
│
▼
Target Code (Machine Code)
1.4 Passes of a Compiler
A pass is one complete scan of the source program. Single-pass compilers process each phase in one scan. Multi-pass compilers produce intermediate files between passes.
- Single-pass: Fast, less memory, but harder to optimize.
- Multi-pass: Better optimization, can handle forward references, but slower.
1-Mark Questions
Define a compiler.
A compiler is a program that translates an entire source program written in a high-level language into an equivalent target program (machine code/object code) before execution.
Define an interpreter.
An interpreter is a program that translates and executes a source program line by line without producing a separate object file.
What is a cross-compiler? [2023]
A cross-compiler runs on one machine (host) but generates machine code for a different machine (target).
What is a symbol table?
A symbol table is a data structure used by the compiler to store information about identifiers (names, types, scope, memory locations) encountered during compilation.
What is a preprocessor?
A preprocessor is a program that processes directives (macros, #include, #define) in the source code before passing it to the compiler.
What is a pass in a compiler?
A pass is one complete scan of the source program. A single-pass compiler processes the source in one scan; a multi-pass compiler uses multiple scans with intermediate files.
5-Mark Questions
Compare compiler and interpreter with at least five points. [2023]
| Feature | Compiler | Interpreter |
|---|---|---|
| Translation mode | Entire source → object code | Line by line |
| Object file | Produces .o/.exe | No separate file |
| Execution speed | Fast | Slow |
| Error detection | After full compilation | At the current line |
| Memory usage | Higher (stores object code) | Lower |
| Debugging | Harder | Easier |
| Examples | C, C++, Java | Python, JavaScript |
Explain the phases of a compiler with a neat diagram. [2023]
A compiler consists of two main parts: Analysis Phase and Synthesis Phase.
Analysis Phases:
- Lexical Analysis: Reads source characters, groups them into tokens (identifiers, keywords, operators). Output: token stream.
- Syntax Analysis (Parsing): Checks token stream against grammar rules, builds a parse tree (CST). Output: parse tree.
- Semantic Analysis: Checks semantic correctness (type checking, scope rules). Output: annotated parse tree.
Synthesis Phases:
- Intermediate Code Generation: Produces a platform-independent representation (Three-Address Code).
- Code Optimization: Improves intermediate code for speed/size (constant folding, loop optimization).
- Code Generation: Translates optimized IR into target machine code, handles register allocation.
Supporting Components: Symbol Table Management (stores identifier info) and Error Handler (reports errors from each phase).
What is a bootstrapper? Explain single-pass vs multi-pass compilers. [2022]
A bootstrapper (or bootstrap compiler) is a compiler written in its own source language. Initially, a compiler is written in another language; once it can compile itself, it is called self-hosting.
Single-pass compiler: Reads the source once and performs all phases. Advantages: fast, uses less memory. Disadvantage: limited optimization, cannot handle forward references easily.
Multi-pass compiler: Makes multiple passes over the source or intermediate representations. Advantages: better optimization, handles forward declarations. Disadvantage: slower, requires intermediate storage.
Explain the role of error handling in a compiler. [2021]
Error handling detects, reports, and recovers from errors in each phase:
- Lexical phase: Reports invalid characters (e.g., $ in a C identifier).
- Syntax phase: Reports syntax errors (missing semicolon, mismatched braces). Recovery strategies: panic-mode (skip tokens until synchronizing token), phrase-level (correct token), error production.
- Semantic phase: Reports type mismatches, undeclared variables, wrong number of arguments.
- Intermediate/Code generation: Reports overflow, unreachable code.
A good error handler: (1) detects each error accurately, (2) reports with location and nature, (3) recovers to continue compilation, (4) does not cascade errors.
15-Mark Questions
Draw and explain the complete structure of a compiler. Discuss the role and function of each phase. [2023]
SOURCE PROGRAM (.c, .java, .py)
│
▼
┌─────────────┐
│ PREPROCESS │ ← Handles #include, #define, macros
└──────┬──────┘
│ Preprocessed source
▼
┌─────────────┐
│ LEXICAL │ ← Tokenization (scanner/lexer)
│ ANALYZER │ ← Removes whitespace/comments, produces tokens
└──────┬──────┘
│ Token stream
▼
┌─────────────┐
│ SYNTAX │ ← Parsing (checks grammar rules)
│ ANALYZER │ ← Builds Parse Tree / CST
└──────┬──────┘
│ Parse Tree
▼
┌─────────────┐
│ SEMANTIC │ ← Type checking, scope validation
│ ANALYZER │ ← Annotated Parse Tree (AST)
└──────┬──────┘
│ AST
▼
┌─────────────┐
│INTERMEDIATE │ ← Three-Address Code (TAC)
│CODE GENER. │ ← Platform-independent representation
└──────┬──────┘
│ IR
▼
┌─────────────┐
│ CODE │ ← Constant folding, DAG, loop unrolling
│ OPTIMIZER │ ← Improves speed/size
└──────┬──────┘
│ Optimized IR
▼
┌─────────────┐
│ CODE │ ← Register allocation, instruction selection
│ GENERATOR │ ← Target machine code
└──────┬──────┘
│
▼
TARGET PROGRAM (.o, .exe, .class)
Symbol Table Management and Error Handling run alongside all phases.
Detailed explanation:
- Lexical Analyzer: Reads characters from source, forms the longest possible lexemes matching token patterns, outputs tokens of the form <type, value>. Example: "int" → <KEYWORD, "int">.
- Syntax Analyzer: Groups tokens into grammatical phrases. Uses a grammar (CFG). Builds a Concrete Syntax Tree (CST) or Abstract Syntax Tree (AST). Detects syntax errors.
- Semantic Analyzer: Ensures the program makes sense. Performs type checking, verifies all identifiers are declared, checks function argument counts/types.
- Intermediate Code Generator: Produces an intermediate representation like Three-Address Code (TAC). E.g.,
t1 = a + b. - Optimizer: Applies transformations to improve the IR — constant propagation, dead code elimination, loop optimization.
- Code Generator: Maps optimized IR to target machine instructions, assigns registers, generates relocatable machine code.
Distinguish between compiler, interpreter and assembler. Explain why we need compilation at all. [2022]
Compiler, Interpreter, and Assembler — Detailed Comparison:
| Aspect | Compiler | Interpreter | Assembler |
|---|---|---|---|
| Input | High-level language | High-level language | Assembly language |
| Output | Machine code / object file | Direct execution | Machine code / object file |
| Translation | Complete before execution | Simultaneous | Complete |
| Execution | After compilation | During translation | Object file executes separately |
| Speed | Fast | Slow | Fastest |
| Error reporting | After full scan | Line-by-line | After assembly |
| Examples | GCC, javac | CPython, Ruby MRI | NASM, MASM |
Why we need compilation: Computers only understand machine code (binary 0s and 1s). Writing programs directly in machine code is nearly impossible for complex software. High-level languages (C, Java, Python) are human-readable. The compiler bridges this gap by translating human-readable code into machine-executable instructions, performing critical checks (syntax, semantics), optimizing for performance, and enabling portability across platforms.
Advantages of using compilers: (1) Efficiency — compiled code runs faster. (2) Error detection — catches many errors before execution. (3) Optimization — improves code quality. (4) Portability — same source compiles on different machines.
Unit 1 Exam Tips
- Compilers vs Interpreters is almost always asked as a 5-mark comparison question.
- Phases of compiler is a guaranteed 5 or 15-mark question — memorize the order and output of each phase.
- Cross-compiler, bootstrapper, preprocessor — 1-mark definition questions are very common.
- Draw the compiler structure diagram in 15-mark questions — it carries marks even if the written explanation is brief.
Previous Year Questions — Unit 1
- [2023] What is a compiler? Differentiate between compiler and interpreter.
- [2023] Explain the various phases of a compiler with a diagram.
- [2022] What is a bootstrapper? Explain single-pass and multi-pass compilers.
- [2022] Define cross-compiler, source program, and target program.
- [2021] Explain the structure of a compiler in detail.
- [2021] What is a preprocessor? What are its functions?
Lexical Analysis
Role of Lexical Analyzer · Input Buffering · Tokens · Regular Expressions · Finite Automata · DFA · NFA · Lex Tool
2.1 Key Definitions
Lexical Analysis: The first phase of compilation. Reads source characters, groups them into tokens, removes whitespace/comments.
Token: A pair <token-name, attribute-value> produced by the lexer. Examples: <ID, pointer to symbol table>, <NUM, value>.
Pattern: The rule describing the form of lexemes for a token. Described by a regular expression.
Lexeme: The actual character sequence (instance) matching a pattern. E.g., "count" is a lexeme matching the pattern for identifier.
Input Buffering: Technique to efficiently read source characters. Uses a buffer divided into sentinel-marked halves (NUL character or EOF marker). Uses lexemeBegin and forward pointers.
Sentinel: A special character (e.g., NUL, EOF) placed at the end of each buffer half to avoid checking for buffer-end on every character.
2.2 Regular Expressions (RE)
Regular expressions describe patterns of strings. Operations:
- Union: r | s (match r OR s)
- Concatenation: rs (r followed by s)
- Kleene Closure: r* (zero or more r)
- Positive Closure: r+ (one or more r)
- Optional: r? (zero or one r)
Precedence: * (highest) → concatenation → | (lowest)
Common RE examples:
- Identifier: letter (letter | digit)*
- Integer: digit+
- Real number: digit+ . digit* ( . digit+ )?
- Binary number: (0|1)*
- Email: [a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
2.3 Finite Automata
NFA (Nondeterministic Finite Automaton): Can have multiple next states for a given input; can have ε-transitions (transitions without consuming input).
DFA (Deterministic Finite Automaton): Exactly one next state for each input symbol; no ε-transitions.
Transition Diagram: Circles = states, arrows = transitions, double circle = accepting state.
| Property | NFA | DFA |
|---|---|---|
| Next state for input | Multiple (or none) | Exactly one |
| ε-transitions | Allowed | Not allowed |
| Size | Usually smaller | Can be exponentially larger |
| Implementation | Difficult | Easy (direct table lookup) |
| Construction | Direct from RE | Subset construction from NFA |
2.4 DFA Minimisation
Minimisation of DFA reduces the number of states while keeping the language unchanged.
Algorithm (Table-filling / Partition refinement):
- Eliminate unreachable states from the start state.
- Mark distinguishable pairs: any pair where one is final and the other is not.
- For each unmarked pair (p,q), mark it if for some input 'a', the pair (δ(p,a), δ(q,a)) is already marked.
- Repeat step 3 until no more marks are added.
- Unmarked pairs are equivalent; merge them.
2.5 Lex Tool
Lex (Flex) is a tool that generates a lexical analyzer from specifications. A Lex file (.l) contains:
- Declarations: Definitions, %{ C code %}
- Translation rules: Pattern { Action }
- Auxiliary functions: Supporting C code
Lex generates a C file with a function yylex() that reads input and returns tokens.
1-Mark Questions
What is a token? Give examples.
A token is a pair <token-name, attribute-value> representing a logical unit. Examples: <ID, pointer>, <NUM, 42>, <IF, –>, <PLUS, +>.
Differentiate lexeme and pattern.
A pattern is the rule that describes a set of lexemes (e.g., "identifier" pattern). A lexeme is a specific character sequence that matches a pattern (e.g., "count" is a lexeme matching the identifier pattern).
What is input buffering in a lexical analyzer? [2023]
Input buffering reads source characters in large blocks (buffers) to reduce I/O operations. Uses two halves with a sentinel character at the end of each half to avoid boundary checks.
What is a sentinel in input buffering?
A sentinel is a special character (e.g., NUL or EOF) placed at the end of each buffer half. It signals the end of the buffer without requiring an explicit boundary check on every character read.
What is lexeme specification?
Lexeme specification defines the rules for what constitutes a valid token. It is described using regular expressions. Lexeme recognition is the process of matching the input against these specifications.
Give one example of a regular expression for an identifier.
letter (letter | digit)* — starts with a letter, followed by any number of letters or digits.
What is the Lex tool? [2022]
Lex (or Flex) is a lexical analyzer generator that takes regular expression specifications and produces C code for a lexical analyzer function (yylex).
What is a DFA?
A DFA (Deterministic Finite Automaton) is a finite state machine where for every state and input symbol, there is exactly one next state. No ε-transitions allowed.
5-Mark Questions
Explain the role of a lexical analyzer. What are token, pattern, and lexeme? [2023]
Role of Lexical Analyzer: The lexical analyzer (scanner) is the first phase of compilation. It reads source characters, groups them into tokens, removes whitespace and comments, and reports lexical errors (invalid characters). It acts as an interface between the source program and the parser.
Token: A pair <token-name, attribute-value> representing a class of lexemes. Token names are used by the parser.
Pattern: A rule describing the form of lexemes for a token, described by a regular expression. E.g., pattern for keyword "if".
Lexeme: A specific character sequence (instance) matching a token's pattern. E.g., "while" is a lexeme for the WHILE token.
Example: For the statement pos = init + rate * 60:
- <ID, "pos"> — lexeme "pos" matches pattern for identifier
- <ASSIGN, "=">
- <ID, "init">
- <PLUS, "+">
- <ID, "rate">
- <MUL, "*">
- <NUM, 60>
Explain input buffering technique with sentinels. [2021]
Input buffering reads source code in large blocks to reduce I/O overhead. The buffer is divided into two N-sized halves. Two pointers are maintained:
- lexemeBegin: Marks the start of the current lexeme.
- forward: Scans ahead to find the end of the lexeme.
A sentinel (special character like NUL, ASCII 0) is placed at the end of each half. When forward reaches the sentinel, a new block is loaded instead of checking boundary on every character.
┌─────── Buffer Half 1 ───────┐ ┌─────── Buffer Half 2 ───────┐
│ c h a r 1 ... N/2-1 │ │ c h a r N/2 ... N-1 │
│ │ │ │
↑ │ ↑ │
lexemeBegin │ sentinel sentinel
↑ │ │
forward │
└────────────────────────────┘ └────────────────────────────┘
What are regular expressions? List the RE operators with precedence. [2022]
Regular Expressions (RE) are algebraic notations used to describe patterns of strings (languages). They are built from:
- Union (|): r | s means strings matching r or s. Lowest precedence.
- Concatenation: rs means r followed by s. Medium precedence.
- Kleene Closure (*): r* means zero or more occurrences of r. Highest precedence.
- Positive Closure (+): r+ means one or more occurrences of r.
- Optional (?): r? means zero or one occurrence of r.
Precedence: * > concatenation > |
Example: RE for unsigned integer: digit+ where digit = (0|1|2|3|4|5|6|7|8|9)
RE for real number: digit+ (. digit+)? which matches: 42, 3.14, 0.5
Differentiate NFA and DFA with a comparison table. [2021]
| Property | NFA | DFA |
|---|---|---|
| Definition | Can have 0, 1, or many next states per input symbol | Exactly one next state per input symbol |
| ε-transitions | Allowed (move without consuming input) | Not allowed |
| State count | Usually smaller | Can be up to 2^n states for n NFA states |
| Implementation | Harder (needs backtracking or subset tracking) | Easy (direct table lookup O(1)) |
| Construction from RE | Direct (Thompson's construction) | Indirect (subset construction from NFA) |
| Equivalence | Every NFA can be converted to an equivalent DFA | Every DFA is trivially an NFA |
15-Mark Questions
Construct a DFA from the regular expression (0+1)* 01. Show all steps including NFA construction and subset construction. Then minimise the DFA. [2023]
Step 1: Construct NFA from RE (Thompson's Construction)
RE: (0+1)* 01
NFA:
ε 0 ε ε 1 ε
──▶(0)───▶(1)───▶(2)───▶(3)───▶(4)───▶(5)───▶(6)───▶(7)───F
↑ │
└─────ε────────┘
(from state 7 back to 1 via * closure)
Where: (0)=start, (7)=final. (0+1)* is a loop from 1→2→3→1 via ε.
Then 01: 4→5 via '0', 5→6 via '1', 6→7 via ε (final).
Step 2: Subset Construction (NFA → DFA)
Compute ε-closure of start state = {0,1,3,7} = A. Then for each input symbol (0,1):
| State | 0 | 1 |
|---|---|---|
| A = {0,1,3,7} | B = ε-closure(move(A,0)) = {1,2,3,4,7} | C = ε-closure(move(A,1)) = {1,3,7} |
| B = {1,2,3,4,7} | A = ε-closure(move(B,0)) = {1,2,3,4,7} = B | D = ε-closure(move(B,1)) = {1,3,5,6,7} |
| C = {1,3,7} | B | C |
| D = {1,3,5,6,7} | B | C |
Final states: any state containing NFA state 7 = all states.
Minimized DFA: All states are final (accept any string ending with 01 from (0+1)*). After minimization, the DFA has 3 states: dead state, intermediate, accept.
Explain the process of DFA minimization with the table-filling algorithm. Minimize the DFA with states {A,B,C,D,E,F} where A is start, C and F are final, and transitions are given: δ(A,0)=B, δ(A,1)=C, δ(B,0)=D, δ(B,1)=E, δ(C,0)=F, δ(C,1)=D, δ(D,0)=A, δ(D,1)=E, δ(E,0)=F, δ(E,1)=A, δ(F,0)=F, δ(F,1)=F. [2022]
Table-Filling Algorithm:
Step 1: Mark distinguishable pairs — any pair with one final and one non-final state:
- (C,A), (C,B), (C,D), (C,E) — C is final, others not
- (F,A), (F,B), (F,D), (F,E) — F is final, others not
Step 2: For each unmarked pair (p,q), check if δ(p,a) and δ(q,a) are marked for any input 'a':
| Unmarked Pair | δ(p,0) | δ(q,0) | Marked? | δ(p,1) | δ(q,1) | Marked? |
|---|---|---|---|---|---|---|
| (A,B) | B | D | (B,D) unmarked | C | E | (C,E) marked ✓ |
| (A,D) | B | A | (A,B) being checked | C | E | marked ✓ |
| (A,E) | B | F | marked ✓ | C | A | unmarked |
| (B,D) | D | A | (D,A) same as (A,D) | E | E | same state → unmarked |
| (B,E) | D | F | marked ✓ | E | A | (E,A) unmarked |
| (D,E) | A | F | marked ✓ | E | A | same as (A,E) |
Equivalent pairs (unmarked):
- (A,B) — but A,B differ on input 1 (C vs E), so (A,B) is actually marked.
- Remaining unmarked: (B,D) — check: δ(B,1)=E, δ(D,1)=E (same). δ(B,0)=D, δ(D,0)=A — (D,A) marked. So (B,D) gets marked.
After full iteration: No equivalent pairs found. The DFA is already minimal.
Unit 2 Exam Tips
- RE construction from given language description is very frequently asked (5 marks).
- NFA → DFA conversion via subset construction: always asked as a 5 or 15-mark question. Be methodical.
- DFA minimization (table-filling): 15-mark question. Draw the table clearly.
- Token vs Pattern vs Lexeme: 1-mark definitions — know them cold.
- Input buffering with sentinels: 5-mark explanation.
Previous Year Questions — Unit 2
- [2023] What is a token, pattern, and lexeme? Explain.
- [2023] Explain input buffering in lexical analysis.
- [2023] Construct NFA for the RE (a+b)*abb.
- [2023] Convert NFA to DFA using subset construction.
- [2022] Explain regular expressions and their operators with precedence.
- [2022] Differentiate NFA and DFA.
- [2022] Minimize the given DFA using the table-filling algorithm.
- [2021] Explain the Lex tool and its components.
- [2021] Explain DFA minimization algorithm with an example.
- [2021] What are tokens? How are they recognized by the lexical analyzer?
Syntax Analysis (Parsing)
Context-Free Grammar · Top-Down vs Bottom-Up · Recursive Descent · LL(1) · Left Factoring · Left Recursion · Shift-Reduce · LR · SLR · LALR · Parsing Tables
3.1 Key Definitions
Syntax Analysis (Parsing): The second phase of compilation. Groups tokens into grammatical phrases (productions) and builds a parse tree.
Context-Free Grammar (CFG): G = (V, T, P, S) where V = variables/non-terminals, T = terminals, P = productions, S = start symbol.
Parse Tree: Tree representation of how a string is derived from the start symbol. Internal nodes = non-terminals, leaves = terminals.
Ambiguous Grammar: A grammar that can produce more than one parse tree for the same string. Ambiguity is bad for parsing.
Left Recursion: A production of the form A → A α (A derives itself first). Causes infinite loop in top-down parsers.
Left Factoring: When two productions for A share a common prefix, factor out the common part: A → α β₁ | α β₂ → A → α A' ; A' → β₁ | β₂.
3.2 Top-Down vs Bottom-Up Parsing
| Aspect | Top-Down Parsing | Bottom-Up Parsing |
|---|---|---|
| Direction | Start symbol → input string | Input string → start symbol |
| Construction | Builds parse tree from root to leaves | Builds parse tree from leaves to root |
| Derivation | Leftmost derivation (LMD) | Rightmost derivation (RMD) in reverse |
| Examples | Recursive Descent, LL(1) | Shift-Reduce, SLR, LR, LALR |
| Grammar | No left recursion, needs left factoring | Handles left recursion naturally |
| Power | Less powerful (LL(k) subset) | More powerful (LR(k) superset) |
| Error detection | Early (at the wrong choice) | Late (after shifting more input) |
| Implementation | Simple (recursive procedures) | Complex (stack + table driven) |
3.3 FIRST and FOLLOW Sets
FIRST(X): Set of terminals that can appear as the first symbol of any string derived from X.
FOLLOW(A): Set of terminals that can appear immediately after A in some sentential form. $ always in FOLLOW(S).
Rules:
- If X → ε, add ε to FIRST(X).
- If X → Y₁Y₂...Yₖ, add FIRST(Yᵢ) to FIRST(X) for each i until a non-nullable Y is found.
- If all Yᵢ are nullable, add ε to FIRST(X).
- FOLLOW(S) always contains $.
- If A → αBβ, add FIRST(β) − {ε} to FOLLOW(B).
- If A → αB or A → αBβ where β → ε, add FOLLOW(A) to FOLLOW(B).
3.4 SLR vs LR vs LALR
| Aspect | SLR | LR(1) | LALR |
|---|---|---|---|
| Full form | Simple LR | Canonical LR | Lookahead LR |
| Items | LR(0) items | LR(1) items (item + lookahead) | Merged LR(1) items |
| Lookahead set | FOLLOW of LHS | Per-item (exact) | Per-state (merged) |
| Table size | Smallest | Largest | Medium |
| Power | Least powerful | Most powerful | Between SLR and LR |
| States | Fewest | Most | Fewer than LR(1) |
| Conflict resolution | May have conflicts LR(1) resolves | Minimizes conflicts | May introduce conflicts |
| Practical use | Yacc/Bison default | Very large tables | Common in practice |
1-Mark Questions
What is a Context-Free Grammar (CFG)?
A CFG is a 4-tuple G = (V, T, P, S) where V is the set of variables/non-terminals, T is the set of terminals, P is the set of productions, and S ∈ V is the start symbol.
What is an ambiguous grammar? [2023]
A grammar is ambiguous if there exists at least one string that has two or more distinct parse trees (or leftmost derivations).
What is left recursion? Why is it a problem? [2022]
Left recursion occurs when a non-terminal A derives itself as the first symbol: A → A α. It causes infinite loops in top-down parsers because the parser keeps expanding A without consuming input.
What is left factoring?
Left factoring removes common prefixes from productions. E.g., A → αβ₁ | αβ₂ becomes A → αA' ; A' → β₁ | β₂. Used to make grammar suitable for predictive parsing.
Define FIRST and FOLLOW sets.
FIRST(X) = set of terminals that can begin strings derived from X. FOLLOW(A) = set of terminals that can immediately follow A in some derivation. $ ∈ FOLLOW(start symbol).
What is a shift-reduce parser?
A bottom-up parser that repeatedly shifts input symbols onto a stack and reduces handle substrings (matching RHS of a production) back to the LHS non-terminal.
What is an LR parser?
An LR parser is a shift-reduce parser that uses LR(k) items (productions with a dot and k lookahead tokens) to construct parsing tables and decides when to shift/reduce based on those tables.
5-Mark Questions
What is recursive descent parsing? Explain with an example. [2023]
Recursive Descent Parsing is a top-down parsing technique where each non-terminal in the grammar has a corresponding recursive procedure. The parser tries to match the input by calling these procedures recursively.
Grammar: E → T E' ; E' → + T E' | ε ; T → int
Procedures:
void E() { T(); E_prime(); }
void E_prime() {
if (lookahead == '+') { match('+'); T(); E_prime(); }
// else ε-production — return
}
void T() { match(INT); } // INT is a terminal token
For input "int + int": E() calls T() which matches "int", then E_prime() sees '+' so it matches '+' and calls T() again matching "int". Parsing succeeds.
Limitation: Fails on left-recursive grammars and requires left-factored grammar for predictive parsing.
Construct an LL(1) parsing table for the grammar: E → T E', E' → + T E' | ε, T → int. Show FIRST and FOLLOW sets. [2023]
Grammar:
E → T E'
E' → + T E' | ε
T → int
FIRST sets:
- FIRST(E) = {int}
- FIRST(E') = {+, ε}
- FIRST(T) = {int}
FOLLOW sets:
- FOLLOW(E) = {$}
- FOLLOW(E') = {+, $}
- FOLLOW(T) = {+, $}
LL(1) Parsing Table:
| Non-terminal | int | + | $ |
|---|---|---|---|
| E | E → T E' | ||
| E' | E' → ε | E' → + T E' | E' → ε |
| T | T → int |
No conflicts → Grammar is LL(1).
Eliminate left recursion and left factor the grammar: S → S a | b S b | b. [2022]
Step 1: Eliminate Left Recursion
Original: S → S a | b S b | b
S → b S b | b | S a
Group: S → α₁ | α₂ | β (where α = Sa, β = bSb | b)
S → β S' ; S' → α S' | ε
S → b S b | b | S' (where S' handles recursion)
S → b S b S' | b S'
S' → a S' | ε
Step 2: Left Factoring
The grammar S → b S b S' | b S' already has common prefix 'b'. Factor:
S → b S'' ; S'' → S b S' | S' ; S' → a S' | ε
This grammar is now suitable for LL(1) parsing.
Explain shift-reduce parsing with an example. [2021]
Shift-reduce parsing is a bottom-up technique that uses a stack and an input buffer. Two actions:
- Shift: Move the next input token onto the stack.
- Reduce: When the RHS of a production appears on top of the stack, replace it with the LHS non-terminal.
Example — Grammar: E → E + T | T ; T → int
Input: int + int $
| Stack | Input | Action |
|---|---|---|
| int + int $ | Shift | |
| int | + int $ | Reduce (T → int) |
| T | + int $ | Reduce (E → T) |
| E | + int $ | Shift |
| E + | int $ | Shift |
| E + int | $ | Reduce (T → int) |
| E + T | $ | Reduce (E → E + T) |
| E | $ | Accept |
Differentiate SLR, LR(1), and LALR parsers. [2023]
| Aspect | SLR | LR(1) | LALR |
|---|---|---|---|
| Items used | LR(0) items | LR(1) items (production + 1 lookahead) | Merged LR(1) items |
| Reduce action | Uses FOLLOW of LHS | Uses per-item lookahead (exact) | Uses merged per-state lookahead |
| Table size | Small | Very large (often impractical) | Moderate (practical) |
| Parsing power | Least (most conflicts) | Most (least conflicts) | Medium |
| Grammar class | SLR grammar | LR(1) grammar | LALR grammar |
| Error detection | May shift too much | Detects errors earliest | Between SLR and LR |
Construct an SLR parsing table for the grammar: E → E + T | T, T → T * F | F, F → ( E ) | id. Show the canonical collection of LR(0) items, FOLLOW sets, and the parsing table. Parse the input "id + id * id". [2023]
Grammar (augmented):
0: E' → .E
1: E → .E + T
2: E → .T
3: T → .T * F
4: T → .F
5: F → .( E )
6: F → .id
Canonical Collection of LR(0) Items (states):
I₀ = closure({E' → .E}) = {E'→.E, E→.E+T, E→.T, T→.T*F, T→.F, F→.(E), F→.id}
GOTO(I₀, E) = I₁ = {E'→E., E→E.+T}
GOTO(I₀, T) = I₂ = {E→T., T→T.*F}
GOTO(I₀, F) = I₃ = {T→F.}
GOTO(I₀, id) = I₄ = {F→id.}
GOTO(I₀, () = I₅ = {F→(.E), E→.E+T, E→.T, T→.T*F, T→.F, F→.(E), F→.id}
GOTO(I₁, +) = I₆ = {E→E+.T, T→.T*F, T→.F, F→.(E), F→.id}
GOTO(I₅, E) = I₇ = {E→E., E→E.+T}
GOTO(I₅, T) = I₈ = {E→T., T→T.*F}
GOTO(I₅, F) = I₉ = {T→F.}
GOTO(I₅, id) = I₁₀ = {F→id.}
GOTO(I₅, () = I₅
GOTO(I₆, T) = I₂
GOTO(I₆, F) = I₃
GOTO(I₆, id) = I₄
GOTO(I₆, () = I₅
GOTO(I₇, +) = I₆
GOTO(I₇, )) = I₁₁ = {F→).E}, E→E.+T, E→.T, T→.T*F, T→.F, F→.(E), F→.id}
GOTO(I₈, *) = I₁₂ = {T→T*.F, F→.(E), F→.id}
GOTO(I₈, F) = I₃
GOTO(I₈, id) = I₄
GOTO(I₈, () = I₅
GOTO(I₉, +) = I₆
GOTO(I₉, *) = I₁₂
GOTO(I₉, )) = I₁₁
GOTO(I₁₂, F) = I₃
GOTO(I₁₂, id) = I₄
GOTO(I₁₂, () = I₅
FOLLOW sets:
- FOLLOW(E') = {$}
- FOLLOW(E) = {+, ), $}
- FOLLOW(T) = {+, *, ), $}
- FOLLOW(F) = {+, *, ), $}
SLR Parsing Table:
| State | Action | Goto | |||||||
|---|---|---|---|---|---|---|---|---|---|
| id | + | * | ( | ) | $ | E | T | F | |
| 0 | S4 | S5 | 1 | 2 | 3 | ||||
| 1 | S6 | acc | |||||||
| 2 | r2 | S12 | r2 | r2 | |||||
| 3 | r4 | r4 | r4 | r4 | |||||
| 4 | r6 | r6 | r6 | r6 | |||||
| 5 | S4 | S5 | 7 | 8 | 9 | ||||
| 6 | S4 | S5 | 2 | 3 | |||||
| 7 | S6 | S11 | |||||||
| 8 | r2 | S12 | r2 | r2 | |||||
| 9 | r4 | r4 | r4 | r4 | |||||
| 10 | r6 | r6 | r6 | r6 | |||||
| 11 | r5 | r5 | r5 | r5 | |||||
| 12 | S4 | S5 | 3 |
Parsing input "id + id * id $":
| Stack | Input | Action |
|---|---|---|
| 0 | id + id * id $ | Shift → S4 |
| 0 4 | + id * id $ | Reduce F→id (r6) |
| 0 3 | + id * id $ | Reduce T→F (r4) |
| 0 2 | + id * id $ | Reduce E→T (r2) |
| 0 1 | + id * id $ | Shift + → S6 |
| 0 1 6 | id * id $ | Shift id → S4 |
| 0 1 6 4 | * id $ | Reduce F→id (r6) |
| 0 1 6 3 | * id $ | Reduce T→F (r4) |
| 0 1 6 2 | * id $ | Shift * → S12 |
| 0 1 6 2 12 | id $ | Shift id → S4 |
| 0 1 6 2 12 4 | $ | Reduce F→id (r6) |
| 0 1 6 2 12 3 | $ | Reduce T→T*F (r2) |
| 0 1 6 2 | $ | Reduce E→E+T (r1) |
| 0 1 | $ | Accept |
Unit 3 Exam Tips
- LL(1) parsing table construction: guaranteed 5 or 15 marks. Memorize the algorithm: compute FIRST/FOLLOW, fill table, check for conflicts.
- Left recursion elimination and left factoring: 5-mark question every year.
- SLR/LR/LALR comparison table: frequently asked as 5 marks.
- Full SLR parsing table construction + parsing trace: 15 marks. Know the canonical collection algorithm.
- Top-down vs Bottom-up comparison: 5 marks. Know all 6-7 points.
Previous Year Questions — Unit 3
- [2023] What is an ambiguous grammar? Give an example.
- [2023] Explain recursive descent parsing with an example.
- [2023] Construct LL(1) parsing table for a given grammar.
- [2023] Construct SLR parsing table and parse a given input string.
- [2022] Eliminate left recursion from: A → Aα | β.
- [2022] Left factor the grammar and construct LL(1) table.
- [2022] Explain shift-reduce parsing with a trace.
- [2022] Compare top-down and bottom-up parsers.
- [2021] Explain LR parsing. What is an LR item?
- [2021] Differentiate SLR, LR(1), and LALR parsers.
- [2021] Compute FIRST and FOLLOW sets for a given grammar.
Syntax Directed Translation & Intermediate Code
SDT Schemes · L-Attributed Definitions · Three-Address Code · Quadruples · Triples · Type Checking · Run-Time Environment · Activation Records · Parameter Passing
4.1 Key Definitions
Syntax Directed Translation (SDT): A scheme for translating a grammar by attaching semantic rules (actions) to productions. Can be implemented as Syntax Directed Definitions (SDD) or Translation Schemes (TS).
Syntax Directed Definition (SDD): A CFG with semantic rules attached to productions. Each rule computes an attribute.
Synthesized Attribute: An attribute whose value is computed from the children of a node in the parse tree (bottom-up flow).
Inherited Attribute: An attribute whose value is passed from the parent or siblings (top-down flow).
L-Attributed Definition: An SDD where inherited attributes are passed only from left to right in the parse tree (suitable for top-down translation).
Three-Address Code (TAC): An intermediate representation where each instruction has at most three operands. Forms: x = y op z.
4.2 Intermediate Code Representations
| Form | Structure | Advantage | Disadvantage |
|---|---|---|---|
| Quadruple | (op, arg1, arg2, result) — 4 fields | Easy to modify, easy to generate | Takes more space |
| Triple | (op, arg1, arg2) — result is position # | Compact, no need for temporary names | Hard to reorder (no explicit result name) |
| Indirect Triple | List of pointers to triples | Easy reordering of code | Indirection overhead |
4.3 Run-Time Environment
Activation Record (AR) / Stack Frame: Data structure containing all information needed for a single procedure invocation.
┌──────────────────────────┐ ← High addresses
│ Actual Parameters │
├──────────────────────────┤
│ Return Address │
├──────────────────────────┤
│ Saved Registers (FP) │
├──────────────────────────┤
│ Local Variables │
├──────────────────────────┤
│ Temporaries │
├──────────────────────────┤
│ Control Link (old FP) │
├──────────────────────────┤
│ Access Link (if needed)│
├──────────────────────────┤
│ Return Value │
├──────────────────────────┤
│ Machine Status │
└──────────────────────────┘ ← Low addresses (Stack grows ↓)
↑
SP (Stack Pointer)
4.4 Parameter Passing Methods
| Method | Mechanism | Effect on Actual | Efficiency |
|---|---|---|---|
| Call by Value | Copy value of actual to formal parameter | Actual unchanged; formal can be modified locally | High (simple copy) |
| Call by Reference | Pass address of actual; formal is alias | Actual is modified if formal is | Medium (no copy, indirect access) |
| Call by Value-Return | Copy in, copy out at return | Actual updated on return | Low (two copies) |
| Call by Name | Textual substitution (thunk mechanism) | Directly modified (like macro expansion) | Very low (thunk overhead) |
1-Mark Questions
What is Syntax Directed Translation (SDT)?
SDT is a method of translating a grammar by attaching semantic actions/rules to its productions. It combines parsing with translation.
What is a synthesized attribute?
A synthesized attribute is an attribute whose value is computed from the children (descendants) of a node in the parse tree. It flows bottom-up.
What is an inherited attribute?
An inherited attribute is an attribute whose value is passed from the parent or siblings of a node in the parse tree. It flows top-down.
What is an activation record?
An activation record (stack frame) is a contiguous block of storage containing all information needed for one procedure invocation: parameters, return address, local variables, temporaries, control links.
What is a quadruple?
A quadruple is a four-field intermediate code representation: (operator, operand1, operand2, result). E.g., (+, a, b, t1) for t1 = a + b.
What is a triple in intermediate code?
A triple is a three-field intermediate code: (operator, operand1, operand2). The result is referenced by its position number. E.g., (+, a, b) at position (0).
What is an indirect triple?
An indirect triple is a list of pointers to triples. It allows easy reordering of instructions without changing the triple list.
5-Mark Questions
Explain SDD and SDT. Differentiate between synthesized and inherited attributes with examples. [2023]
SDD (Syntax Directed Definition): A CFG with semantic rules. Each production has associated semantic rules that compute attribute values.
SDT (Syntax Directed Translation scheme): A CFG with semantic actions embedded within productions (between symbols). Actions can appear anywhere in the production body.
Synthesized Attribute: Computed from children. Example: E → E₁ + T { E.val = E₁.val + T.val } — E.val is synthesized from E₁ and T.
Inherited Attribute: Received from parent/siblings. Example: T → { T.type = E.type } int — T.type is inherited from parent E.
L-attributed definitions allow inherited attributes to be passed from left to right only, making them suitable for both top-down and bottom-up evaluation.
Generate Three-Address Code for: a = b + c * d - e / f. Show quadruples and triples. [2023]
Three-Address Code (TAC):
t1 = c * d
t2 = e / f
t3 = b + t1
t4 = t3 - t2
a = t4
Quadruples:
| # | Op | Arg1 | Arg2 | Result |
|---|---|---|---|---|
| 1 | * | c | d | t1 |
| 2 | / | e | f | t2 |
| 3 | + | b | t1 | t3 |
| 4 | - | t3 | t2 | t4 |
| 5 | = | t4 | — | a |
Triples:
| # | Op | Arg1 | Arg2 |
|---|---|---|---|
| (0) | * | c | d |
| (1) | / | e | f |
| (2) | + | b | (0) |
| (3) | - | (2) | (1) |
| (4) | = | (3) | a |
Explain parameter passing methods: call by value, call by reference, call by value-result, and call by name. [2022]
Call by Value: The value of the actual parameter is copied into the formal parameter. Changes to formal do NOT affect actual. E.g., C language default.
void swap(int a, int b) { int t=a; a=b; b=t; }
// Calling: x=10, y=20; swap(x,y);
// After call: x=10, y=20 (unchanged!)
Call by Reference: The address of the actual parameter is passed. The formal parameter becomes an alias (another name) for the actual. Changes to formal DO affect actual. E.g., C++ with &.
void swap(int &a, int &b) { int t=a; a=b; b=t; }
// After call: x=20, y=10 (SWAPPED!)
Call by Value-Return: Copy values in at call time, copy back at return. May cause issues with overlapping actuals.
Call by Name: The formal parameter is textually replaced by the actual parameter everywhere in the procedure body (implemented via thunks). Very expensive.
What is type checking? Explain type conversion and type equivalence. [2021]
Type Checking verifies that the operands of an operator are of compatible types. Performed during semantic analysis.
Type Conversion: Converting a value from one type to another.
- Implicit (coercion): Automatically done by compiler. E.g., int + float → int converted to float.
- Explicit (casting): Done by programmer. E.g., (int)3.14 → 3.
Type Equivalence:
- Name equivalence: Two types are equivalent if they have the same name. Strict.
- Structural equivalence: Two types are equivalent if they have the same structure (same type components).
Explain the run-time environment in detail. Draw and describe the activation record layout. Explain parameter passing methods with examples. [2023]
Run-Time Environment: The environment created during program execution that manages storage, procedure calls, and variable access.
Activation Record (Stack Frame) Layout:
High Addresses
┌──────────────────────────────┐
│ Actual Parameters │ ← passed by caller
├──────────────────────────────┤
│ Return Address (RA) │ ← where to return after call
├──────────────────────────────┤
│ Saved Registers / Old FP │ ← previous frame pointer
├──────────────────────────────┤
│ Local Variables & Data │ ← compiler-allocated
├──────────────────────────────┤
│ Temporaries │ ← compiler-generated temps
├──────────────────────────────┤
│ Control Link (old FP) │ ← points to caller's AR
├──────────────────────────────┤
│ Access Link (for nesting) │ ← for non-local access
├──────────────────────────────┤
│ Return Value │ ← result of function
├──────────────────────────────┤
│ Machine Status (PC, etc.) │ ← saved before call
└──────────────────────────────┘
Low Addresses (Stack grows downward ↓)
Storage Organization:
┌─────────────────────────┐ ← High addresses
│ Static Data Area │ ← Global variables, constants
├─────────────────────────┤
│ Code Area │ ← Executable instructions
├─────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ Activation │ │ ← Dynamic (stack-allocated)
│ │ Records (AR) │ │
│ │ (Stack Frames) │ │
│ │ │ │
│ └─────────────────┘ │
│ │
├─────────────────────────┤
│ Heap Area │ ← Dynamically allocated (malloc)
└─────────────────────────┘ ← Low addresses
Parameter Passing Methods — Detailed:
- Call by Value: Actual's value copied to formal. Formal is a local variable. Changes don't propagate back. Simple, safe, efficient.
- Call by Reference: Address of actual passed. Formal is an alias. Changes propagate. Efficient for large structures.
- Call by Value-Result (Copy-Restore): Copy in at call, copy out at return. Good for pass-in and pass-out semantics. Problem: aliased parameters interact unexpectedly.
- Call by Name: Textual substitution using thunks. Actual is re-evaluated each time formal is used. Most flexible but slowest.
Unit 4 Exam Tips
- SDD vs SDT, synthesized vs inherited attributes: 5-mark question. Always asked.
- Three-Address Code generation: 5 marks. Know the quadruple, triple, and indirect triple forms.
- Activation record layout diagram: 5 or 15 marks. Draw it from memory.
- Parameter passing methods comparison: always asked (5 marks). Write a small code example for each.
- Run-time environment (storage organization + AR): 15 marks. Include both diagrams.
Previous Year Questions — Unit 4
- [2023] Explain SDD and SDT with an example.
- [2023] Generate Three-Address Code and quadruples for an expression.
- [2023] Explain parameter passing methods.
- [2022] Differentiate synthesized and inherited attributes.
- [2022] Explain the run-time environment and activation records.
- [2022] Generate triples and indirect triples for a given expression.
- [2021] What is type checking? Explain type conversion.
- [2021] Explain L-attributed definitions and their use in top-down translation.
- [2021] Explain the storage organization for run-time environment.
Code Optimization & Code Generation
Optimization Criteria · Local Optimization · DAG · Global Optimization · Data Flow Analysis · Basic Blocks · Flow Graphs · Register Allocation · Peephole Optimization
5.1 Key Definitions
Code Optimization: The phase that transforms the intermediate code to improve execution speed and/or reduce code size, without changing the output.
Local Optimization: Optimizations applied within a single basic block (no branching).
Global Optimization: Optimizations applied across basic blocks (loop optimization, data flow analysis).
DAG (Directed Acyclic Graph): A graphical representation of a basic block where common subexpressions are identified and eliminated. Nodes represent operators/operands; edges show data flow.
Peephole Optimization: A local optimization technique that examines a small window ("peephole") of target instructions and replaces them with better sequences.
Data Flow Analysis: A framework to compute information about how data values propagate through a program (reaching definitions, live variables, available expressions).
5.2 Basic Blocks & Flow Graphs
Basic Block: A sequence of consecutive statements with:
- Only one entry (first statement is the leader)
- Only one exit (control flows to next block after the last statement)
Leaders (entry points of basic blocks):
- First statement of the program.
- Any statement that is the target of a conditional/unconditional goto.
- Any statement immediately following a conditional/unconditional goto.
Flow Graph: A directed graph where nodes are basic blocks and edges represent possible control flow between blocks.
5.3 Data Flow Analysis
Reaching Definitions: A definition d reaches a point p if there is a path from d's definition to p where d is not redefined.
Live Variable Analysis: A variable is live at a point if its value is used along some path from that point to the exit.
Available Expressions: An expression x + y is available at point p if on every path from the entry to p, x + y has been computed and neither x nor y has been redefined since.
5.4 Register Allocation
Register Allocation assigns program variables to a limited set of CPU registers.
Graph Coloring Approach:
- Construct an interference graph: nodes = variables, edge between two variables if they are simultaneously live.
- Color the graph with k colors (k = number of available registers).
- Variables with the same color can share a register.
If the graph is not k-colorable, spill some variables to memory.
5.5 Peephole Optimization
Peephole optimization examines a small sliding window of target code (typically 2-3 instructions) and replaces it with a shorter/faster sequence:
- Constant folding: 3 + 4 → 7
- Algebraic simplification: x * 1 → x, x + 0 → x
- Dead code elimination: Remove statements whose results are never used
- Sequence simplification: Eliminate redundant loads/stores
- Strength reduction: Replace expensive ops: x*2 → x+x
1-Mark Questions
What is code optimization? [2023]
Code optimization is the phase that transforms intermediate or target code to improve execution speed and/or reduce code size, without changing the program's output.
What is a basic block?
A basic block is a sequence of consecutive statements with exactly one entry point (first statement) and one exit point (last statement). Control enters at the first statement and leaves at the end.
What is a leader in basic block identification?
A leader is the first statement of a basic block. Statements that are leaders: (1) first statement, (2) target of a goto/branch, (3) statement immediately following a goto/branch.
What is a DAG in compiler optimization?
A DAG (Directed Acyclic Graph) represents a basic block where nodes are operators/operands and edges show data flow. It helps identify and eliminate common subexpressions.
What is peephole optimization? [2022]
Peephole optimization examines a small "window" (peephole) of adjacent instructions and replaces them with a shorter or faster equivalent sequence.
What is reaching definitions?
A definition d reaches a point p if there exists a path from the definition of d to p where d is not redefined along the path.
What is a live variable?
A variable is live at a point if its current value may be used along some path from that point to the program's exit (before being redefined).
5-Mark Questions
Construct the DAG for the basic block: a = b + c; b = a - d; c = b + c; d = a - d. [2023]
Step 1: Identify leaders and basic block (all statements form one block here).
Step 2: Build DAG:
(+) ←── b ──┐
│ │
c ─┘ │
│
┌──────────┘ → a (stored)
│
│
(-) ←── a ──┐
│ │
d ─┘ │
│
┌───┘ → b (stored)
│
│
(+) ←── b (from above)
│
c (original, not the redefined b)
│
┌──┘ → c (stored)
│
│
(-) ←── a (from first computation)
│
d
│
┌────┘ → d (stored)
Shared nodes:
• (+) for "b+c" used in a = b+c and c = b+c
• (-) for "a-d" used in b = a-d and d = a-d
Optimized code: t1 = b + c; a = t1; b = t1 - d; c = b + c_original; d = t1 - d
Construct basic blocks and draw the flow graph for the given code. [2022]
Code:
1: a = b + c
2: b = a - d
3: if b < c goto 8
4: d = a * e
5: e = d + 1
6: goto 9
7: x = d - e
8: y = a * c
9: z = x + y
Step 1: Find Leaders
- Statement 1 (first statement) → Leader
- Statement 3 (target of goto? No, but let's check: statement 8 is target of goto from 6) → Leader
- Statement 4 (after goto/branch at 3) → Leader
- Statement 7 (target of goto from 3) → Leader
- Statement 8 (target of goto from 6) → Leader
- Statement 9 (after goto from 6) → Leader
Step 2: Basic Blocks
- B1: {1: a=b+c; 2: b=a-d; 3: if b<c goto 8}
- B2: {4: d=a*e; 5: e=d+1; 6: goto 9}
- B3: {7: x=d-e}
- B4: {8: y=a*c}
- B5: {9: z=x+y}
Step 3: Flow Graph
┌───┐
│ B1 │
└─┬─┘
│ (b≥c)
▼
┌───┐
│ B2 │
└─┬─┘
│ (goto)
▼
┌───┐
│ B5 │
└───┘
│
(b<c) │
▼
┌───┐
│ B3 │──┐
└───┘ │
│
▼
┌───┐
│ B4 │
└─┬─┘
│
▼
┌───┐
│ B5 │
└───┘
Explain reaching definitions and live variable analysis. [2021]
Reaching Definitions: A definition d reaches point p if there exists a path from the point of d's definition to p where d is NOT redefined. Used for:
- Constant propagation
- Common subexpression elimination
- Detecting uninitialized variables
Data Flow Equations (Reaching Definitions):
- IN[n] = UNION of OUT[p] for all predecessors p of n
- OUT[n] = GEN[n] ∪ (IN[n] − KILL[n])
- GEN[n] = definitions generated in block n
- KILL[n] = definitions killed (overwritten) in block n
Live Variable Analysis: A variable v is live at point p if its value at p may be read along some path from p to the exit. Used for:
- Register allocation (only allocate registers to live variables)
- Dead code elimination
- Copy propagation
Data Flow Equations (Live Variables):
- OUT[n] = UNION of IN[s] for all successors s of n
- IN[n] = USE[n] ∪ (OUT[n] − DEF[n])
- USE[n] = variables used before defined in n
- DEF[n] = variables defined in n
Explain code generation from DAG. Given the DAG for: a = b + c; d = a + e; f = b + c; g = d + f; generate optimized code. [2023]
Step 1: Build DAG
Node 1: b (identifier)
Node 2: c (identifier)
│
Node 3: + (b + c) ← shared node!
│
┌────┴────┐
▼ ▼
Node 4: a Node 6: f
(store) (store)
Node 5: e (identifier)
│
Node 7: + (a + e)
│
Node 8: d (store)
│
Node 9: + (d + f)
│
Node 10: g (store)
Step 2: Identify common subexpression
Node 3 (b + c) is computed once but used for both 'a' and 'f'. This is a common subexpression — optimization already built into DAG.
Step 3: Generate optimized code from DAG
Topological order: Nodes 1, 2, 5 first, then Node 3, then 4/6, then 7, then 8/9, then 10.
Generated code:
t1 = b + c // Node 3 — computed once
a = t1 // Node 4
f = t1 // Node 6 — reused, NOT recomputed
t2 = a + e // Node 7
d = t2 // Node 8
t3 = d + f // Node 9
g = t3 // Node 10
Optimization achieved: The expression (b + c) is computed once and reused for both 'a' and 'f'. Without DAG optimization, it would be computed twice.
Explain peephole optimization in detail. List and explain at least six peephole optimization rules. Apply them to optimize a given code sequence. [2022]
Peephole Optimization examines a small sliding window of consecutive instructions and replaces them with a shorter, faster, or simpler equivalent. The "peephole" is a short sequence of target instructions being examined.
Six Peephole Optimization Rules:
- Constant Folding: Replace operations on constants with their result.
Example:MOV R0, #5→ADD R0, #3becomesMOV R0, #8 - Algebraic Simplification: Replace expensive operations with simpler ones.
x * 1 → x,x + 0 → x,x * 2 → x + x,x / 1 → x - Dead Code Elimination: Remove instructions whose results are never used.
MOV R0, #0followed by code that never reads R0 → remove. - Redundant Load/Store Elimination:
MOV R0, M[i]→MOV R1, M[i]→ Remove second load if R0 still holds the value. - Sequence Simplification:
GOTO L1followed byL1: ...→ Remove the GOTO. - Strength Reduction:
x = y * 2→x = y + y(addition is cheaper than multiplication).
Example Application:
Original Code: Optimized Code:
──────────────────────────────────────────────────
MOV R1, #5 MOV R1, #5
MOV R2, #3 ADD R1, R1, #3 ← const folding
ADD R1, R1, R2 MOV R0, M[100] ← remove redundant
MOV R0, M[100] load
MOV R1, #0 MOV R0, M[R1] ← dead code elim
MOV R0, M[R1] (rest of code)
Unit 5 Exam Tips
- DAG construction for basic block optimization: 5 marks. Know the algorithm.
- Code generation from DAG: 15 marks. Show topological ordering of DAG nodes.
- Basic blocks and flow graphs: 5 marks. Always asked.
- Peephole optimization rules: memorize at least 6 rules. 5 or 15 marks.
- Register allocation via graph coloring: 5 marks.
- Data flow analysis (reaching definitions, live variables): 5 marks, sometimes 15.
Previous Year Questions — Unit 5
- [2023] Explain code optimization and its criteria.
- [2023] Construct a DAG for a given basic block.
- [2023] Generate optimized code from a given DAG.
- [2022] Explain peephole optimization with rules and examples.
- [2022] Construct basic blocks and draw the flow graph.
- [2022] Explain register allocation using graph coloring.
- [2021] Explain data flow analysis and reaching definitions.
- [2021] Differentiate local and global optimization.
- [2021] Explain live variable analysis with a data flow equation.
5-Unit Summary
- Compiler vs Interpreter (comparison table)
- 5 phases (analysis + synthesis)
- Single-pass vs Multi-pass
- Cross-compiler, bootstrapper
- Token, Pattern, Lexeme
- Regular Expressions (operators & precedence)
- NFA → DFA (subset construction)
- DFA Minimisation (table-filling)
- Input buffering with sentinels
- Lex tool
- CFG, Parse Tree, Ambiguity
- Top-Down vs Bottom-Up
- Left Recursion Elimination
- Left Factoring
- FIRST/FOLLOW sets
- LL(1) parsing table
- Shift-Reduce, SLR, LR, LALR
- SDD vs SDT
- Synthesized vs Inherited attributes
- L-attributed definitions
- Three-Address Code (TAC)
- Quadruples, Triples, Indirect Triples
- Activation Record layout
- Parameter passing (4 methods)
- Type checking & conversion
- Local vs Global optimization
- DAG for basic blocks
- Basic blocks & flow graphs
- Reaching definitions
- Live variable analysis
- Register allocation (graph coloring)
- Peephole optimization (6+ rules)
Previous Year Questions (MAKAUT)
Complete list of questions from 2023, 2022, 2021
All Previous Year Questions
- [2023] What is a compiler? Differentiate between compiler and interpreter.
- [2023] Explain the various phases of a compiler with a diagram.
- [2023] What is a token, pattern, and lexeme? Explain.
- [2023] Explain input buffering in lexical analysis.
- [2023] Construct NFA for the RE (0+1)* 01.
- [2023] Convert NFA to DFA using subset construction.
- [2023] What is an ambiguous grammar? Give an example.
- [2023] Explain recursive descent parsing with an example.
- [2023] Construct LL(1) parsing table for a given grammar.
- [2023] Construct SLR parsing table and parse a given input string.
- [2023] Explain SDD and SDT with an example.
- [2023] Generate Three-Address Code and quadruples for an expression.
- [2023] Explain parameter passing methods.
- [2023] Differentiate SLR, LR(1), and LALR parsers.
- [2023] Explain code optimization and its criteria.
- [2023] Construct a DAG for a given basic block.
- [2023] Generate optimized code from a given DAG.
- [2022] What is a bootstrapper? Explain single-pass and multi-pass compilers.
- [2022] Define cross-compiler, source program, and target program.
- [2022] Explain regular expressions and their operators with precedence.
- [2022] Differentiate NFA and DFA.
- [2022] Minimize the given DFA using the table-filling algorithm.
- [2022] Eliminate left recursion from: A → Aα | β.
- [2022] Left factor the grammar and construct LL(1) table.
- [2022] Explain shift-reduce parsing with a trace.
- [2022] Compare top-down and bottom-up parsers.
- [2022] Differentiate synthesized and inherited attributes.
- [2022] Explain the run-time environment and activation records.
- [2022] Generate triples and indirect triples for a given expression.
- [2022] Explain parameter passing methods.
- [2022] Construct basic blocks and draw the flow graph.
- [2022] Explain peephole optimization with rules and examples.
- [2022] Explain register allocation using graph coloring.
- [2021] Explain the structure of a compiler in detail.
- [2021] What is a preprocessor? What are its functions?
- [2021] Explain the Lex tool and its components.
- [2021] Explain DFA minimization algorithm with an example.
- [2021] What are tokens? How are they recognized by the lexical analyzer?
- [2021] Explain LR parsing. What is an LR item?
- [2021] Differentiate SLR, LR(1), and LALR parsers.
- [2021] Compute FIRST and FOLLOW sets for a given grammar.
- [2021] What is type checking? Explain type conversion.
- [2021] Explain L-attributed definitions and their use in top-down translation.
- [2021] Explain the storage organization for run-time environment.
- [2021] Explain data flow analysis and reaching definitions.
- [2021] Differentiate local and global optimization.
- [2021] Explain live variable analysis with a data flow equation.