Data flow analysis framework
The common structure of global data flow analyses: data flow values at program points, transfer functions per block, direction, the meet operator and boundary values, the iterative algorithm and why it terminates, monotone and distributive frameworks, meet over paths versus the iterative solution, and the table of the four standard analyses.
Why it exists
Local optimisation sees one basic block at a time. To know that a value computed in one block can be reused in another, that a variable assigned in one block is never read afterwards, or that a variable always holds the same constant when control reaches a block, the compiler must follow all the paths through the flow graph. Data flow analysis collects such facts for every point of the program. The remarkable thing is that the many different analyses (reaching definitions, live variables, available expressions, constant propagation) are all instances of one framework: a few set equations per block and one iterative algorithm that solves them.
Prerequisites
You need the flow graph of basic blocks (basic blocks and control flow graphs).
Precise definitions
Program points and data flow values. A program point lies before or after each instruction; the point at the start of block is IN[] and the one at its end is OUT[]. The analysis associates a data flow value with each point: for reaching definitions a set of definitions, for live variables a set of variables, and so on. An execution is a path through the graph; the value at a point summarises what is true at that point for all (or some) executions that arrive there.
Transfer functions. The effect of a block on the data flow value is a function with OUT[] = (IN[]) in a forward analysis (information flows along the control flow) or IN[] = (OUT[]) in a backward one. The classical analyses are gen-kill problems:
are the facts the block creates, are the facts it destroys.
Meet operator. Where paths join (several predecessors in a forward analysis, several successors in a backward one) the values are combined with a meet operator : union if "true on some path" is what is wanted (a may analysis), intersection if "true on all paths" (a must analysis).
Boundary and initial values. The value at the entry (forward) or at the exits (backward) is fixed by the problem (the empty set for the four standard analyses). All other points start at the identity (top) element of the meet: the empty set for union, the set of all facts for intersection (so that the first meet leaves the other operand unchanged).
The four standard analyses.
| Analysis | Domain | Direction | Meet | Transfer function |
|---|---|---|---|---|
| Reaching definitions | sets of definitions | forward | union | |
| Live variables | sets of variables | backward | union | |
| Available expressions | sets of expressions | forward | intersection | |
| Constant propagation | map variable to {undefined, constant, NAC} | forward | pointwise meet on the lattice | run the block's assignments |
A fifth classical one, very busy (anticipated) expressions, is backward with intersection; it is outside the syllabus.
Iterative algorithm (forward, gen-kill).
OUT[ENTRY] = boundary; for every other block B: OUT[B] = top
repeat
for every block B other than ENTRY (in reverse postorder):
IN[B] = meet over all predecessors P of OUT[P]
OUT[B] = f_B(IN[B])
until no OUT changesThe backward version swaps the roles: it starts from the exits, takes the meet of the INs of the successors to form OUT[], computes IN[] = (OUT[]), and visits the blocks in the reverse of the forward order. A pass that changes nothing ends the iteration, so the last pass only confirms the result.
Why it terminates and what it computes. The data flow values form a semilattice (a set with a meet operation that is idempotent, commutative and associative, with a top element). For finite sets of facts every chain of values is finite. If every transfer function is monotone ( implies ), each value can only move in one direction (down the lattice) in each iteration, so the algorithm must stop. The result is the maximum fixed point (MFP) of the equations. It is always a safe approximation. The ideal answer is the meet over all paths (MOP): apply the transfer functions along each path from the entry to the point and take the meet of the results. In general MFP MOP (the iterative solution may be less precise). When the framework is distributive, , the two are equal. All gen-kill problems are distributive, so their MFP is exactly the MOP; constant propagation is monotone but not distributive (the next page has the example).
Speed. The order in which blocks are visited does not change the result but changes the number of passes. A depth-first order (reverse postorder) lets information travel along an acyclic path in one pass; for typical reducible flow graphs the number of passes is bounded by the depth of loop nesting plus a small constant (the textbook states at most with the largest number of back edges on an acyclic path), so two or three passes are usual. This bound is a statement about the worst case of the order, not an exact count.
How it works
The animation runs the iterative algorithm for reaching definitions on a flow graph with a loop. The code is the textbook-style loop with six definitions of i, j and a; the flow graph has the blocks B1, B2, B3, B4, with B2 → B3, B2 → B4, B3 → B4 and the back edge B4 → B2. Every step recomputes IN and OUT of one block in reverse postorder, with the equation written out. Pass 1 gives the sets their first values; B2 is visited when OUT[B4] is still empty, so pass 2 revises IN[B2] after B4 has been computed; pass 3 confirms that nothing changes (so the algorithm needs three passes counting the final confirming one).
Reaching definitions: the iterative algorithm: start
Computed by a script, not typed- 11: i = m - 1
- 22: j = n
- 33: a = u1
- 44: i = i + 1
- 55: if a > i goto 7
- 66: j = j - 1
- 77: a = u2
- 88: if j > 0 goto 4
| block | gen | kill | IN | OUT |
|---|---|---|---|---|
| B1 | {d1, d2, d3} | {d4, d5, d6} | ||
| B2 | {d4} | {d1} | ||
| B3 | {d5} | {d2} | ||
| B4 | {d6} | {d3} |
initial values
Step 0
Reaching definitions is a forward problem with union as the meet: a definition reaches the start of a block if it reaches the end of some predecessor. All sets start empty. Each block has a gen set (the definitions made in it that survive to its end) and a kill set (the other definitions of the same variables). Table columns: gen, kill, IN, OUT.
Compare the other analyses on the same footing: live variables run the same loop backwards with use and def in place of gen and kill (liveness); available expressions use intersection and the universal set as the initial value (available expressions); constant propagation works on a different lattice (reaching definitions and constant propagation).
Worked examples
EX 1Direction and meet
EasyFor each analysis, direction and meet: reaching definitions: forward, union (a definition may reach along some path); live variables: backward, union (live if some path uses it); available expressions: forward, intersection (available only if it is computed on every path); very busy expressions: backward, intersection. A one-line test: "does information flow with the execution?" gives forward; "is it some path or every path?" gives union or intersection.
EX 2Write the equations
EasyReaching definitions: IN[] = OUT[], OUT[] = gen[] (IN[] kill[]). Live variables: OUT[] = IN[], IN[] = use[] (OUT[] def[]). Available expressions: IN[] = OUT[] (and IN[entry] ), OUT[] = e_gen[] (IN[] e_kill[]). The shape of the transfer function is the same in all three; only the direction and the meet differ.
EX 3Why does an intersection problem start with the full set?
MediumIn available expressions the OUT of every block except the entry starts as the set of all expressions . If it started empty, the intersection at a join in a loop would stay empty forever (the empty set is a fixed point of the equations) and nothing would ever be found available across a loop. Starting from (the identity of intersection), each pass can only remove expressions that are really not available, so the algorithm converges to the largest solution, which is the correct one. For a union problem the same reasoning gives the empty set as the starting value.
EX 4Count the passes
MediumFor the reaching-definitions example above the iterative algorithm takes 3 passes (the last confirming); for the live-variable example of the liveness page 2 passes (the block order reversed); for the available-expressions example 2 passes; for the constant-propagation examples 2 passes. These counts come from running the algorithm in the standard orders; a different visiting order may take more passes but ends in the same sets (the result does not depend on the order, which is a property of monotone frameworks). A question may count the final confirming pass or not: read it.
EX 5True or false
GATE level(a) "The iterative algorithm always terminates for a monotone framework with finite height": true. (b) "MFP and MOP are the same for every framework": false; equal for distributive frameworks (all gen-kill problems), while for constant propagation MFP is less precise. (c) "In a backward problem the entry block is visited first": false; the exit blocks are the boundary and the blocks are visited in the reverse order. (d) "The solution of a may-analysis is always a superset of the facts that hold on the actual executions": true: it is a safe over-approximation. (e) "Available expressions initialises OUT of non-entry blocks to the empty set": false; to the universal set. (f) "The order of visiting blocks changes the final sets": false (only the number of passes).
Cheat box
Cheat box
- Framework: values at program points, transfer function per block, direction, meet, boundary value, initial value.
- Gen-kill: OUT = gen (IN kill) forward; IN = use (OUT def) backward.
- Reaching definitions: forward, . Live variables: backward, . Available expressions: forward, . Constant propagation: forward, lattice meet, not distributive.
- Initial values: for problems, the universal set for problems (except the boundary).
- Iterate until no change; use reverse postorder (forward) or its reverse (backward); the last pass only confirms.
- Monotone and finite: terminates; MFP is safe; distributive: MFP = MOP.
Traps and GATE patterns
Connections
Builds on
Unlocks
Across subjects
No cross-subject links yet.
The graph is built from basic blocks. The three bit-vector instances are reaching definitions, liveness and available expressions; their results enable the optimisations of local optimisation across block boundaries (global common subexpressions, dead code, constant folding).
PYQs
No PYQs attached to this concept yet
Previous year questions are being added section by section from your PYQ book, each with my own re-checked answer. Nothing is shown from memory.
Self-check
Question 1
Which analysis is backward with union as the meet?
Show answerHide
Live variable analysis.
Question 2
What are the initial values of IN and OUT for a union analysis and for an intersection analysis (non-boundary blocks)?
Show answerHide
The empty set for union; the set of all facts for intersection.
Question 3
When do MFP and MOP coincide?
Show answerHide
When the transfer functions are distributive over the meet, as for all gen-kill problems.
Question 4
Why does the iterative algorithm terminate?
Show answerHide
The values form a finite lattice and the transfer functions are monotone, so each value can change only a finite number of times in one direction.
Revision card
One-screen revision
- Value per point, per block, direction, meet, boundary, initial value.
- Gen-kill forward: OUT = gen (IN kill); backward: IN = use (OUT def).
- RD: forward ; live: backward ; available: forward (start with ); constants: lattice, not distributive.
- Iterate to a fixed point in a good order; MFP safe; distributive gives MOP.
References
- ·Aho, Lam, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (2nd edition), Chapter 9 Machine-Independent Optimizations (9.2 introduction to data-flow analysis, 9.3 foundations of data-flow analysis: semilattices, transfer functions, the iterative algorithm, meet-over-paths)
- ·Cooper and Torczon, Engineering a Compiler, Chapter 9 Data-Flow Analysis
Verification
The iterative algorithm of scripts/cd_dfa.py is run on the textbook-style flow graph used on these pages and its results are recomputed by methods that do not use the data flow equations (graph reachability for reaching definitions, an instruction-level path search for live variables, enumeration of paths for available expressions) on 222 random programs with if, while, && and || (scripts/enum-checks/cd_dfa.py); no difference. The pass counts quoted (3, 2, 2, 2) are those of the program, using reverse postorder for forward problems and its reverse for backward problems. The bounds on the number of passes in terms of loop depth are quoted from the textbook without proof. SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand or by a small script (token counts, FIRST and FOLLOW sets, LL(1) tables and moves, LR item sets, state counts and parses, operator-precedence values, attribute evaluation and printing schemes, activation-record counts and access links, parameter-passing results, three-address code, jumping and backpatched code, leaders and blocks, value numbering and DAGs, gen/kill and data flow sets, Sethi-Ullman labels and live ranges); long C programs were not re-read line by line (they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose); tables and animations are machine output and were read, not re-run; No other defect found. Status raised to self-checked; verified needs a textbook or human check.
last verified: never
My notes (saved in this browser)