Fact-checked by Grok 2 weeks ago

Data-flow analysis

Data-flow analysis is a fundamental technique in compiler design for performing static program analysis, which approximates the dynamic flow of data and control through a program's to infer properties about and expressions at various points without executing the code. This analysis enables optimizing to identify opportunities for transformations that improve code efficiency, such as eliminating , propagating constants, and reusing common subexpressions. By modeling program behavior abstractly, it supports both forward analyses (e.g., tracking definitions that reach a use) and backward analyses (e.g., determining variable liveness), providing a foundation for safe and profitable optimizations. At its core, data-flow analysis operates on an of the program, typically divided into basic blocks connected by control-flow edges, where about data states—such as sets of reaching definitions or live variables—is propagated iteratively to reach a fixed point. The process uses transfer functions to describe how data facts are generated (Gen) or killed (Kill) within each block, combined with meet operators (e.g., for forward problems like reaching definitions) to merge from predecessors, ensuring on a of possible states. Classic examples include reaching definitions analysis, which computes the set of variable assignments that may reach a given point to prevent undefined variable uses, and liveness analysis, a backward pass that identifies variables used on some execution path without prior redefinition to guide . Beyond intraprocedural analysis within single procedures, data-flow techniques extend to interprocedural contexts, handling calls and returns across function boundaries, and have evolved to incorporate advanced representations like form for more precise value tracking. These methods, rooted in algorithms, are essential for modern compilers in languages like and , influencing optimizations that reduce execution time and resource usage while preserving program semantics.

Fundamentals

Definition and Purpose

Data-flow analysis is a static analysis technique used in compilers and verification tools to determine the possible values that variables may hold at various points in a without executing it. This method models the program's structure as a (CFG), which is a where nodes represent basic blocks—sequences of consecutive statements with no branches or jumps—and edges indicate possible control transfers between these blocks. By propagating abstract information across the CFG, data-flow analysis tracks how data values or properties flow through the , enabling inferences about variable states along all possible execution paths. The primary purpose of data-flow analysis is to facilitate optimizations that improve and , such as , which removes unreachable or unused instructions, and , which assigns variables to registers based on their liveness. It also supports detection by identifying issues like uninitialized variable uses during compilation. Beyond optimization, data-flow analysis underpins broader static analysis applications, including the detection of security vulnerabilities through techniques like taint tracking and bug finding in tools. Data-flow analysis originated in the 1960s and 1970s as part of early research, pioneered by Frances E. Allen at , who developed foundational techniques for analyzing control and data flows in flowchart-based programs. Allen's work, including her 1970 paper on control flow analysis, laid the groundwork for extending these methods to languages, enabling global optimizations across entire programs. This historical development transformed static analysis from ad hoc optimizations to a systematic framework integral to modern .

Lattices and Abstract Domains

In data-flow analysis, lattices provide a to model the sets of possible states or properties at points, enabling the representation and combination of information in a way that supports fixed-point computations. A is defined as a (poset) equipped with a partial order \leq, where every pair of elements has a least upper bound (join, denoted \sqcup or \vee) and a greatest lower bound (meet, denoted \sqcap or \wedge). The combines information conservatively (e.g., of sets), while the meet intersects it, ensuring that the captures approximations of program behaviors without losing essential properties. The of a , defined as the length of the longest chain from bottom to top element, bounds the number of iterations needed for in finite-height lattices, while the width, related to the size of the largest , influences the complexity of representing and manipulating elements. Abstract domains approximate the concrete domain of all possible program values or states, which is often infinite and intractable, by mapping it to a finite that over-approximates relevant properties. This approximation is formalized through , where a concretization function \gamma maps abstract elements back to sets, and an abstraction function \alpha projects sets onto the domain, forming a that ensures (every abstract property implies a concrete one). For instance, the domain abstracts integer values into a finite with elements \{\bot, -, 0, +, \top\} (where \bot is the bottom element, - negative, $0zero,+positive,\toptop representing any sign), ordered by [implication](/page/Implication) (e.g.,- \leq \top$), with join as the least upper bound (least precise common sign) and meet as the greatest lower bound (most precise). Such domains make analysis decidable by reducing infinite state spaces to manageable finite structures, trading for . Transfer functions in data-flow analysis, which model the effect of program statements on elements, must be monotonic to guarantee convergence of iterative analyses. Monotonicity requires that for any elements x \leq y, the function satisfies f(x) \leq f(y), preserving the order and ensuring that information flows do not oscillate indefinitely in finite . This property, combined with the 's completeness ( of joins and meets for all subsets), ensures the of least fixed points under fixed-point theorems. A common example is the powerset , used in analyses like reaching definitions, where the is the power set of possible definitions ordered by subset inclusion (\subseteq), with (\cup) as join to accumulate reachable facts and (\emptyset) as bottom element. This captures sets of variables or definitions conservatively, where the join operation merges information from multiple control-flow paths without under-approximating possibilities.

Data Flow Equations

Data flow problems in compiler optimization are expressed as systems of equations over a program's (CFG), where each node represents a and edges denote possible control transfers. These equations capture the propagation of abstract information, such as variable definitions or uses, across the graph to enable optimizations like or constant propagation. The solutions to these equations provide a fixed-point approximation of the program's behavior in the chosen abstract domain. In the general form for forward data flow analysis, the input information to a block B, denoted \mathit{in}[B], is the meet (e.g., intersection for sets) of the output information from its predecessors: \mathit{in}[B] = \bigwedge_{p \in \mathit{pred}(B)} \mathit{out} The output information from B, denoted \mathit{out}[B], is then computed by applying a f_B to the input: \mathit{out}[B] = f_B(\mathit{in}[B]) Here, f_B performs local analysis specific to the statements in B, such as updating sets of reachable values. Forward analyses accumulate information in the direction of , computing outputs from inputs to track effects propagating forward. In contrast, backward analyses propagate information reversely, defining the output of B as the join (e.g., union for sets) of the inputs to its successors: \mathit{out}[B] = \bigvee_{s \in \mathit{succ}(B)} \mathit{in} with the input derived via the : \mathit{in}[B] = f_B(\mathit{out}[B]) This distinction allows backward problems, like liveness analysis, to reason about information needed after a . Transfer functions f_B encapsulate block-local computations and are often expressed in gen-kill form for bit-vector analyses, where generated (\mathit{gen}[B]) and killed (\mathit{kill}[B]) elements represent added or invalidated information: \mathit{out}[B] = (\mathit{in}[B] \setminus \mathit{kill}[B]) \cup \mathit{gen}[B] More generally, these use operations like difference (\ominus) and join (\oplus) to ensure monotonicity. The full system comprises such constraints for all blocks in the CFG, forming a set of interdependent equations solved collectively.

