CD Notes
Detailed guide to common subexpression elimination covering local and global CSE, available expressions analysis, DAG-based detection, and practical optimization examples.
What is CSE?
Common Subexpression Elimination identifies expressions that are computed more than once with the same operand values, and replaces redundant computations with a reference to the previously computed result. It is one of the most effective compiler optimizations, often yielding 10-30% improvement.
Identifying Common Subexpressions
An expression E is a common subexpression at point P if
1. E was previously computed at point Q
2. None of the operands of E have been modified between Q and P
Example
t1 = a + b ← first computation of (a + b)
t2 = a - c
t3 = a + b ← COMMON! Same as t1, a and b unchanged
t4 = t1 * t3 can be optimized to: t4 = t1 * t1
Before CSE: After CSE:
t1 = a + b t1 = a + b
t2 = a - c t2 = a - c
t3 = a + b t3 = t1 ← reuse t1
t4 = t1 * t3 t4 = t1 * t1
Local CSE Using DAGs
Within a basic block, DAGs naturally detect common subexpressions:
Code
a = b + c
d = b + c
e = a + d
DAG construction
Step 1: a = b + c
[+] → a
/ \
[b] [c]
Step 2: d = b + c (same + node exists with same children!)
[+] → a, d ← d points to SAME node
/ \
[b] [c]
Step 3: e = a + d (both operands point to same node!)
[+] → e ← this is actually a+a = 2*a
|
[+] → a, d
/ \
[b] [c]
Optimized code from DAG
a = b + c
d = a (copy instead of recomputation)
e = a + a (or e = a + d, same thing)
Algorithm for Local CSE
| Algorithm | Value Numbering (local CSE) |
| 3. If YES | x gets same value number, replace with copy |
| 4. If NO | create new entry, assign new value number to x |
| a = b + c VN: b=1, c=2, (+=,1,2) | 3, a=3 |
| d = b + c VN lookup: (+,1,2) exists | value 3! d=3, emit: d=a |
| e = d + b VN: (+,3,1) | new value 4, e=4 |
| f = a + b VN lookup: (+,3,1) exists | value 4! f=4, emit: f=e |
Global CSE: Available Expressions
For CSE across basic blocks, we need Available Expressions analysis:
An expression e is AVAILABLE at point P if
- On EVERY path from program entry to P, e is computed
- AND none of its operands are redefined after that computation
Dataflow equations
AVAIL_IN[B] = ∩ AVAIL_OUT[P] for all predecessors P
AVAIL_OUT[B] = (AVAIL_IN[B] - KILL[B]) ∪ GEN[B]
GEN[B] = expressions computed in B with operands not later redefined in B
KILL[B] = expressions whose operands are defined in B
Example
┌─────┐
│ B1 │
│a=b+c│ GEN={b+c}
└──┬──┘
┌───┴───┐
┌────┐ ┌────┐
│ B2 │ │ B3 │
│d=b+c│ │b=7 │ KILL={b+c, b+d, ...}
└──┬─┘ └──┬─┘
└──────┬──────┘
┌───┴──┐
│ B4 │
│e=b+c │ ← Is b+c available here?
└──────┘
Analysis
AVAIL_OUT[B1] = {b+c}
AVAIL_OUT[B2] = {b+c} (b+c generated, operands unchanged)
AVAIL_OUT[B3] = {} (b redefined, kills b+c)
AVAIL_IN[B4] = AVAIL_OUT[B2] ∩ AVAIL_OUT[B3] = {b+c} ∩ {} = {}
b+c is NOT available at B4 (because path through B3 redefines b)
Cannot eliminate e = b+c in B4.
Global CSE with Temporaries
| B1 | B1 | |
|---|---|---|
| ... | t=b+c | |
| x=b+c | x=t | |
| B2 | B2 | |
| ... | ... |
CSE in Loops
Algebraic Identities and CSE
CSE can be enhanced by recognizing algebraic equivalences:
Limitations of CSE
CSE Implementation with Hash Tables
Interview Questions
- Q: What is the difference between local and global CSE?
A: Local CSE works within a single basic block using value numbering (simple, no control flow). Global CSE works across blocks using available expressions dataflow analysis (must ensure expression is computed on ALL paths to the point). Global CSE requires inserting temporary variables to carry values across blocks.
- Q: How does pointer aliasing affect CSE?
A: If two pointers might alias (point to same memory), a store through one may invalidate expressions involving the other. Example: after *p = 5, the compiler cannot assume *q still has its old value. Conservative alias analysis limits CSE effectiveness — expressions involving pointed-to values are killed by any potentially aliasing store.
- Q: Can CSE increase register pressure? When is it not profitable?
A: Yes. CSE replaces recomputation with a saved value that must remain live (in a register or stack slot). If the live range is long and registers are scarce, the spill cost may exceed the recomputation cost. This is especially true for cheap operations (single ADD) with long distances between uses.
- Q: How does CSE interact with other optimizations?
A: CSE enables dead code elimination (redundant computation becomes dead), reduces instruction count (fewer operations to schedule), and interacts with loop invariant code motion (loop-invariant CSEs should be hoisted). Constant propagation can expose new CSE opportunities by making operands identical.
- Q: What is the time complexity of local CSE vs global CSE?
A: Local CSE using value numbering is O(n) where n is instructions in the block (hash table lookups). Global CSE requires dataflow analysis: O(n × e) where e is edges in CFG, iterated to fixed point. In practice, convergence is fast (2-3 iterations), making it O(n) for typical programs.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Common Subexpression Elimination (CSE) in Compiler Optimization.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Compiler Design topic.
Search Terms
compiler-design, compiler design, compiler, design, code, optimization, common, subexpression
Related Compiler Design Topics