Introduction

Global optimisations need facts that hold at a program point on every execution, or on some execution: "this definition of x may reach here", "y may be used later", "a + b has already been computed on every path to here", "z is always 3 here". Data-flow analysis computes such facts for every point of a flow graph by solving a system of equations, one pair per block, by repeated approximation until nothing changes.

All the standard analyses share one shape: a set of values, a direction (forward or backward), a meet operator that combines information where paths join, transfer functions for blocks, and an initial value. Learning that shape once makes reaching definitions, liveness, available expressions and constant propagation instances of the same algorithm. Questions ask for the direction and meet of an analysis, its equations, or the IN and OUT sets of a small flow graph.

Prerequisites

  • Basic Blocks and Control-Flow Graphs: blocks, predecessors and successors, and loops in the flow graph.
  • Sets, union and intersection, and the idea of a fixed point of an equation system.

Core concepts

  • Program points. Before and after each instruction; for a block BB, IN[B]IN[B] is the value at its start and OUT[B]OUT[B] at its end.
  • Transfer function fBf_B. Maps the value on one side of BB to the value on the other. For the classic bit-vector problems it has the form f(x)=gen∪(x−kill)f(x) = gen \cup (x - kill).
  • Direction. Forward: information flows with control, OUT[B]=fB(IN[B])OUT[B] = f_B(IN[B]) and IN[B]IN[B] combines predecessors' OUT. Backward: against control, IN[B]=fB(OUT[B])IN[B] = f_B(OUT[B]) and OUT[B]OUT[B] combines successors' IN.
  • Meet ∧\wedge. Combines values at a join. Union for "may" problems (a fact holds if it holds along some path); intersection for "must" problems (it holds along all paths).
  • Boundary value. The value at the entry (forward) or exit (backward).
  • Initial value. Where iteration starts for the other blocks: the empty set for union problems, the universal set for intersection problems.
  • Semilattice. The set of values with the meet; it must have a top element ⊤\top with x∧⊤=xx \wedge \top = x. The ordering x≤yx \le y means x∧y=xx \wedge y = x: xx is the safer, less precise value.

How it works

The four classic problems

AnalysisDomainDirectionMeetTransferBoundaryInitial
Reaching definitionsSets of definitionsForward∪\cupgenB∪(x−killB)gen_B \cup (x - kill_B)OUT[entry]=∅OUT[\text{entry}] = \emptysetOUT[B]=∅OUT[B] = \emptyset
Live variablesSets of variablesBackward∪\cupuseB∪(x−defB)use_B \cup (x - def_B)IN[exit]=∅IN[\text{exit}] = \emptysetIN[B]=∅IN[B] = \emptyset
Available expressionsSets of expressionsForward∩\cape_genB∪(x−e_killB)e\_gen_B \cup (x - e\_kill_B)OUT[entry]=∅OUT[\text{entry}] = \emptysetOUT[B]=UOUT[B] = U
Constant propagationMap from variables to {undefined, constant, not-a-constant}ForwardLattice meet per variableEvaluate the block on the mapEvery variable undefinedEvery variable undefined
Forward and backward analyses, may and must problems
Figure 1. A forward analysis computes OUT from IN and meets the OUT values of predecessors; a backward analysis computes IN from OUT and meets the IN values of successors. Reaching definitions and available expressions are forward, live variables backward; union marks a may problem and intersection a must problem.

The iterative algorithm

For a forward problem:

Text
OUT[entry] = boundary value
for each block B other than entry: OUT[B] = initial value (top)
while some OUT changes:
    for each block B other than entry:
        IN[B]  = meet of OUT[P] over predecessors P of B
        OUT[B] = f_B(IN[B])

A backward problem is the mirror image: swap IN and OUT and use successors. Visiting blocks in reverse postorder (roughly, program order) for forward problems, and in postorder for backward problems, makes information flow far in each pass. The algorithm needs one pass more than the number of passes that change something, since the last pass only confirms that nothing changes.

Gen and kill for a block