Core Algorithm

Iterative Fixed-Point Computation

In data-flow analysis, a fixed point represents a solution to the data flow equations where, for every B, the incoming \mathrm{in}[B] equals the meet (join) over the outgoing from its predecessors, \mathrm{in}[B] = \bigwedge_{p \in \mathrm{pred}(B)} \mathrm{out}, and the outgoing \mathrm{out}[B] is obtained by applying the for B, \mathrm{out}[B] = f_B(\mathrm{in}[B]). The least fixed point is the minimal such solution with respect to the partial order of the , providing the most precise approximation that satisfies the equations from below. The iterative fixed-point computation approximates this solution by beginning with initial values for \mathrm{in} and \mathrm{out} across all blocks and repeatedly updating them according to the data flow equations until the values stabilize, meaning no further changes occur. In each , the incoming values are recomputed as the meet of the current outgoing values from predecessors, and the outgoing values are updated via the block-specific functions; this process continues until to the fixed point. For guaranteed convergence, the data flow framework must be monotone, meaning that the transfer functions f_B are monotone with respect to the lattice order—if x \sqsubseteq y, then f_B(x) \sqsubseteq f_B(y)—and the meet operation is also monotone, ensuring that iterations produce non-decreasing (or non-increasing, depending on formulation) sequences in finite-height lattices. In such monotone systems over finite lattices, the iterative process terminates after a finite number of steps, yielding the least fixed point. To compute the least fixed point via chaotic iterations—which process blocks in an arbitrary order without relying on topological sorting—the initial values are set to the bottom element \bot for both forward and backward analyses, allowing the approximations to refine progressively through the monotone updates. This chaotic approach ensures correctness and termination under the monotonicity assumption, independent of the iteration schedule.

Convergence Properties

The convergence of iterative data-flow analysis algorithms relies fundamentally on the Knaster-Tarski theorem, which guarantees the existence of fixed points for monotone functions defined over complete lattices. Specifically, if f: L \to L is a monotone function on a complete lattice (L, \sqsubseteq), then the set of fixed points of f forms a complete lattice, and f has both a least fixed point (the greatest lower bound of all fixed points) and a greatest fixed point (the least upper bound of all fixed points). This theorem underpins the theoretical foundation for solving data-flow equations, as the transfer functions in data-flow frameworks are typically monotone with respect to the lattice order, ensuring that iterative applications of f will converge to these fixed points. In finite lattices, which are common in practical data-flow analyses (such as bit-vector representations for reaching definitions), termination is further assured by the finite height of the . The height of a L is the length of the longest of strictly increasing elements, and since monotone iterations produce non-decreasing sequences, the process cannot exceed this height without stabilizing at a fixed point. Thus, for a with |L| elements, the algorithm terminates after at most |L| iterations in the worst case, as each step ascends the partial order until no further changes occur. Control flow graphs (CFGs) often contain cycles due to loops and branches, which could potentially lead to propagation in non-terminating analyses. However, the monotonicity of the functions ensures that propagates around these cycles in a controlled manner, with values accumulating (via join operations) until the analysis reaches a stable fixed point where further iterations yield no changes. Non-convergence can arise in analyses over domains (e.g., unbounded intervals) or with non-monotone functions, where ascending chains may not stabilize. To mitigate this, widening operators are introduced to artificially bound the height by over-approximating values after a certain number of iterations, thereby enforcing termination while preserving .

Worklist Implementation

The worklist algorithm provides an efficient mechanism for solving data-flow equations by maintaining a of nodes whose may need updating, thereby avoiding unnecessary reprocessing of unchanged nodes. It begins by initializing the for each node (typically to the bottom element of the ) and adding the entry node to the worklist. The algorithm then iteratively dequeues a node B, computes its new output using the applied to its current input , and checks the successors of B. If the propagated information (via the ) changes the input of a successor, that successor is enqueued for reprocessing. This process continues until the worklist is empty, at which point the fixed point has been reached. The following pseudocode illustrates a simple forward analysis implementation, assuming a control-flow graph with nodes representing basic blocks, transfer functions f_B, and meet operation \sqcup:
for each basic block B in the program:
    in[B] ← ⊥  // bottom element
    out[B] ← ⊥
in[entry] ← initial  // initial abstract state at entry
worklist ← {entry}  // initialize with entry block

while worklist ≠ ∅:
    dequeue B from worklist
    old_out ← out[B]
    out[B] ← f_B(in[B])  // apply transfer function
    if out[B] ≠ old_out:  // change detected
        for each successor S of B:
            old_in ← in[S]
            in[S] ← in[S] ⊔ out[B]  // meet with propagated state
            if in[S] ≠ old_in:
                enqueue S to worklist
This formulation ensures that updates propagate only when necessary, adapting to the structure of the . In terms of efficiency, the worklist approach processes each edge at most a number of times proportional to the height of the , but for practical domains like bit vectors, the is O(E \times |D|), where E is the number of edges in the and |D| is the size of the abstract domain per (reflecting the of meet and operations). This is superior to naive over all nodes in each pass, which would be O(N \times E \times |D|) in the worst case for N nodes. Variants of the worklist algorithm address different strategies and update sparsity. Node-first queuing processes entire nodes upon dequeuing, computing outputs and propagating to all successors, while edge-first queuing treats pending updates as edge-specific events, potentially reducing redundant computations in sparse graphs by queuing only affected incoming edges. Additionally, naive worklists re-enqueue nodes without tracking changes, leading to repeated processing, whereas deletion worklists maintain auxiliary structures (such as sets on edges) to remove nodes from the queue once their states stabilize, enabling incremental updates and achieving near-linear complexity for certain sparse scenarios despite overhead in small domains.

Initialization Strategies

