G27
Self-checkedTier 1 11 min readcd.dataflow-reaching-constprop

Reaching definitions and constant propagation

Reaching definitions (gen, kill, the equations, use-definition information) and constant propagation on the lattice of undefined, constant and not-a-constant values, with the transfer function, the meet, a worked flow graph, and the example that shows constant propagation is not distributive.

Why it exists

Two basic questions about a variable at some point of the program: which assignments could have produced the value it has now? (reaching definitions) and is that value always the same known constant? (constant propagation). The first supports many checks and optimisations (a use with exactly one reaching definition can be replaced by the value of that definition; a use with none may read an uninitialised variable). The second lets the compiler replace variables by constants and fold the arithmetic, which often removes whole branches.

Prerequisites

You need the framework of data flow analysis: values at points, transfer functions, meet and the iterative algorithm (data flow framework).

Precise definitions

Definition. An instruction that may assign a value to a variable xx (here x = ...; also reads into xx, and assignments through pointers in full languages). The definitions of the program are numbered d1,d2,…d_1, d_2, \ldots in program order.

Reaching definition. A definition dd of xx reaches a point pp if there is a path from the point after dd to pp along which xx is not redefined (killed). It is a may property: one path is enough. A definition of xx kills every other definition of xx.

Gen and kill of a block. genB\text{gen}_B: the definitions in BB that reach the end of BB, that is, for each variable defined in BB only the last definition of it in the block. killB\text{kill}_B: all other definitions (in the whole program) of the variables that BB defines.

Equations (forward, union).

IN[B]=⋃P pred of BOUT[P],OUT[B]=genB∪(IN[B]−killB),OUT[ENTRY]=∅.\text{IN}[B] = \bigcup_{P\ \text{pred of}\ B} \text{OUT}[P],\qquad \text{OUT}[B] = \text{gen}_B \cup (\text{IN}[B] - \text{kill}_B),\qquad \text{OUT}[\text{ENTRY}] = \emptyset.

Uses of reaching definitions. Use-definition chains (for each use of a variable, the definitions that may supply its value): the basis of constant folding and copy propagation (if a single definition x = 5 reaches a use of x, the use can be replaced by 5), of detecting possibly-uninitialised variables (no definition reaches the use), and of loop-invariant code motion.

Constant propagation. For each variable at each program point the analysis tracks a value from a three-level lattice:

UNDEF  (no information yet)>c (a particular constant)>NAC (not a constant).\text{UNDEF}\ \ (\text{no information yet})\quad >\quad c\ (\text{a particular constant})\quad >\quad \text{NAC}\ (\text{not a constant}).

The meet ∧\wedge of two values: UNDEF∧v=v\text{UNDEF} \wedge v = v; c∧c=cc \wedge c = c; c1∧c2=NACc_1 \wedge c_2 = \text{NAC} for c1≠c2c_1 \ne c_2; NAC∧v=NAC\text{NAC} \wedge v = \text{NAC}. At a join the meet is taken variable by variable. The data flow value at a point is a map from each variable to such a value, and the forward analysis starts with UNDEF everywhere except for variables whose value is not known at compile time (inputs), which are NAC.

Transfer function. Run the assignments of the block in order on the map. For x = c (a constant) set xx to cc; for x = y give xx the value of yy; for x = y op z: if both values are constants c1c_1, c2c_2 the value is c1 op c2c_1 \,\text{op}\, c_2; if either is NAC, NAC; if one is UNDEF and the other a constant, UNDEF (not yet known). A read from memory or a call gives NAC.

Properties. The framework is monotone (so the iterative algorithm terminates), but not distributive: the value of x+yx + y computed after the meet of two paths can be worse than the meet of the values computed along each path. The iterative (MFP) answer is therefore a safe but possibly less precise approximation of the meet over paths.

How it works

First the bookkeeping of reaching definitions on the loop program with six definitions (d1: i = m - 1, d2: j = n, d3: a = u1, d4: i = i + 1, d5: j = j - 1, d6: a = u2). The animation computes gen and kill for each block. In B2 the definition d4 of i is generated, and it kills the other definition of i, namely d1; in B3, d5 kills d2; in B4, d6 kills d3. B1 generates all three of its definitions and, since it defines i, j and a, kills d4, d5 and d6.

Reaching definitions: gen and kill sets: start

Computed by a script, not typed
  1. 11: i = m - 1
  2. 22: j = n
  3. 33: a = u1
  4. 44: i = i + 1
  5. 55: if a > i goto 7
  6. 66: j = j - 1
  7. 77: a = u2
  8. 88: if j > 0 goto 4
