Data-flow analysis is a technique for gathering information about the possible set of values calculated at various points in a computer program. Computer programs (also software programs, or just programs) are instructions for a Computer. A program's control flow graph (CFG) is used to determine those parts of a program to which a particular value assigned to a variable might propagate. In computer science a control flow graph (CFG is a representation, using graph notation of all paths that might be traversed through a program during The information gathered is often used by compilers when optimizing a program. A compiler is a Computer program (or set of programs that translates text written in a computer language (the source language) into another Compiler optimization is the process of tuning the output of a Compiler to minimize or maximize some attribute of an Executable computer program A canonical example of a data-flow analysis is reaching definitions. In Compiler theory, a reaching definition for a given instruction is another instruction the target variable of which may reach the given instruction without an intervening
A simple way to perform dataflow analysis of programs is to set up dataflow equations for each node of the control flow graph and solve them by repeatedly calculating the output from the input locally at each node until the whole system stabilizes, i. A node is an abstract basic unit used to build linked Data structures such as trees, Linked lists and computer-based representations of graphs e. , it reaches a fixpoint. In Mathematics, a fixed point (sometimes shortened to fixpoint) of a function is a point that is mapped to itself by the function This general approach was developed by Gary Kildall while teaching at the Naval Postgraduate School. Gary Arlen Kildall (May 19 1942 – July 11 1994 was an American Computer scientist and Microcomputer Entrepreneur who created the CP/M The Naval Postgraduate School (NPS is a research university at the graduate-school level operated by the United States Navy. [1]
Contents |
Data flow analysis attempts to obtain particular information at each point in a procedure. Usually, it is enough to obtain this information at the boundaries of basic blocks, since from that it is easy to compute the information at points in the basic block. In forward flow analysis, the exit state of a block is a function of the block's entry state. This function is the composition of the effects of the statements in the block. The entry state of a block is a function of the exit states of its predecessors. This yields a set of data flow equations:
For each block b:

In this, transb is the transfer function of the block b. It works on the entry state inb, yielding the exit state outb. The join operation join combines the exit states of the predecessors
of b, yielding the entry state of b.
After solving this set of equations, the entry and / or exit states of the blocks can be used to derive properties of the program at the block boundaries. The transfer function of each statement separately can be applied to get information at a point inside a basic block.
Each particular type of data flow analysis has its own specific transfer function and join operation. Some data flow problems require backward flow analysis. This follows the same plan, except that the transfer function is applied to the exit state yielding the entry state, and the join operation works on the entry states of the successors to yield the exit state.
The entry point (in forward flow) plays an important role: Since it has no predecessors, its entry state is well-defined at the start of the analysis. In Computer programming, an entry point is a Memory address, corresponding to a point in the code of a computer program which is intended as destination of a For instance, the set of local variables with known values is empty. If the control flow graph does not contain cycles (there were no explicit or implicit loops in the procedure) solving the equations is straightforward. In Computer science control flow (or alternatively flow of control refers to the order in which the individual statements, instructions or Function The control flow graph can then be topologically sorted; running in the order of this sort, the entry states can be computed at the start of each block, since all predecessors of that block have already been processed, so their exit states are available. In Graph theory, a topological sort or topological ordering of a Directed acyclic graph (DAG is a linear ordering of its nodes in which each node comes If the control flow graph does contain cycles, a more advanced algorithm is required.
The most common way of solving the data flow equations is by using an iterative algorithm. It starts with an approximation of the in-state of each block. The out-states are then computed by applying the transfer functions on the in-states. From these, the in-states are updated by applying the join operations. The latter two steps are repeated until we reach the so-called fixpoint: the situation in which the in-states (and the out-states in consequence) don't change.
A basic algorithm for solving data flow equations is the round-robin iterative algorithm:
To be usable, the iterative approach should actually reach a fixpoint. This can be guaranteed by imposing constraints on the combination of the value domain of the states, the transfer functions and the join operation.
The value domain should be a partial order with finite height (i. In Mathematics, especially Order theory, a partially ordered set (or poset) formalizes the intuitive concept of an ordering sequencing or arrangement e. , there are no infinite ascending chains x1 < x2 < . . . ). The combination of the transfer function and the join operation should be monotonic with respect to this partial order. Monotonicity ensures that on each iteration the value will either stay the same or will grow larger, while finite height ensures that it cannot grow indefinitely. Thus we will ultimately reach a situation where T(x) = x for all x, which is the fixpoint.
It is easy to improve on the algorithm above by noticing that the in-state of a block will not change if the out-states of its predecessors don't change. Therefore, we introduce a work list: a list of blocks that still needs to be processed. Whenever the out-state of a block changes, we add its successors to the work list. In each iteration, a block is removed from the work list. Its out-state is computed. If the out-state changed, the block's successors are added to the work list. For efficiency, a block should not be in the work list more than once.
The algorithm is started by putting the entry point in the work list. It terminates when the work list is empty.
The efficiency of iteratively solving data-flow equations is influenced by the order at which local nodes are visited. Furthermore, it depends, whether the data-flow equations are used for forward or backward data-flow analysis over the CFG. Intuitively, in a forward flow problem, it would be fastest if all predecessors of a block have been processed before the block itself, since then the iteration will use the latest information. In the absence of loops it is possible to order the blocks in such a way that the correct out-states are computed by processing each block only once.
In the following, a few iteration orders for solving data-flow equations are discussed (a related concept to iteration order of a CFG is tree traversal of a tree). In computer science a control flow graph (CFG is a representation, using graph notation of all paths that might be traversed through a program during In Computer science, tree-traversal refers to the process of visiting each node in a Tree data structure, exactly once in a systematic way In Graph theory, a tree is a graph in which any two vertices are connected by exactly one path.
The initial value of the in-states is important to obtain correct and accurate results. If the results are used for compiler optimizations, they should provide conservative information, i. e. when applying the information, the program should not change semantics. The iteration of the fixpoint algorithm will take the values in the direction of the maximum element. Initializing all blocks with the maximum element is therefore not useful. At least one block starts in a state with a value less than the maximum. The details depend on the data flow problem. If the minimum element represents totally conservative information, the results can be used safely even during the data flow iteration. If it represents the most accurate information, fixpoint should be reached before the results can be applied.
The following are examples of properties of computer programs that can be calculated by data-flow analysis. Note that the properties calculated by data-flow analysis are typically only approximations of the real properties. This is because data-flow analysis operates on the syntactical structure of the CFG without simulating the exact control flow of the program. However, to be still useful in practice, a data-flow analysis algorithm is typically designed to calculate an upper respectively lower approximation of the real program properties.
The reaching definition analysis calculates for each program point the set of definitions that may potentially reach this program point.
1: if b==4 then 2: a = 5; 3: else 4: a = 3; 5: endif 6: 7: if a < 4 then 8: . . . |
The reaching definition of variable "a" at line 7 is the set of assignments a=5 at line 2 and a=3 at line 4. |
The live variable analysis calculates for each program point the variables that may be potentially read afterwards before their next write update. In Computer science, live variable analysis (or simply liveness analysis) is a classic Data flow analysis performed by Compilers to calculate The result is typically used by dead code elimination to remove statements that assign to a variable whose value is not used afterwards. In Compiler theory, dead code elimination is a Compiler optimization that removes code that does not affect the program
The in-state of a block is the set of variable that is live at the end of the block. Its out-state is the set of variable that is live at the start of it. The in-state is the union of the out-states of the blocks successors. The transfer function of a statement is applied by making the variables that are written dead, then making the variables that are read live.
// out: {}
b1: a = 3;
b = 5;
d = 4;
if a > b then
// in: {a,b,d}
// out: {a,b}
b2: c = a + b;
d = 2;
// in: {b,d}
// out: {b,d}
b3: endif
c = 4;
return b * d + c;
// in:{}
|
The out-state of b3 only contains b and d, since c has been written. The in-state of b1 is the union of the out-states of b2 and b3. The definition of c in b2 can be removed, since c is not live immediately after the statement.
Solving the data flow equations starts with initializing all in-states and out-states to the empty set. The work list is initialized by inserting the exit point (b3) in the work list (typical for backward flow). Its computed out-state differs from the previous one, so its predecessors b1 and b2 are inserted and the process continues. The progress is summarized in the table below.
| processing | in-state | old out-state | new out-state | work list |
|---|---|---|---|---|
| b3 | {} | {} | {b,d} | (b1,b2) |
| b1 | {b,d} | {} | {} | (b2) |
| b2 | {b,d} | {} | {a,b} | (b1) |
| b1 | {a,b,d} | {} | {} | () |
Note that b1 was entered in the list before b2, which forced processing b1 twice (b1 was re-entered as predecessor of b2). Inserting b2 before b1 would have allowed earlier completion.
Initializing with the empty set is an optimistic initialization: all variables start out as dead. Note that the out-states cannot shrink from one iteration to the next, although the out-state can be smaller that the in-state. This can be seen from the fact that after the first iteration the out-state can only change by a change of the in-state. Since the in-state starts as the empty set, it can only grow in further iterations.
The examples above are problems in which the data flow value is a set, e. g. the set of reaching definitions, or the set of live variables. These sets can be represented efficiently as bit vectors, in which each bit represents set membership of one particular element. Using this representation, the join and transfer functions can be implemented as bitwise logical operations. The join operation is typically union or intersection, implemented by bitwise logical or and logical and. The transfer function for each block can be decomposed in so-called gen and kill sets.
As an example, in live-variable analysis, the join operation is union. The kill set is the set of variables that are written in a block, whereas the gen set is the set of variables that are read without being written first. The data flow equations become


In logical operations, this reads as
Data-flow analysis is inherently flow-sensitive. Data-flow analysis is typically path-insensitive, though it is possible to define data-flow equations that yield a path-sensitive analysis.
These terms aren't specific to data-flow analysis. The following definitions should be moved somewhere else and fleshed out with more verbose examples. Wikipedia's inter-document structure for static analysis could use some rework.