In data-flow analysis, initialization strategies establish the starting values for data-flow facts at entry and exit points, as well as at interior program points, while node ordering determines the sequence of updates during iterative computation. These choices directly influence the efficiency of reaching the fixed point by providing a sound starting approximation that aligns with the analysis direction and lattice structure. For computing the least fixed point, initialize to the bottom element \bot everywhere, with boundary conditions applied (e.g., in[entry] = \bot for forward; out[exit] = \bot for backward). This allows upward propagation via joins for both forward and backward analyses. Note that for analyses requiring the greatest fixed point (e.g., certain must-analyses), initialization to \top and downward meets may be used instead. Node ordering further optimizes the process by approximating a topological traversal of the . In forward analyses, reverse postorder—derived from a depth-first traversal where nodes are numbered in decreasing finishing times—prioritizes processing a node's predecessors before the node itself, thereby minimizing redundant propagations across forward edges. In backward analyses, postorder reverses this priority, processing successors first to efficiently propagate information upstream. Precomputing successors and predecessors, known as tabulation, constructs adjacency lists for the prior to iteration, enabling constant-time lookups for merging data from predecessors or distributing to successors without repeated graph traversals. This step enhances overall runtime by decoupling structure computation from the fixed-point solver. The combined impact of these strategies substantially reduces the iteration count required for convergence. In directed acyclic graphs, topological ordering (a special case of reverse postorder for forward analyses) achieves the solution in a single pass, as information flows without cycles. In cyclic graphs typical of real programs, these methods limit passes to roughly the loop nesting depth plus one or two, with Knuth's study indicating an average loop nesting depth of 2.75 across programs from the 1970s.

Illustrative Examples

Forward Data Flow: Reaching Definitions

Reaching definitions analysis is a forward data-flow problem that determines, for each point, the set of variable definitions that may reach it along some execution path without being overwritten. A definition d of variable v reaches a point p if there exists a path from the site of d to p such that no other definition of v occurs on that path. This analysis supports optimizations like and constant propagation by identifying potentially relevant prior assignments. The data-flow equations for reaching definitions treat definitions as elements in a set lattice, with as the . For a B, \textit{gen}[B] is the set of definitions generated within B, and \textit{kill}[B] is the set of definitions of the same variables that are overwritten within B. The input set to B is the of the output sets from its predecessors: \textit{in}[B] = \bigcup_{p \in \textit{pred}(B)} \textit{out} The output set from B combines local definitions with those incoming definitions not killed: \textit{out}[B] = \textit{gen}[B] \cup (\textit{in}[B] \setminus \textit{kill}[B]) These equations propagate information forward from the program entry, where \textit{in}[\textit{entry}] = \emptyset. Consider a simple program with a loop and conditional branch to illustrate the control-flow graph (CFG) and computation:
1: x = 1     // d1
2: y = 2     // d2
3: if (x > 0) goto 6
4: x = 3     // d4
5: goto 7
6: y = 4     // d6
7: print x
8: if (y > 0) goto 3
The CFG blocks are:
  • (lines 1-2): gen = {d1, d2}, kill = ∅
  • (line 3): gen = ∅, kill = ∅
  • B3 (lines 4-5): gen = {d4}, kill = {d1}
  • (line 6): gen = {d6}, kill = {d2}
  • B5 (lines 7-8): gen = ∅, kill = ∅
    Edges: B1 → B2; B2 → B3; B2 → B4; B3 → B5; B4 → B5; B5 → B2 ( back); B5 → exit. This setup highlights and .
The solution is computed iteratively using a until a fixed point is reached, starting with all out sets empty. A initially contains all blocks; process a block by updating its in from predecessors' out, then compute its out, and add successors to the worklist if changed. Process B1: in[B1] = ∅, out[B1] = {d1, d2}. Propagate to B2.
Process B2: in[B2] = {d1, d2}, out[B2] = {d1, d2}. Propagate to B3, B4.
Process B3: in[B3] = {d1, d2}, out[B3] = {d2, d4}. Propagate to B5.
Process B4: in[B4] = {d1, d2}, out[B4] = {d1, d6}. Propagate to B5.
Process B5: in[B5] = {d2, d4} ∪ {d1, d6} = {d1, d2, d4, d6}, out[B5] = {d1, d2, d4, d6}. Propagate to B2.
Reprocess B2: in[B2] = {d1, d2} ∪ {d1, d2, d4, d6} = {d1, d2, d4, d6}, out[B2] = {d1, d2, d4, d6}. Propagate to B3, B4.
Reprocess B3: in[B3] = {d1, d2, d4, d6}, out[B3] = {d2, d4, d6}. Propagate to B5 (changed).
Reprocess B4: in[B4] = {d1, d2, d4, d6}, out[B4] = {d1, d4, d6}. Propagate to B5 (changed).
Reprocess B5: in[B5] = {d2, d4, d6} ∪ {d1, d4, d6} = {d1, d2, d4, d6} (no change). No further changes.
At the print x in B5, the reaching definitions for x are {d1, d4} (d1 via the branch to B4, d4 via the branch to B3). The loop causes multiple passes, propagating definitions back via B5 → B2 until convergence.

Backward Data Flow: Live Variables

Live variable analysis is a classic backward data-flow problem in compiler optimization, aimed at identifying which variables hold useful values at each program point for purposes such as register allocation and dead code elimination. A variable is live at a program point if there exists a path from that point to a use of the variable before any redefinition occurs along that path. This analysis proceeds in the reverse direction of control flow, starting from program exits where no variables are live and propagating liveness information upward through predecessors. For a B, the set \gen[B] contains the variables used within B, while the kill set \kill[B] includes variables defined in B. The core data-flow equations capture how liveness flows backward: \in[B] = (\out[B] \setminus \kill[B]) \cup \gen[B] \out[B] = \bigcup_{s \in \succ(B)} \in Here, \in[B] represents the set of live variables entering B, \out[B] the set exiting B, and \succ(B) the successors of B; the boundary condition sets \out[\text{exit}] = \emptyset. These equations reflect the backward nature: liveness at the exit of a block depends on liveness entering its successors, and uses in a block propagate liveness into it unless killed by definitions. To demonstrate, consider a simple control-flow graph (CFG) with four basic blocks, representing a conditional assignment followed by an increment and loop check:
  • Block 1: t1 = a[i]; t2 = b[i] (entry point)
  • Block 2: if (t1 <= t2) goto 4 (branches to 3 or 4)
  • Block 3: t1 = t2
  • Block 4: b[i] = t1; i = i + 1; if (i < 10) goto 1 (exit via implicit return after loop)