blockdefinitions in the blockgenkill
B1
B2
B3
B4

Step 0

For every block we need the definitions it generates and kills. A definition is an instruction that assigns a variable; the definitions are numbered d1, d2, ... in program order. gen[B] holds the definitions in B that reach its end (a later definition of the same variable in B replaces an earlier one). kill[B] holds all other definitions in the program of the variables that B defines.

0 / 4
computed by script: the analysis is executed, one block at a time. python3 scripts/gen-cd-dfa-traces.py

The iterative solution of the equations for this program is shown in the animation on the framework page: IN[B2] = {d1, d2, d3, d4, d5, d6} (the first three come from B1, the last three round the loop), so every definition of i, j and a reaches the start of B2.

Constant propagation on a graph with a branch. B1 sets x = 2 and y = 3 and tests c. If c > 0 control goes directly to the join; otherwise B2 sets y = 5. At the join z = x + y, w = x + 1 and print z. The variable c is an input (NAC). At the join x = 2 from both paths (so 2 ∧ 2 = 2), but y is 3 on one and 5 on the other: 3 ∧ 5 = NAC. Therefore z = x + y is NAC and w = x + 1 = 3 is a constant that can be folded.

Constant propagation: start

Computed by a script, not typed
  1. 11: x = 2
  2. 22: y = 3
  3. 33: if c > 0 goto 5
  4. 44: y = 5
  5. 55: z = x + y
  6. 66: w = x + 1
  7. 77: print z
blockIN (known values)OUT (known values)
B1
B2
B3

Step 0

Constant propagation is a forward problem on a different lattice: every variable has a value that is undefined (UNDEF, nothing known yet), a constant, or NAC (not a constant). The meet of two different constants is NAC; UNDEF meets anything to give that thing. The variable c is an input, so it is NAC at the entry.

0 / 6
computed by script: the analysis is executed, one block at a time. python3 scripts/gen-cd-dfa-traces.py

Finally the example showing that constant propagation is not distributive. On one path the code sets x = 2, y = 3, on the other x = 3, y = 2, then z = x + y. The meet of the values at the join gives x = NAC and y = NAC, so z = NAC. But along both paths z = 5: applying the transfer function on each path and then taking the meet would have given the constant 5. The analysis loses this constant; it is safe (it does not claim anything false) but not as precise as it could be.

Constant propagation loses a constant: z is 5 on both paths: start

Computed by a script, not typed
  1. 11: if c > 0 goto 5
  2. 22: x = 2
  3. 33: y = 3
  4. 44: goto 7
  5. 55: x = 3
  6. 66: y = 2
  7. 77: z = x + y
  8. 88: print z
blockIN (known values)OUT (known values)
B1
B2
B3
B4

Step 0

On one path x = 2 and y = 3, on the other x = 3 and y = 2. Constant propagation computes the meet of the values at the join first and applies the transfer function afterwards.

0 / 8
computed by script: the analysis is executed, one block at a time. python3 scripts/gen-cd-dfa-traces.py

Worked examples

EX 1Gen and kill

Easy

In the block i = m - 1; j = n; a = u1 (B1 of the animation) the definitions d1,d2,d3d_1, d_2, d_3 are all generated; they kill the other definitions of i, j, a, namely d4d_4 (i), d5d_5 (j) and d6d_6 (a). If a block contained x = 1; x = 2 with definitions d7d_7 and d8d_8, only d8d_8 would be in gen (the first value is overwritten inside the block); d7d_7 is in kill together with the other definitions of x elsewhere in the program.

EX 2Which definitions reach the start of B4?

Easy

With IN[B4] = OUT[B2] ∪\cup OUT[B3] = {d2, d3, d4, d5, d6} (the animation of the framework page): d1 is missing because d4 redefines i before any path reaches B4 (every path to B4 goes through B2); d2 reaches along the path B2 → B4 (B2 does not define j) and d5 along the path through B3; both reach. The variable a has two definitions reaching: d3 (from B1, via B2 and B3) and d6 (round the loop). So at the start of B4 the value of a may come from d3 or from d6: it is not a single definition.

EX 3Constant propagation through a diamond

Medium

B1: x = 2; y = 3; if c > 0 goto B3; B2: y = 5; B3: z = x + y; w = x + 1; print z. IN[B3] = OUT[B1] ∧\wedge OUT[B2]: x: 2∧2=22 \wedge 2 = 2; y: 3∧5=NAC3 \wedge 5 = \text{NAC}; c: NAC. In B3: z = 2 + NAC = NAC, w = 2 + 1 = 3. So w can be replaced by the constant 3 everywhere it is used, z cannot. Note that x is propagated although it is assigned on a path that does not go through B2.