For reaching definitions, a block's gengen holds the definitions in it that are not overwritten later in the same block; its killkill holds every other definition, anywhere in the program, of a variable the block defines. The composition of the instructions' transfer functions gives the block's, because each has the form gen∪(x−kill)gen \cup (x - kill).

Example: reaching definitions

Text
B1: d1: x = 1
    d2: y = 2
B2: d3: x = x + 1
B3: d4: y = x * 2
B4: d5: x = y - 1

Edges: entry →B1\to B_1, B1→B2B_1 \to B_2, B2→B3B_2 \to B_3, B2→B4B_2 \to B_4, B3→B4B_3 \to B_4, B4→B2B_4 \to B_2, B4→B_4 \to exit. The definitions of x are d1,d3,d5d_1, d_3, d_5; those of y are d2,d4d_2, d_4.

Blockgenkill
B1B_1d1,d2d_1, d_2d3,d4,d5d_3, d_4, d_5
B2B_2d3d_3d1,d5d_1, d_5
B3B_3d4d_4d2d_2
B4B_4d5d_5d1,d3d_1, d_3

Iterating in the order B1,B2,B3,B4B_1, B_2, B_3, B_4, with every OUT starting empty:

BlockOUT after pass 1OUT after pass 2Final IN
B1B_1d1,d2d_1, d_2d1,d2d_1, d_2∅\emptyset
B2B_2d2,d3d_2, d_3d2,d3,d4d_2, d_3, d_4d1,d2,d4,d5d_1, d_2, d_4, d_5
B3B_3d3,d4d_3, d_4d3,d4d_3, d_4d2,d3,d4d_2, d_3, d_4
B4B_4d2,d4,d5d_2, d_4, d_5d2,d4,d5d_2, d_4, d_5d2,d3,d4d_2, d_3, d_4

Pass 3 changes nothing, so three passes are made. In pass 1, B2B_2 has not yet seen OUT[B4]OUT[B_4], which is still empty; in pass 2 it picks up d4d_4 and d5d_5 through the back edge, giving OUT[B2]={d3}∪({d1,d2,d4,d5}−{d1,d5})={d2,d3,d4}OUT[B_2] = \{d_3\} \cup (\{d_1, d_2, d_4, d_5\} - \{d_1, d_5\}) = \{d_2, d_3, d_4\}.

Reading the result: at the start of B2B_2, x may come from d1d_1 or d5d_5, so x is not a known constant there. d3d_3 does not reach the start of B2B_2: every path back to B2B_2 passes B4B_4, which redefines x. d2d_2 reaches B4B_4 because the path B1,B2,B4B_1, B_2, B_4 skips B3B_3, the only other block that defines y. Visiting the blocks in the reverse order B4,B3,B2,B1B_4, B_3, B_2, B_1 gives the same final sets after four passes.

Use-definition chains. Reaching definitions answer "which definitions can supply the value read here?". Restrict the IN set of a block to the definitions of the variable being read:

UseReaching definitions of that variableConsequence
x in d3d_3 (B2B_2)d1d_1, d5d_5two possible values; not a constant
x in d4d_4 (B3B_3)d3d_3 onlythe value is always the one computed in B2B_2
y in d5d_5 (B4B_4)d2d_2, d4d_4depends on whether B3B_3 ran

These lists are the ud-chains of the uses. A unique reaching definition assigning a known constant is sufficient, not necessary, for constant replacement: all possible reaching definitions assigning the same known constant also permit it, provided no unmodeled modification intervenes (compare Constant Propagation).

Two definitions of one variable in a block. In a block d6: x = 1; d7: y = x; d8: x = y + 2, in a program where x is also defined by d9d_9 and y by d10d_{10} elsewhere, gen={d7,d8}gen = \{d_7, d_8\}: d6d_6 is overwritten by d8d_8 before the block ends. kill={d6,d9,d10}kill = \{d_6, d_9, d_{10}\}: the block kills its own earlier definition d6d_6 as well as the definitions in other blocks. A definition reaches the end of a block only if it is the last definition of its variable in the block.

New analyses described in words