The uses and definitions are: \gen{{grok:render&&&type=render_inline_citation&&&citation_id=1&&&citation_type=wikipedia}} = \{a, b, i\}, \kill{{grok:render&&&type=render_inline_citation&&&citation_id=1&&&citation_type=wikipedia}} = \{t1, t2\}; \gen{{grok:render&&&type=render_inline_citation&&&citation_id=2&&&citation_type=wikipedia}} = \{t1, t2\}, \kill{{grok:render&&&type=render_inline_citation&&&citation_id=2&&&citation_type=wikipedia}} = \emptyset; \gen{{grok:render&&&type=render_inline_citation&&&citation_id=3&&&citation_type=wikipedia}} = \{t2\}, \kill{{grok:render&&&type=render_inline_citation&&&citation_id=3&&&citation_type=wikipedia}} = \{t1\}; \gen{{grok:render&&&type=render_inline_citation&&&citation_id=4&&&citation_type=wikipedia}} = \{i, t1\}, \kill{{grok:render&&&type=render_inline_citation&&&citation_id=4&&&citation_type=wikipedia}} = \{b, i\}. Initialization assumes propagation from the exit where out sets start empty. The iterative computation begins backward from the exit. Initially assuming out = ∅ (ignoring loop initially), in = {i, t1}. Then out = in = {i, t1}, in = {t2} ∪ ({i, t1} \ {t1}) = {i, t2}. out = in ∪ in = {i, t1, t2}, in = {t1, t2} ∪ {i, t1, t2} = {i, t1, t2}. out = in = {i, t1, t2}, in = {a, b, i} ∪ ({i, t1, t2} \ {t1, t2}) = {a, b, i}. Subsequent iterations account for the back (out = in ∪ ∅ = {a, b, i}), updating in = {i, t1} ∪ ({a, b, i} \ {b, i}) = {a, i, t1}, and propagating: in = {a, i, t2}, out = {a, i, t1, t2}, in = {a, i, t1, t2}, out = {a, i, t1, t2}, in = {a, b, i} (no change for t1, t2 as killed in B1). Convergence shows i live throughout the , a and b live into B1 due to uses there, and t1, t2 live across the branch in B2. This backward propagation highlights how uses in later blocks (e.g., in B4) make variables like t1 and i live earlier, enabling decisions like spilling registers for live temporaries.

Extensions and Variations

Alternative Algorithms

For reducible control flow graphs (CFGs), exact solutions to data-flow problems can be computed in linear time using non-iterative methods such as interval analysis, which decomposes the graph into nested intervals based on dominator relationships and propagates information top-down through the . This approach, developed by Allen and Cocke, identifies structured regions (intervals) where each interval has a single entry point, allowing data-flow facts to be computed once per interval and inherited by sub-intervals without repeated iteration over cycles. By exploiting the reducibility property—where back edges connect to interval headers— the method avoids the quadratic or worse complexity of general iterative algorithms, achieving O(E) time where E is the number of edges, and provides precise results equivalent to the meet-over-all-paths solution for distributive problems like reaching definitions. Path expressions offer another exact method for reducible CFGs, representing paths from the entry as expressions over labels to solve forward or backward data-flow equations symbolically. Tarjan's algorithm decomposes the graph using the dominator tree, computes path expressions within strongly connected components via on interval matrices, and combines them bottom-up, yielding a solution in O(m α(m,n)) time for m edges and n s, where α is the inverse —nearly linear in practice. This technique directly encodes transfer functions in the expressions, enabling efficient evaluation of data-flow facts at any without enumerating all paths, and is particularly effective for analyses like constant propagation in structured code. Chaotic iteration variants improve upon the standard fixed-point computation by employing non-deterministic or randomized node ordering in the worklist, potentially accelerating for certain lattices and graph topologies. Introduced by Cousot and Cousot, chaotic selects an arbitrary sequence of nodes for updating data-flow equations until stabilization. While guaranteed to reach the least fixed point regardless of order for frameworks, these variants are practical enhancements for large-scale analyses where count dominates runtime. Symbolic methods leverage binary decision diagrams (BDDs) to represent and manipulate large finite domains compactly, avoiding explicit enumeration in iterative data-flow solvers. Whaley et al. integrate BDDs with rules to model relations like points-to sets, where operations such as and correspond to graph propagation, enabling fixpoint computation in time proportional to BDD node count rather than domain size. For instance, in context-sensitive pointer analysis, BDDs compress exponential state spaces into compact representations, supporting scalable solutions for non-bitvector domains like in Java programs. Hybrid approaches merge iterative data-flow with on-demand constraint solving to enable demand-driven analysis, computing facts only for queried program points rather than exhaustively. In SUPA, et al. build a sparse value-flow via initial , then apply backward queries with strong updates—treating constraints as filters—to refine results within a , achieving 97% of full flow-sensitive precision at 0.18 seconds per query on C/C++ benchmarks. This combination reduces spurious facts by iteratively solving def-use constraints, making it suitable for targeted applications like virtual call resolution while maintaining scalability for large codebases.

Bit Vector Optimizations

Bit vector optimizations exploit the finite domain nature of many data-flow analyses, where sets of elements—such as program variables, definitions, or expressions—can be represented compactly using bits, enabling hardware-accelerated operations for efficiency. In this approach, each potential element in the domain is assigned a unique bit position in a , where a set bit indicates presence and a cleared bit indicates absence; the is typically partitioned into fixed-width words (e.g., 32 or 64 bits) to leverage processor-level parallelism in bitwise instructions. This representation is particularly suited to gen-kill style analyses, where the elements are subsets of a finite , allowing the entire analysis to operate on these compact structures rather than explicit lists or sets. Transfer functions in bit vector analyses are implemented using predefined masks for generation () and killing (kill) sets, which are themselves bit vectors marking the elements added or removed by a or . For a forward analysis like reaching definitions, the out-set for a block is computed as the bitwise OR of the in-set with the gen mask, followed by a bitwise AND with the complement of the kill mask to remove invalidated elements; similarly, joins across predecessors use bitwise OR to the incoming sets. These operations—OR for meet ( in forward flows), AND for , and XOR or AND-NOT for differences—are executed in time per word, yielding significant speedups over or list-based manipulations, especially for domains with hundreds or thousands of elements. Backward analyses, such as live variables, adapt the same with reversed gen/kill roles and AND for joins. As noted in early formulations, this bit-parallel reduces the iterative fixed-point solver's per-iteration cost from (for n elements) to O(n/w), where w is the word size. A concrete example is the forward reaching definitions analysis, where the consists of definition sites (e.g., assignments to ), and the goal is to determine which reach each program point. Suppose a program has 64 definition sites, fitting neatly into a single 64-bit word; each bit represents whether a particular reaches the point. The gen set for a containing a d_i is a mask with only the i-th bit set, while the kill set includes bits for all definitions of the same as d_i. During , the in-vector for a is the OR of out-vectors from its predecessors, and the out-vector is then (in OR gen) AND (NOT kill), all via native CPU instructions like OR, AND, and NOT on the 64-bit the entire in a handful of cycles per . This bit-parallelism scales the analysis to larger programs without proportional slowdowns, as demonstrated in classical implementations where over thousands of completes in milliseconds. While effective, bit vector representations have limitations tied to their fixed : the size must be known statically and bounded, as dynamic resizing is inefficient, and very large domains (e.g., millions of ) require arrays of multiple words, increasing and operation counts linearly with the number of words. Operations remain fast but lose full parallelism beyond word size, though 64-bit architectures mitigate this for domains up to thousands of . To handle overflows or sparse sets, approaches sometimes complement bit vectors with sparse bit maps, but pure bit vectors excel in dense, medium-sized finite domains common to intraprocedural analyses. These optimizations are widely adopted in production compilers; for instance, the Compiler Collection () implements per-function bit vector data-flow analysis using this framework in its optimization passes, such as for , with a generic analyzer supporting custom gen/kill masks across versions like GCC 4.3 and later. Similarly, in the infrastructure, analyses like live variables in utilize llvm::BitVector to represent sets of registers or variables, enabling efficient propagation during and optimization, as seen in the core analysis libraries.