EX 4Meet table

Medium

UNDEF∧4=4\text{UNDEF} \wedge 4 = 4; 4∧4=44 \wedge 4 = 4; 4∧7=NAC4 \wedge 7 = \text{NAC}; NAC∧4=NAC\text{NAC} \wedge 4 = \text{NAC}; UNDEF∧UNDEF=UNDEF\text{UNDEF} \wedge \text{UNDEF} = \text{UNDEF}. In a loop the first time round the back edge brings UNDEF (the value has not been computed yet), so the first pass sees only the other predecessor; later passes may turn a constant into NAC if the loop changes the variable (i = i + 1 makes i NAC at the loop header).

EX 5Is constant propagation distributive?

GATE level

No. Take x = 2; y = 3 on one branch and x = 3; y = 2 on the other, then z = x + y. The iterative algorithm gives NAC for z; the meet over paths gives 5. The framework is monotone, so the algorithm terminates and is safe; the loss of precision is why this is the standard example when a question asks "for which of these problems are MFP and MOP different?" The bit-vector problems (reaching definitions, liveness, available expressions) are distributive: their MFP equals MOP. (The run of both paths was checked: z is 5 in both.)

Cheat box

Cheat box

  • Reaching definitions: forward, union. gen = last definition of each variable in the block; kill = all other definitions of those variables.
  • IN = union of OUT of predecessors; OUT = gen ∪\cup (IN −- kill). OUT[entry] = ∅\emptyset.
  • Use of RD: a use with one reaching definition x = c can be replaced by c; none means uninitialised.
  • Constant propagation lattice: UNDEF, constants, NAC. Meet: UNDEF ∧v=v\wedge v = v; c∧c=cc \wedge c = c; c1∧c2=NACc_1 \wedge c_2 = \text{NAC}; NAC absorbs.
  • Transfer: evaluate with the known constants; any NAC operand gives NAC.
  • Constant propagation is monotone but not distributive: MFP can be weaker than MOP.

Traps and GATE patterns

Connections

The framework is the data flow framework; the other bit-vector problems are liveness (the same machinery backwards) and available expressions (intersection). Within a single block the same effect is obtained by value numbering in local optimisation.

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 definitions are in gen and kill for the block x = 1; y = 2; x = 3, if the program has other definitions d9d_9 of x and d10d_{10} of y?

Show answer

gen contains the definitions of y (the single one) and the last definition of x (x = 3). kill contains the other definitions of x and y in the program, including d9d_9, d10d_{10} and the block's own first x = 1.

Question 2

What is UNDEF∧5\text{UNDEF} \wedge 5, 5∧55 \wedge 5, 5∧65 \wedge 6?

Show answer

5, 5 and NAC.

Question 3

Why can a constant be lost by the iterative algorithm although it holds on every path?

Show answer

The meet is taken before the transfer function is applied; the transfer function of z = x + y is not distributive over the meet, so the sum of two NACs is NAC even if the real sums on the paths agree.

Question 4

A use of v has two reaching definitions v = 1 and v = 2. Can it be replaced by a constant?

Show answer

No: the value is 1 on some paths and 2 on others (NAC).

Revision card

One-screen revision

  • RD: gen = surviving definitions of the block, kill = all other definitions of the same variables; forward ∪\cup.
  • Constant propagation: UNDEF, constant, NAC; meet pointwise; evaluate with constants.
  • Not distributive: MFP can lose constants that MOP keeps.
  • A use with one reaching constant definition can be folded.

References

  • ·Aho, Lam, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (2nd edition), Chapter 9 Machine-Independent Optimizations (9.2.4 reaching definitions, 9.3.3 constant propagation)
  • ·Cooper and Torczon, Engineering a Compiler, Chapter 9 Data-Flow Analysis (reaching definitions; constant propagation)

Verification

Reaching definitions: the sets of the textbook-style example (gen, kill, IN, OUT for four blocks) are computed by the iterative algorithm and recomputed by graph reachability on 222 random programs (scripts/enum-checks/cd_dfa.py). Constant propagation: on 3000 random executions of random programs (36000 block entries) every variable the analysis calls constant had that value and every variable it calls undefined had not been assigned; the two-path example in which the analysis answers NAC for a sum that is 5 on both paths was run on both paths. The convention that an undefined value meets a constant to give that constant (an optimistic treatment of reads of unassigned variables) is that of the textbook. 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