AnalysisThe fact at a pointDirectionMeet
Possibly uninitialised variablessome path from the entry reaches here without assigning vForward∪\cup
Definitely assigned variablesv has been assigned on every path from the entryForward∩\cap
Dominatorsblock dd lies on every path from the entryForward∩\cap
Very busy (anticipated) expressionse will be evaluated on every path from here before any operand changesBackward∩\cap

Related material. Very busy expressions are the anticipated expressions of partial-redundancy elimination (Dragon Book Section 9.5); they show that a backward must problem exists.

Why it terminates and what it computes

  • Monotone transfer functions (x≤y⇒f(x)≤f(y)x \le y \Rightarrow f(x) \le f(y)) and a semilattice of finite height guarantee that each value can only move down a finite number of times, so the iteration stops.
  • The result is the maximum fixed point (MFP): the largest solution of the equations under the lattice order, which is the most precise safe answer the equations allow.
  • The ideal answer is the meet over all paths (MOP): apply the transfer functions along every path to the point and meet the results. Iteration meets at joins first, so in general MFP≤MOPMFP \le MOP (MFP is safe but possibly less precise). If every transfer function is distributive, f(x∧y)=f(x)∧f(y)f(x \wedge y) = f(x) \wedge f(y), then MFP=MOPMFP = MOP.
  • The gen-kill problems are distributive. Constant propagation is monotone but not distributive (see the note Constant Propagation).

Choosing direction and meet

Ask two questions. Does the fact at a point depend on the past or the future? Definitions and computed expressions come from the past (forward); uses come from the future (backward). Must it hold on all paths or just some? "May reach" and "may be live" need some path (union); "is available" needs all paths (intersection).

Important properties and observations

  • Union problems start from ∅\emptyset; intersection problems start from UU (except at the boundary). Starting available expressions from ∅\emptyset everywhere gives a correct but needlessly weak answer on loops.
  • The order of visiting blocks changes the number of passes, not the result.
  • Passes are bounded by the depth of the flow graph plus 2 for these bit-vector problems when blocks are visited in depth-first order; the depth is the largest number of retreating edges on any cycle-free path, 1 in the example, which needs 3 passes.
  • Precision versus safety. Reaching definitions and liveness may overestimate (a "may" answer that includes too much is safe for optimisation); available expressions may underestimate.
  • Instruction-level points follow from block-level IN or OUT by applying the instructions' transfer functions one at a time.

Common misconceptions.

  • "Liveness is forward." It is backward: whether a variable is used later depends on what follows.
  • "Available expressions uses union." It uses intersection: the expression must be computed on every path.
  • "The iteration's first pass gives the answer for loops." Back edges carry information that needs at least one more pass.

GATE examination perspective

  • Direction and meet. Consider a named analysis or a new one described in words.
  • Equations. Write IN and OUT for a forward or backward problem; know gen, kill, use and def.
  • Compute sets. Work on a small flow graph: gen and kill per block, then IN and OUT by iteration.
  • Initialisation trap. Universal set for intersection problems.
  • ud-chains. Apply this to a use of v, list the reaching definitions of v only; a unique known-constant definition is sufficient; multiple reaching definitions also allow replacement when all assign the same known constant and no unmodeled modification intervenes.
  • gen and kill with repeated targets. Only the last definition of each variable in a block is in gengen; the earlier ones are in killkill.
  • Classify a new analysis. By the two questions: past or future (direction), all paths or some path (meet).
  • Properties. Monotone, distributive, MOP versus MFP, termination on a finite lattice.

Summary

Sources

  1. A. V. Aho, M. S. Lam, R. Sethi, J. D. Ullman, Compilers, Principles, Techniques, and Tools, 2nd ed., Pearson Addison-Wesley, 2007: Section 9.2 (the data-flow abstraction, the data-flow analysis schema, schemas on basic blocks, reaching definitions, live-variable analysis, available expressions, summary), Section 9.3 (semilattices, transfer functions, the iterative algorithm for general frameworks, meaning of a data-flow solution), Section 9.5 (partial-redundancy elimination and anticipated expressions; related material only), Section 9.6.7 (speed of convergence of iterative data-flow algorithms).

Next steps