Interprocedural Frameworks (IFDS)

Interprocedural Finite Distributive (IFDS) problems represent a of data-flow analyses that extend intraprocedural techniques across boundaries while maintaining and . These problems are characterized by a finite of data-flow facts D, where the data-flow functions map powersets of D to powersets of D and distribute over the meet , typically or depending on the analysis direction. IFDS enables the modeling of interprocedural effects, such as parameter passing and return values, without exploding the state space for every possible call , thus avoiding the exponential cost of fully context-sensitive analyses in large programs. The IFDS framework models the program as an exploded supergraph G_{IP}^\#, where nodes are pairs consisting of a program point from the interprocedural and an from D \cup \{0\} (with 0 as a "" ). This captures call-return through "valid paths" that procedure nesting, using summary edges to represent the net effect of subroutines without unrolling the call . The core solving mechanism is the tabulation , a dynamic programming approach that computes reachable facts by propagating along the exploded supergraph's edges, ensuring efficiency in O(|E|D^3) time, where |E| is the number of edges in the supergraph. Distributive allow the composition of local and summary functions to be handled modularly, making IFDS applicable to a wide range of subset-based problems. A example is interprocedural reaching definitions, which determines the set of definitions that may reach a program point across procedure calls. In this , facts in D represent individual definitions, and functions propagate sets of reaching definitions through calls by incorporating callee summaries (e.g., definitions introduced or killed within a subroutine). For instance, at a call site, the framework computes the effect of the callee on caller state via exploded paths that align entry and exit points, enabling precise tracking of definitions like those for possibly uninitialized s without requiring full inlining. This avoids the imprecision of call-string approximations while scaling to real-world codebases. Developed in the mid-1990s by Reps, Horwitz, and Sagiv, the IFDS framework has influenced subsequent interprocedural analysis techniques and is implemented in tools such as , a Java optimization framework that leverages IFDS solvers for tasks like taint analysis and def-use chain construction. Its graph-reachability reduction has enabled efficient, context-sensitive analyses in production static analysis systems, with extensions appearing in post-1990s works to handle more complex domains.

Analysis Sensitivities

Flow Sensitivity

Flow-sensitive data flow analysis tracks the propagation of data facts along specific control flow paths in a program's control flow graph (CFG), accounting for the sequential order of statements and the impact of branches or loops on those facts. This approach distinguishes the effects of different execution paths, such as how an if-then-else construct alters the set of reaching definitions at a subsequent program point. In contrast, flow-insensitive analysis disregards path order, treating all statements as a commutative set and computing a single, conservative approximation for the entire procedure, which simplifies computation but sacrifices precision. Implementation of flow-sensitive analysis typically involves iterative propagation over the CFG using fixpoint algorithms. Starting from an initial state—such as empty sets for forward analyses like reaching definitions—the algorithm applies transfer functions to each node, merging information from predecessors (for forward ) or successors (for backward ) until convergence. Seminal procedures, such as those using interval partitioning of the CFG, process nodes in within reducible graphs to efficiently compute facts like live variables or available expressions at each point. Worklist-based variants prioritize updated nodes to reduce iterations, often representing facts with bit vectors for scalability. The primary trade-off of flow-sensitive analysis is its superior precision against increased computational cost. By maintaining separate data facts per program point, it can detect path-dependent behaviors—such as branch-specific liveness in conditional code—that flow-insensitive methods over-approximate, enabling optimizations like partial redundancy elimination where path context matters. However, this requires solving equations for every CFG and , leading to time and that scales with the number of program points (often in practice for flow-sensitive analyses), compared to the single-state computation of insensitive approaches (linear for simple cases but up to cubic for precise pointer analyses). Flow-sensitive analyses are particularly essential for intraprocedural optimizations in compilers, where precise path information enhances without excessive interprocedural overhead. They are routinely applied in tools for analyses like reaching definitions, where tracking path-specific propagations directly informs , though approximations are common in broader program scopes to balance precision and performance.

Context Sensitivity

Context-sensitive data-flow analysis enhances precision in interprocedural settings by tracking the calling context—such as the sequence of call sites or caller states—when propagating data-flow facts across procedure boundaries, in contrast to context-insensitive analysis, which computes a single, unified summary for each procedure regardless of how it is invoked. This approach, pioneered in the call-string method, tags data-flow facts with a history of recent call sites to distinguish invocations and avoid merging facts from unrelated contexts, thereby reducing spurious results in analyses like pointer aliasing or live variables. Key techniques for achieving context sensitivity include the full call-string approach, which maintains an unbounded history of calls but is generally impractical due to in state space, and bounded variants like k-limiting, which restrict the call-string depth to a fixed k to ensure termination at the cost of some precision loss. Selective context sensitivity applies full tracking only to critical call sites, such as those involving or virtual method invocations, while using insensitive summaries elsewhere to balance precision and efficiency; this is particularly useful in demand-driven analyses where computation focuses on queried elements. A representative example is context-sensitive points-to analysis, which differentiates pointer targets based on calling contexts to resolve virtual calls more accurately and eliminate spurious aliases that could arise from merging unrelated invocations. For instance, in object-oriented languages like , this prevents over-approximating method dispatch by considering the specific caller, leading to fewer false positives in optimizations or error detection. In modern applications, such analyses power precise refactoring and code inspection in integrated development environments (IDEs), as seen in IntelliJ IDEA's data-flow engine, which employs call-context sensitivity to trace variable states across invocations for features like and null-dereference warnings.

Object and Field Sensitivity

In data-flow analysis for object-oriented languages, object sensitivity enhances by distinguishing abstract representations of objects based on their allocation sites or other distinguishing locations, rather than treating all objects of the same type as indistinguishable (object-insensitive). This approach tracks flows separately for each unique allocation context, reducing over-approximations in analyses like points-to, where insensitive methods merge all instances of a into a single abstract object. Seminal work formalized parameterized object sensitivity, allowing the number of distinguished objects (k) to be tuned for a balance between and scalability in programs. Field sensitivity complements object sensitivity by modeling distinctions among an object's fields, treating accesses to different fields (e.g., obj.x versus obj.y) as separate data-flow paths, in contrast to field-insensitive analyses that treat all fields uniformly. This is particularly crucial in languages like , where field updates can alias unexpectedly if not distinguished. Analyses often employ hybrid strategies, combining strong updates (precise overwrites that eliminate prior values for a specific field) with weak updates (conservative may-overwrites for unknown cases), to maintain while improving in heap manipulations. A representative example appears in points-to analysis for , where an object-sensitive and field-sensitive approach significantly reduces false positives in aliasing queries compared to insensitive variants; for instance, it can separately track pointers to obj.fieldA and obj.fieldB allocated at different sites, avoiding spurious merges that lead to imprecise construction or . Empirical studies confirm that such sensitivities yield up to 10-20x fewer aliases in medium-sized benchmarks, enhancing downstream optimizations like devirtualization. However, these sensitivities pose scalability challenges in programs with large heaps, as the number of abstract objects and field combinations can explode exponentially, leading to high memory and time costs; for example, distinguishing thousands of allocation sites may require heuristics like context truncation or selective sensitivity. Modern extensions address this through frameworks like TVLA (Three-Valued Logic Analyzer), which uses to perform scalable, parametric shape analysis that inherently supports object- and field-sensitive abstractions for verification tasks in the 2020s, such as detecting heap errors in concurrent systems.

Common Analyses and Applications

Reaching Definitions and Constant Propagation

Reaching definitions analysis identifies the set of variable assignments that may reach a given program point along some execution path, serving as a foundational forward data-flow problem in optimization. This analysis computes, for each point p, the set \mathit{in} of definitions reaching p from its predecessors, using the equations \mathit{in} = \bigcup_{\hat{p} \in \mathit{pred}(p)} \mathit{out}[\hat{p}] and \mathit{out} = \mathit{gen} \cup (\mathit{in} \setminus \mathit{kill}), where \mathit{gen} includes definitions generated at p and \mathit{kill} includes those invalidated by redefinitions at p. In practice, reaching definitions enables by identifying assignments that do not reach any use of the variable, allowing their removal to reduce program size and execution time. Constant propagation extends reaching definitions by tracking definite constant values for variables across program points, formulated as a forward must-analysis to determine values on all paths. The analysis uses a for each v with elements \top (unknown), c (definite c), and \bot (non-), ordered as \top \geq c \geq \bot for all constants c, with meet operation \sqcap taking the greatest lower bound (e.g., intersecting constants yields the common value if identical, else \bot). The f_p for point p maps input information m (a function from variables to lattice elements) to output m', preserving values for unassigned variables and evaluating assignments: for v = e(w_1, \dots, w_k), if all m(w_i) are constants, set m'(v) to the computed constant; if any is \bot, set to \bot; otherwise, propagate the unknown or constant as appropriate. The flow equations are then \mathit{in} = \bigsqcap_{\hat{p} \in \mathit{pred}(p)} \mathit{out}[\hat{p}] and \mathit{out} = f_p(\mathit{in}), solved iteratively until fixed-point . Combining reaching definitions with constant propagation simplifies optimization by using propagated constants to refine reaching sets, enabling more precise and code transformation. For instance, consider the code snippet:
x = 5;
if (cond) {
  y = x * 2;
} else {
  y = x + 3;
}
z = y;
Reaching definitions initially shows the assignment to x reaches both branches. Constant propagation determines x is definitely 5 at both entry points, transforming the branches to y = 10; and y = 8;, but since y values differ, it sets y to \bot at the merge, preventing further propagation to z. This refines the reaching set for y, confirming the original x definition reaches z indirectly and eliminating any unreachable prior definitions of x. In compiler implementations, reaching definitions and constant propagation leverage bit vectors for efficiency, representing sets of definitions or lattice values as fixed-length bit strings where each bit corresponds to a potential definition or constant slot. Operations like (bitwise OR for may-analysis) and (bitwise AND for must-analysis) on these vectors enable fast iterative solving, with complexity scaling linearly in the number of program points for reducible control-flow graphs. This approach, integral to passes, has been adopted in production s since early frameworks.

Available Expressions and Loop-Invariant Code Motion

Available expressions analysis is a classic forward data-flow problem in compiler optimization that identifies expressions whose values are guaranteed to be computed prior to a given program point along every possible execution path, without subsequent modification of their operands. This "must" analysis uses intersection as the meet operation to ensure the property holds universally, enabling optimizations like by avoiding recomputation of redundant expressions. The framework was introduced as part of the general data-flow analysis by Kildall in 1973. The analysis operates over the (CFG), where each node represents a , and information flows forward from entry to exit. For a basic block B, the sets are defined as follows: \mathrm{gen}(B) contains all expressions computed exactly once in B (assuming no side effects or multiple computations), while \mathrm{kill}(B) includes all expressions that contain at least one variable redefined in B, as such redefinitions invalidate prior computations. The data-flow equations, solved iteratively until a fixed point is reached, are: \mathrm{in}(B) = \bigcap_{P \in \mathrm{pred}(B)} \mathrm{out}(P) \mathrm{out}(B) = \mathrm{gen}(B) \cup \left( \mathrm{in}(B) \setminus \mathrm{kill}(B) \right) with the initial condition \mathrm{in}(\mathrm{entry}) = \emptyset. These equations propagate availability forward, intersecting at join points to reflect the "must" requirement. Consider a simple example in a CFG with blocks where an expression x + y is computed in block A and potentially redefined later:
Block A: temp = x + y;  // gen = {x + y}, kill = empty for this expr
Block B: z = temp * 2;  // no gen/kill for x+y
Block C: x = 5;         // kill = all exprs with x, e.g., {x + y}
If all paths to B pass through A without killing x + y, then x + y is available at the entry to B, allowing reuse of temp instead of recomputing. This analysis complements forward analyses like constant propagation by focusing on composite expressions rather than single variables, enabling more aggressive optimizations when combined. Available expressions analysis directly supports (LICM), an optimization that hoists computations invariant across loop iterations outside the loop body to reduce execution time, particularly in performance-critical kernels. An expression qualifies as loop-invariant if it is available at the loop header (i.e., computed on all paths to the header without operand modification) and remains un-killed within the loop (no redefinitions of its operands). The analysis confirms availability at the preheader, ensuring the hoisted value is correct for all iterations. This technique is especially effective in numerical computations where repeated invariant expressions dominate runtime. For instance, consider the following loop:
a = ...;
b = ...;
while (condition) {
  x = a + b;  // expression a + b
  ... use x ...
}
print x;
If available expressions shows a + b available at the loop header and no statements in the loop redefine a or b, the optimizer rewrites it as:
a = ...;
b = ...;
x = a + b;  // hoisted
while (condition) {
  ... use x ...
}
print x;
This eliminates redundant additions per iteration. While LICM often integrates with other analyses for safety, available expressions provides the core mechanism for detecting hoistable computations, enhancing code efficiency without altering semantics.

Uses in Compilers and Static Analysis Tools

Data-flow analysis is integral to compiler optimizations, enabling transformations that improve code efficiency and performance. Reaching definitions analysis identifies assignments that propagate to uses, facilitating by removing computations whose results are never accessed, as implemented in optimizing compilers like and . Liveness analysis determines variables that hold values needed in the future, supporting by allowing multiple non-overlapping live variables to share registers, thereby reducing memory traffic and execution time. Available expressions analysis detects redundant computations across the program, enabling partial and to avoid recomputing identical expressions. Static analysis tools leverage data-flow analysis to detect bugs and security issues without executing the code. SpotBugs, the successor to FindBugs, applies forward and backward data-flow analyses on Java bytecode to uncover patterns like unclosed resources and infinite recursive loops, enhancing code reliability in large-scale Java applications. The Clang Static Analyzer for C and C++ integrates data-flow techniques with symbolic execution to trace potential defects, such as use-after-free errors and null pointer dereferences, providing path-sensitive diagnostics for developers. Interprocedural frameworks extend data-flow analysis for security, particularly in taint tracking, which propagates labels on untrusted inputs to identify flows to sensitive sinks like SQL queries, mitigating vulnerabilities in applications. Beyond compilers, data-flow analysis supports program verification by linking to frameworks, where lattice-based approximations prove properties like absence of runtime errors; for example, has been used to formalize data-flow analyses. In integrated development environments (IDEs), it powers features like by inferring data flows to predict types and suggest contextually relevant completions, as in IntelliJ IDEA's analysis engine. Modern extensions address scalability and integration challenges. Cloud-based platforms distribute interprocedural data-flow computations across clusters to analyze massive codebases, achieving linear speedups for applications like auditing. AI-assisted tools in the 2020s, such as , enhance developer productivity with suggestions informed by patterns that implicitly rely on flow-aware static analyses for accuracy and .

References

  1. [1]
    [PDF] Lecture 2 Introduction to Data Flow Analysis - SUIF
    Introduction to Data Flow Analysis. I. Introduction. II. Example: Reaching definition analysis. III. Example: Liveness analysis. IV. A General Framework. ( ...
  2. [2]
    [PDF] Data Flow Analysis - Stanford InfoLab
    We can use DFA to check a program for uses of a variable X where there is a path in the flow graph to that use along which X might not be defined. It is ...
  3. [3]
    Data-Flow Analysis | Request PDF - ResearchGate
    Data-flow analysis is the classic technique for compile-time program analysis. It allows the compiler to reason about the runtime flow of values in the program.
  4. [4]
    Data flow analysis: an informal introduction - Clang
    Data flow analysis is a static analysis technique that proves facts about a program or its fragment. It can make conclusions about all paths through the program ...
  5. [5]
    [PDF] Representation and Analysis of Software 1 Introduction 2 Control ...
    A control flow graph1 (CFG) is a directed graph in which each node represents a basic block and each edge represents the flow of control between basic blocks. ...
  6. [6]
    DATAFLOW ANALYSIS - cs.wisc.edu
    Dataflow analysis is usually performed on the program's control-flow graph (CFG); the goal is to associate with each program component (each node of the CFG) ...Solving a set of equations · Iterative algorithms · The Lattice Model of...<|separator|>
  7. [7]
    [PDF] Lecture Notes on Dataflow Analysis
    Mar 16, 2023 · An important optimization in a compiler is dead code elimination which removes un- needed instructions from the program. Even if the ...
  8. [8]
    [PDF] CMSC 430 Introduction to Compilers Data Flow Analysis
    • An analysis that takes the whole program into account is whole program. • Note: global analysis means “more than one basic block,” but still within a function.
  9. [9]
    Control flow analysis | ACM SIGPLAN Notices
    The underlying motivation in all the different types of control flow analysis is the need to codify the flow relationships in the program. The codification may ...
  10. [10]
    [PDF] Control Flow Analysis
    Frances E. Allen. IBM CORPORATION. INTRODUCTION. Any static, global analysis ... Allen. CONTROL FLOW ANALYSIS. 15-. Page 16. SIGPLAN Notices. 1970 July. As a ...
  11. [11]
    Remembering Frances E. Allen - IBM Research
    Aug 5, 2020 · Fran's 1970 paper on Control Flow analysis introduced the notion of “intervals” and node dominance relations, important improvements over the ...<|separator|>
  12. [12]
    A Unified Approach to Global Program Optimization
    The purpose here is to describe a single program flow analysis algorithm which extends all of these straight-line optimizing techniques to in- clude branching.
  13. [13]
    Abstract interpretation: a unified lattice model for static analysis of ...
    ... Abstract interpretation: a unified lattice model for static analysis of programs by construction or approximation of fixpoints. Authors: Patrick Cousot.
  14. [14]
    A unified approach to global program optimization
    A unified approach to global program optimization. Article. Free access. Share on. A unified approach to global program optimization. Author: Gary A. Kildall.
  15. [15]
    Monotone data flow analysis frameworks | Acta Informatica
    We consider a generalization of Kildall's lattice theoretic approach to data flow analysis, which we call monotone data flow analysis frameworks.
  16. [16]
    [PDF] Data-Flow Analysis - Stony Brook Computer Science
    Tarski-Knaster Theorem: Given a complete lattice L and a monotone function G : L → L, the fixed points of G form a complete lattice. Consequently, there exist ...
  17. [17]
    Lecture 7: September 25, 2007 - SUIF Compiler
    Sep 25, 2007 · Knaster-Tarski theorem: If f is a monotone function on a complete lattice L, then the set of fixedpoints of f in L is also a complete lattice.
  18. [18]
    [PDF] Foundations of Data Flow Analysis - CSE - IIT Kanpur
    Knaster-Tarski Fixed Point Theorem. ▷ Let f be a monotonic function on a complete lattice. (S,기,V). Define. Then,. Page 114. Knaster-Tarski Fixed Point Theorem.Missing: convergence | Show results with:convergence
  19. [19]
    [PDF] CS 4120/5120 Lecture 22 Dataflow analysis frameworks
    Mar 19, 2018 · It works well to have the initial ordering of nodes be reverse postorder, so that in the case of a forward analysis, information starts from the ...
  20. [20]
    [PDF] Lecture Notes: A Dataflow Analysis Framework for WHILE3ADDR
    If our flow functions are well-behaved (technically, if they are monotone, as discussed in a future lecture) then each time the flow function runs on a given ...
  21. [21]
  22. [22]
    [PDF] A program data flow analysis procedure - A.M. Turing Award
    The data flow analysis procedure given in this paper determines, among other things, the set of definitions,. R~, that reach each node in the control flow graph ...
  23. [23]
    [PDF] Reaching definition analysis II Example - SUIF Compiler
    Data flow analysis abstraction: • For each static point in the program: combines information of all the dynamic instances of the same program point.<|separator|>
  24. [24]
    [PDF] CS153: Compilers Lecture 20: Dataflow analysis - Harvard University
    Uses and Definitions. •Every instruction/statement uses some set of variables. •i.e., reads from them. •Every instruction/statement defines some set of ...
  25. [25]
    [PDF] Copy Propagation KILL Sets GEN Sets Live Variables
    We call a variable X live at point p if there is some path from p along which the value of X may be used before redefinition. Otherwise, X is dead. 1. Page 2 ...
  26. [26]
    [PDF] FAST ALGORITHMS FOR SOLVING PATH PROBLEMS by
    The paper describes a method to compute path expressions by dividing a graph into components, using Gaussian elimination, and combining solutions. A simplified ...
  27. [27]
    P. Cousot & R. Cousot, Fixed Point Approach to the Approximate ...
    For continuous equations, the exact solution can be constructed iteratively by Jacobi's successive approximations, but in practice any chaotic iteration method ...
  28. [28]
    [PDF] Using Datalog with Binary Decision Diagrams for Program Analysis
    BDD op- erations take time proportional to the size of the data structure, not the number of tuples in a relation, which leads to fast execution times.
  29. [29]
    [PDF] Value-Flow-Based Demand-Driven Pointer Analysis for C and C++
    Abstract—We present SUPA, a value-flow-based demand-driven flow- and context-sensitive pointer analysis with strong updates for C and. C++ programs.
  30. [30]
    [PDF] A SURVEY OF DATA FLOW ANALYSIS TECHNIQUES - Ken Kennedy
    This is the goal of compiler optimization. Note that optimization is not intended to compensate for poor programming, but rather to reduce the inefficiencies in ...
  31. [31]
    [PDF] A Generalised Theory of Bit Vector Data Flow Analysis - CSE IITB
    A unified approach to global program optimization. In Proceedings of the. 1st Annual ACM Symposium on Principles of Programming Languages, pages 194–206 ...Missing: Frances | Show results with:Frances
  32. [32]
    Implementing Data Flow Analysis in GCC | 13 | Uda
    This chapter presents a generic data flow analyzer for per function bit vector data flow analysis in GNU Compiler Collection (GCC) 4.3.0.
  33. [33]
    lib/Analysis/LiveVariables.cpp Source File - Clang
    3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4// See https://llvm.org/LICENSE.txt for license information.
  34. [34]
    [PDF] Precise Interprocedural Dataflow Analysis via Graph Reachability
    “chaotic iteration with needed information only” [10] or the. “minimal ... and the algorithm of Cousot and Cousot [10]. However, for general IFDS ...
  35. [35]
    Inter-procedural data-flow analysis with IFDS/IDE and Soot
    The IFDS and IDE frameworks by Reps, Horwitz and Sagiv are two general frameworks for the inter-procedural analysis of data-flow problems with distributive ...
  36. [36]
    [PDF] The Effects of the Precision of Pointer Analysis *
    What are the relative benefits of flow-sensitive and flow-insensitive pointer analy- ses? When will the additional precision of a flow-sensitive analysis lead ...<|control11|><|separator|>
  37. [37]
    [PDF] Interprocedural data flow analysis in Soot using value contexts
    Soot currently supports interprocedural analysis of Java pro- · grams using ... Hence this · problem does not fit in the IFDS/IDE framework, but such flow.
  38. [38]
    [PDF] Context-sensitive points-to analysis: is it worth it?* - PLG
    The algorithm essentially performs a k-CFA analysis, where k is the maximum call depth in the original call graph; k is always much higher than 2. The number of ...
  39. [39]
    Analyze data flow | IntelliJ IDEA Documentation - JetBrains
    Oct 10, 2024 · IntelliJ IDEA dataflow analysis enables you to trace all the possible data transformations without running the program.
  40. [40]
    Data flow analysis | CLion Documentation - JetBrains
    Feb 11, 2024 · Data flow analysis in CLion is call-context sensitive: each function is analysed for each call site separately,. parameters and return values ...
  41. [41]
    [PDF] Parameterized Object Sensitivity for Points-to Analysis for Java
    In this paper we present object sensitivity, a new form of context sensitivity for flow- insensitive points-to analysis for Java. The key idea of our approach ...<|control11|><|separator|>
  42. [42]
    [PDF] Efficient Field-Sensitive Pointer Analysis for C
    The focus of this paper is in extending our system to be field-sensitive. In fact, there ex- ist several field-sensitive pointer analyses for Java, which are.Missing: seminal | Show results with:seminal
  43. [43]
    [PDF] Pick Your Contexts Well: Understanding Object-Sensitivity
    Abstract. Object-sensitivity has emerged as an excellent context abstraction for points-to analysis in object-oriented languages. Despite its prac-.
  44. [44]
    [PDF] Shape Analysis
    Three-valued logic relies on basic user-defined shape predicates (such as local points-to predicates, global reachability predicates expresses by transitive ...
  45. [45]
  46. [46]
  47. [47]
    [PDF] Lecture 4: Available expression analysis
    Available expression analysis. Available expressions is a forwards data-flow analysis: information from past instructions must be propagated forwards through ...
  48. [48]
    [PDF] Lecture 7: Redundancy elimination
    Loop-invariant code motion recognises these redundant computations and moves such expressions outside of loop bodies so that they are only evaluated once.