A Flow-Sensitive Approach for Steensgaard’s Pointer Analysis

José Wesley de Souza Magalhães Daniel Mendes Barbosa Federal University of Viçosa Federal University of Viçosa Florestal, Minas Gerais, Brazil Florestal, Minas Gerais, Brazil [email protected] [email protected]

ABSTRACT pointer analysis is utilizing flow-sensitivity, which has been consid- Pointer analysis is a very useful technique to help make ered in a larger number of works than in the past. Many of these safe optimizations in codes, determining which memory locations flow-sensitive pointer analysis in recent works utilize inclusion- every pointer may points to at the execution of a program. One of based analysis, as know as Andersen’s style[2], a iterative solution 3 the main characteristics of pointer analysis that increases precision that is more expensive in terms of processing, having a O(n ) com- is the flow-sensitivity. Flow-sensitive pointer analysis are more plexity. precise than flow-insensitive ones, because they respect the control The performance is also an important aspect in pointer analysis, flow of a program and computes the points-to relation foreach since we’re treating large programs with many lines of code. In or- program point. However, this aspect makes flow-sensitive pointer der to achieve better results in terms of performance, our algorithm analysis more expensive and inefficient in very large programs. was implemented using equality-based, or Steensgaard’s pointer In this work, we implemented a Steensgaard’s style pointer anal- analysis, that is known faster [15]. Steensgaard’s pointer analysis ysis in a flow-sensitive approach, mixing the efficiency of Steens- executes in almost linear time without a large increase in resources gaard’s algorithm, that executes in almost linear time, with the consumption and memory usage[9]. precision of flow-sensitivity to achieve better results, keeping the However, the Steensgaard’s pointer analysis doesn’t generate best features of each aspect. We evaluate our analysis in open- very precise solutions because it is originally a flow-insensitive algo- source benchmarks and achieve better performance in comparison rithm [15]. A determining characteristic for this imprecision of the with an original Steensgaard’s algorithm and another flow-sensitive solutions is the merge of equivalent nodes. When a pointer changes analysis. to point to another memory location, the original Steensgaard’s algorithm merge the nodes relative to the old and the new locations CCS CONCEPTS pointed by this pointer into a single node. Besides that, this new unified node must point to wherever both of original nodes pointed, • Theory of computation → Program analysis; which causes the merge to be propagated to all subsequent points- to relations [12]. This propagation leads to incorrect relations in KEYWORDS the final graph, and the analysis may conclude, e.g, that pointers Flow-Sensitivity, Steensgaard’s Pointer Analysis, LLVM that are never assigned in the source code have some relation. The remainder of the paper is organized as follows. Section 2 brings some information necessary for a better understanding of 1 INTRODUCTION Steensgaard’s pointer analysis, flow-sensitivity and LLVM. Section Compilers always face difficulties to analyze and handle complex 3 presents related work. In Section 4 we detail our flow-sensitive codes that make massive use of pointers. The main reason of these algorithm. Section 5 discusses the experimental results and Section difficulties is the fact that it’s not possible to know the memory 6 concludes. locations acessed by pointers by just analyzing the statements in source code [9]. It’s important to know these memory locations 2 BACKGROUND to perform safe optimizations, such as and This section provides important background information useful for error detection [5]. Eliminate code without knowing which memory understanding the rest of the paper. First, we present the Steens- regions are acessed through that code could lead to loss of valuable gaard’s algorithm, then we describe the basics of flow-sensitivity in information, and consequently malfunctioning of the program. the context of pointer analysis, and finally we described LLVM, the Pointer analysis is a technique that consists in statically deter- infrastructure used in this work, focusing on the LLVM mine which memory locations each pointer may points to at exe- internal representation along with single static assignment (SSA). cution time, building a points-to graph containing the pointers and theirs relations, also called points-to sets. Achieve precise results using pointer analysis is a complex process and it takes too long to 2.1 Steensgaard’s Pointer Analysis execute as the size of programs increases, and besides that, static The Steensgaard’s pointer analysis [15] is an algorithm that handle a analysis are undecidable [13][15]. The recent works in area [7][8] constraint system to build the points-to sets and the points-to graph. [13][17][16] aim to increase the precision and speed of analysis, The considered constraints are the same addressed in Andersen’s but keeping it scalable to very large programs. pointer analysis and consists of the following four statements [13]. The precision of a pointer analysis is important to ensure the accuracy of the solution, however high precision is a NP-Hard prob- • x=&y (Address-of ): the pointer x is assigned the address of lem [6]. Nowadays the most efficient way to increase precision of variable y. • x=y (Copy): pointer variable y is copied over to pointer vari- able x. It means that the x will points to where y points to. • x=*y (Load): for each variable v that y may points to, x will point to where v points to. • *x=y (Store): for each variable v that x may points to, v will now points to where y points to. For each statement listed above, Steensgaard’s algorithm will update the points-to set of each pointer assuming equivalence in both sides of the statement [12]. This means that every time that a constraint is treated, the points-to set updated are admittedly equal to the source points-to set, and not a subset of them. This increases the analysis’s speed, and is efficiently implemented using the Union-Find algorithm [4]. The table below shows the effects of (a) (b) each statements on the points-to sets involved. We use the abbrevi- ation p for points-to set.

Table 1: Constraint System for Steensgaard’s Pointer Analy- sis

Statement Constraint Name Result x=&y Address-of address(y) ∈ p(x) x=y Copy p(x) = p(y) x=∗y Load v ∈ p(y), p(x) = p(v) ∗x=y Store ∀ v ∈ p(x), p(v) = p(y) ∀ The nodes in the points-to graph represent the pointers used in the code and it’s points-to set, and there’s an edge from a node x to a node y if (y) ∈ points-to(x). In Steensgaard’s graph, every node (c) (d) has one single outgoing edge, and this lead to the merge of the correspondent nodes when there’s a new assignment to a pointer. Figure 1: Example of source code in C and the correspondent As we mentioned earlier, this merge may produce incorrect results. graph generated by Steensgaard’s algorithm. In figure 1, the graph in 1b represents the points-to relation up to line 5 . The statement in line 8 causes a merge between y and x nodes 1c. Since we now have a single node (x,y), the propagation unifies p1 and p2 nodes in 1d. However, this graph tell us that p1 [7]. Thus, a flow-sensitive analysis achieve more precises results may point to y and that p2 may point to x, which affirmative isn’t than a flow-insensitive analysis, since the points-to set keeps the true. locations that a pointer exactly points to at each program point, The Steensgaard’s pointer analysis is interprocedural [15], which instead the locations that a pointer may points to at execution time. means the analysis takes the uses of pointers in function calls In figure 1, a flow-sensitive pointer analysis would tell usthat p3 into account, either as parameter or return type, and not just the points to x at line 5, and points to y at line 8, also maintaining the local scope of those pointers [12]. This characteristic increases correct relations involving the other pointers. the complexity of analysis, however increases precision as well. Steensgaard’s algorithm is originally flow-insensitive [15], but in Steensgaard’s pointer analysis is also context-insensitive and field- this work we implement a flow-sensitive version of Steensgaard’s insensitive, i.e., the analysis doesn’t consider the context when algorithm, increasing the precision of results and keeping high analyzing the target of a function call [12] and always makes the performance and scalability in very large programs. same decisions for all function calls, even if they have distinct behaviors. A pointer analysis field-insensitive doesn’t handle fields 2.3 LLVM IR from structure variables [12]. The LLVM (Low-Level-Virtual-Machine)[10] has its own interme- diate representation (IR) which is used for the analysis made in 2.2 Flow-Sensitivity this compilers infrastructure. This IR utilizes a partial SSA form Flow-Sensitivity is the pointer analysis’s property that refers to [8], that separate the variables in two classes. One containing the how the analysis respects the control flow of program and the variables that have their address exposed, which can be referenced order of statements. A flow-insensitive pointer analysis computes by pointers, the address-taken variables; and another one containing a unique solution for the whole program, in contrast to a flow- the variables which are never referenced by pointers, the top-level sensitive ones, which computes a solution to each program point variables. 2 In LLVM, the top-level variables are converted in the Single 3 RELATED WORK Static Assignment (SSA) form [3], and kept in registers, unlike Pointer analysis is actually one of the most studied topics in com- the address-taken variables, which are not in SSA form and are piler research. A good survey and starting point is the work by Hind kept in memory. This difference means that address-taken are only and Pioli [9], which describes the main features and algorithms accessible by LOAD or STORE instructions [8], and there can be of pointer analysis. In this section we focus on discuss the related more than one definition of them, since they are not in SSA form works on flow-sensitive pointer analysis, Steensgaard’s pointer [7]. In the LLVM is possible to iterate over each instruction of the analysis and analysis of C programs. IR, what allows a thorough analysis of the code as a whole. Lattner, Lenharth and Adve [11] introduce a new Steensgaard’s One of the most used forms to represent intermediate code is style based analysis called Data Structure Analysis, which is context- using basic blocks and control flow graphs (CFG), and LLVM makes sensitive and field-sensitive, but flow-insensitive. To obtain context- use of such form. A basic block is a sequence of instructions in sensitivity and field-sensitivity, the authors perform heap cloning which control flow only enters through the first instruction of to create representations for data structures and use such repre- the block and just leaves at the last instruction, i.e., there’s no sentations every time the analysis handle a instance of those data branching except at the end of the block. Basic blocks are the nodes structure. of control flow graphs, and there’s an edge from ablock X to a block Hardekopf and Lin’s work [7] evolves a semi-sparse flow-sensitive Y if the first instruction of Y can be executed right after the last pointer analysis which uses the partial SSA form to generate a instruction of X [1]. For a better understanding of our algorithm, sparse representation for some variables, thus eliminating irrele- it’s important to know the difference between must-blocks and vant nodes from the points-to graphs and increasing efficiency. In may-blocks. A must-block is a basic block that is to be reached [8], the authors proposed a staged flow-sensitive pointer analysis, for sure during execution of program. A may-block is basic block using a preliminary auxiliary analysis to make possible the spar- that is not necessarily reached, e.g, a basic block from a branching sity of the main flow-sensitive pointer analysis. Their analysis was statement. In figure 2, the nodes if.then and if.else are examples of able to perform in benchmarks with millions of line of codes. The may-blocks, whereas entry node is a must-block. sparsity in pointer analysis is also explored in [16], in which Sui, Di and Xue proposed a sparse flow-sensitive pointer analysis for multithreaded C programs. In [13] Nagaraj and Govindarajan work with flow-sensitive pointer analysis as a graph-rewriting problem that was solved by a parallel algorithm. They also used a staged pre-analysis to help building the points-to graph, and proposed their own system and set of rules to rewrite the graph. Their results show a scaling up to 2.6x for 8 threads using a efficient parallel framework. The work by Ye, Sui and Xue [17] proposes a region-based flow- sensitive pointer analysis, called SELFS, which performs in regions of the program instead individual statements. In this way the flow- sensitivity is maintained across the regions, whereas within a region a flow-insensitive analysis is performed. The authors argue that code regions must be handled in different ways due their distinct characteristics and that SELFS is as precise as traditional flow- sensitive analysis for almost all purposes. The partitioning of code in regions was made by a unification-based method that considers a region to be a piece of code containing only one load or one store constraints as shown in table 1, and builds a graph that way. The results shows that this approach obtained better results for large programs. Lin [12] presents a Steensgaard’s implementation for alias anal- ysis. It’s worth noting that aims to determine if two pointer may alias in execution time. Lin introduces many aspects of alias and pointer analysis and compares Steensgaard’s implementa- tion results in terms of precision, time and memory usage with two others analysis: one Andersen’s style and another Data Structure Analysis. Lin’s implementation is the only one Steensgaard’s style in LLVM, and maintains Steensgaard’s original aspects, i.e., the analysis is interprocedural and insensitive in terms of context, field Figure 2: Control Flow Graph for the source code in 1a and flow.

3 4 IMPLEMENTATION DETAILS there’s no node allocated to those temporaries. To work around We implemented our algorithm in C++ using the LLVM compiler this problem, we use a stack to keep references to the address- infrastructure [10]. taken variables and enable a flag indicating that there was anew Our algorithm is executed over LLVM’s IR files, therefore we stacking. When occurs a LOAD instruction we stack up the variable first compile the C source files generating the IR. For each function being loaded, so in the next instruction that use temporaries, our in source code, we first allocate a node for all variables found, and algorithm gets the variable names in stack, instead getting by the initialize the points-to set to null. As shown in figure 2, all the operands of instruction. variables are allocated before the other statements, even if there are However, there are two situations in which our algorithm must global variables. A node is composed by a dynamic array, which create temporary nodes. In IR, the access to array types is made in contains the represented variable to the node in the first position two instructions. The first instruction calculates the specific index and the points-to set of the represented variable in the following to be accessed in the array and stores the correspondent value in a positions; and a pointer to another node, to represent the edges of temporary; after that, the second instruction assigns the value to points-to graph. We’ll explain the need of a dynamic array later. To the pointer that access the array in source code. In this case, we represent our graph, we utilize a dictionary structure, in which the create a node to that temporary, by doing that we guarantee that variable name is used as key and the value object is a node. We can the search made in the assignment statement returns the correct use the variable name as key because LLVM creates a unique name access to the array and the correct points-to relation can be built. for all variables, even if that name is repeated in different scopes; The same occurs in access to built-in or user defined data structures and by doing that facilitates the search for a node, because we fields, and since our analysis is field-insensitive, we treat it thesame get variables names by simply analyze an instruction. We perform way. Right after the assignment statement, our algorithm delete the a linear pass through the IR module, iterating over the CFG and temporary node, in order to reduce graph size, and consequently all instructions in a module, this way we’re able to access every memory usage. instruction operand, and consequently every correspondent vari- As we mentioned earlier, Steensgaard’s pointer analysis is in- able in source code. This is equivalent to go through the program terprocedural [15], so we must handle function calls. This leads to statement by statement. the other situation in which we save temporary nodes, that occurs When occurs an assignment statement, we perform a search when there’s a function call with non-void return value. The IR in the graph using the variables involved in the statement. This stores this return value in a temporary and then assigns to the search returns references to nodes correspondent to the variables pointer which receives the return of function in source code. The on the left and right sides of the statement. Since we’re using a treatment to this situation is analogous to the one in array and data dictionary, a search on graph has O(1) cost. As we go through structures access, except for the fact we don’t save the temporary the code, the points-to relations are updated based in the four node in the points-to graph. We create another dictionary contain- constraints mentioned in table 1. To handle a Address-of constraint, ing information about all the non-void return functions found in we make the left-side node points to the right-side node; in case the source code, in this case, we use the function name as key and of a Copy constraint, we make the left-side node points to where the value object is a node for the return of the function. We couldn’t the right-side node points, by doing that, the points-to set of the save that kind of node in the points-to graph because it may be a left-side pointer becomes the points-to set of the right-side pointer. variable in source code with the same name as function, and this Flow-sensitivity facilitates handling Load and Store constraints, would overwrite the node associated with this name. because the points-to set have one single variable, and we just Our algorithm create points-to relation between the formal pa- have to make the node correspondent to the variable in points-to rameters and the actual parameters nodes treating a function call as set of right-side node points to where the left-side node points a Copy constraint, due the C’s passing parameters mechanism. By in case of Load constraints, and the opposite in Store constraints. doing that, we make the real parameter points to where the formal In our analysis, the only situation in which we have more than parameters points to, i.e. the actual parameter points-to set is now one variable in any points-to set occurs in branching statements in equal to the formal parameter points-to set. Since our analysis is which the branch condition is only known at runtime. We explain static, the points-to relation inside a function are already formed such situation in Subsection 4.1. when occurs the call for it. In case of a pointer receiving the return In order to apply flow-sensitivity, we create a new graph every of a function, the algorithm searches for the return of function node time occurs an assignment statement involving a pointer and up- in the dictionary of functions, instead in points-to graph, and assign dated the points-to relation up to that point of code in this new it to the points-to set of the pointer which receives the return. graph. By doing that, we keep the correct points-to sets for each point of code, and it’s possible to know to where the pointers points to in each line of code. Instead doing the original Steensgaard’s 4.1 Control flow in multiple paths merge, we overwrite the points-to sets, and since the nodes aren’t Although flow-sensitivity increases precision in a pointer analysis, unified, the propagation of merge is not necessary. there’s some situations where it’s not possible to know at compile Due the partial SSA form, LLVM loads the address-taken variables time which path the control flow will take. For example, when a into temporaries before use them in another instruction. Thus when variable that only takes on value inserted by program user is used as our algorithm analyze such instructions and get its operands to a condition in a branching statement or a ternary operator, we can’t perform the search, this would return a null reference because know if that condition will be true or false. All we already described

4 about our algorithm occurs in must-blocks, but this situation leads 3, in which pointer a points to b, thus we have a new graph at this to creation of may-blocks. point in 4b. We create nodes in an analogous way for all variables In a may-block, we cannot overwrite the points-to set when when analyzing main function, thus the points-to graph up to line 9 analyzing an assignment statement because we don’t know if that block will actually be executed at runtime. Overwriting the points- to set in a may-block would cause loss of reference and incorrect points-to relations depending on which path was taken. Thus, our analysis recognizes a may-block and perform a different handling of pointer assignments. When a pointer has an assigned value in more than one may- block coming from the same branching condition, we create a new node containing all the variables that are assigned to that pointer in (a) (b) those blocks. Because that a node contains a dynamic array, so we can add variables on it and make a points-to set with more than one variable. Thus we insert this node on graph using a concatenation of block and its function names as key. We’re able to do that since basic blocks names are unique inside the same function in LLVM’s IR [10]. This approach is similar to Steensgaard’s original merge, but we create a new node with more than a variable, and don’t merge the nodes correspondent to these variables. By keeping the original nodes, our algorithm preserves relations which includes some variables in the multiple variables node.

4.2 Example In this section we show an example of our analysis running and explained main differences from original Steensgaard’s algorithm. Consider the source code in figure 3, our algorithm builds the (c) (d) points-to graphs in figure 4.

1 int f(int* a){ 2 int b; 3 a=&b; 4 return b; 5 } 6 7 int main (){ 8 int* p1 , p2 , p3; 9 int x, y, z, cond; 10 p1 =&x; 11 p2 =&y; (e) (f) 12 p3=p1; 13 p1=f(p2); 14 scanf("%d",&cond); 15 if(cond >0) 16 p3 =&y; 17 else 18 p3 =&z; 19 *p2=z; 20 }

Figure 3: Example of source code in C.

In 4a, we first create nodes for all variables in function f. The first points-to relation is built from a Address-of constraint in line (g) (h) 5 Our experiments were conducted in a machine with a 2.5 GHz 4-core 64-bit processor and 4 GB of addressable memory. The ma- chine runs Ubuntu GNU/Linux 14.04 and has version 6.0.0 of LLVM [10] installed. We use a set of benchmarks of programs written in C language, composed by some known open-source projects and by some multi-source applications from the LLVM test suite. The benchmarks used are described in table 2. The values in third column correspond to the number of nodes in last generated graph, which is equivalent to the number of variables in the source code.

Table 2: Benchmarks utilized to validate the analysis

(i) (j) Benchmark Description Nodes in Graph Gzip Data Compressor 1071 Figure 4: Points-to graphs generated by our algorithm for Treecc Compiler Construction 1309 each point of code by running on example of figure 3. Lua Programming Language 2040 ex Text Processor 2104 SVN Source Control 3041 is shown in 4c. The assignment statements in lines 10 and 11 create the points-to relations in figures 4d and 4e respectively. There’s a Copy constraint in line 12, thus our algorithm makes p3 pointer Table 3 shows the analysis total time and memory usage for each points to where p1 points to, i.e., x in this case, resulting in the benchmark. To get the execution time results we used the LLVM’s graph in figure 4f. built-in time tracker, and we measure the memory consumption In line 13 occurs a call of function f. At this moment, LLVM’s IR using the Valgrind tool [14]. create a temporary called %call to store the return of the function on it, and we create a node for this temporary. Our algorithm builds Table 3: Time (in seconds) and memory usage (in Megabytes) the points-to relation between actual and formal parameters, thus of the analysis. p2 points to b, which is the points-to set of formal parameter a. We also make the pointer p1, which receives the return of f points to Benchmark Execution Time Memory Usage the %call node. The points-to graph for this point of code its in Gzip 0.2760 10.67 figure 4g. Right after that, our algorithm updates the points-to set Treecc 1.4320 29.05 of p1, making it to be equal to the points-to set of %call and deletes Lua 2.5360 45.97 the temporary node, resulting the graph in figure 4h. ex 1.6800 30.48 The branching statement in line 15 creates two may-blocks in SVN 3.0440 57.64 IR, corresponding to the true and false paths. We don’t know which path the control flow will take at this point, because the branching condition only will be known at runtime. There are assignment statements involving p3 in both may-blocks, thus we create a new . node containing y and z variables that its now the points-to set of 3 3 04 p3, as its shown in figure 4i. Finally, in line 19 the variable z is assigned to pointer p2. Since we 2.54 don’t merge the y and z nodes, we keep the original references and don’t build incorrect points-to relation. The points-to graph of this 2 point of code is shown in figure 4j. Our algorithm maintains a graph for each point of code, which demonstrate that flow-sensitivity 1.68 enables us to know the correct points-to relations for the whole 1.43

program. Execution Time 1 5 RESULTS In this section, we evaluate the performance of our analysis in terms 0.28 of time and memory usage and show our experimental results. Due 0 to the benchmarks utilized in this work, we compare some results Gzip Treecc Lua ex SVN with the obtained results from [8] and some with [12]. Some of the benchmarks utilized weren’t used in previous works, thus we Benchmarks weren’t able to compare results, but such results can be used as reference for comparisons in future works. Figure 5: Time results (in seconds) of the analysis.

6 As we can see in table 3, the execution time increases according to the input size, i.e, according to the number of pointers in source code, as shown in table 2. The figure 5 shows the time results in benchmarks ordered by their number of pointers, which makes clear the asymptotic behavior of the algorithm for the benchmarks used. The only exception of this behavior is the ex benchmark, which has more pointers than Lua, but our analysis takes less time to execute on it. This may be explained by the fact that Lua benchmark has more source files than ex. The time results shows that our analysis is fast for all benchmarks, taking seconds to execute even in the larger benchmarks, such as SVN.

60 57.64

50 (a) Comparison with [12]. (b) Comparison with [8]. 45.97 Figure 7: Time results comparison with a original Steens- 40 gard’s algorithm in 7a and with a flow-sensitive Andersen’s algorithm in 7b

30 29.05 30.48 Memory Usage 20 6 CONCLUSION The performance of an analysis has great impact in the compilation 10 10.67 process, both in terms of speed and accuracy of such process. Precise and fast pointer analysis remains a challenge for the compilers Gzip Treecc Lua ex SVN community. Benchmarks In this paper we shown that a equality-based, or Steensgaard’s pointer analysis can be implemented with flow-sensitivity without Figure 6: Memory usage results (in Megabytes) of the analy- losing its efficiency and scalability and producing more precise sis. results, however it consumes more memory than the original algo- rithm in order to achieve this precision. Our algorithm achieved fast results for all the used benchmarks and a reasonable memory usage The figure 6 shows the memory usage of our algorithm forthe for a flow-sensitive analysis. Also we demonstrate that our strategy used benchmarks. Flow-sensitive pointer analysis consumes more to build points-to relation and create new nodes in multiple paths memory than flow-insensitive ones due the fact of keeping one cases helps a lot keeping high precision. points-to graph for each point of code. Besides that, we use memory We currently use a dictionary structure to represent the points-to to store the functions dictionary and the nodes for may-blocks graph. For futures works our algorithm can be improved by using assignments, which makes the memory usage of our algorithm a most effective structure to represent the points-to graph, such as larger than other flow-sensitive analysis. The memory usage results Binary Decision Diagrams, in order to use less memory. were very similar to execution time ones, including the ex and Lua benchmarks and this occurs due the same reason we already REFERENCES explained. We’re not able to compare memory usage results because [1] Alfred V. Aho, Monica S. Lam, Ravi Sethi, and Jeffrey D. Ullman. 2006. Compil- the related works don’t measure that the same way we did. ers: Principles, Techniques, and Tools (2Nd Edition). Addison-Wesley Longman Figure 7 compares our analysis with two others in terms of exe- Publishing Co., Inc., Boston, MA, USA. cution time. In 7a we show a comparison between our algorithm [2] Lars Ole Andersen. 1994. Program analysis and specialization for the C program- ming language. Ph.D. Dissertation. University of Cophenhagen. with a original, i.e., flow-insensitive Steensgaard’s algorithm pre- [3] Ron Cytron, Jeanne Ferrante, Barry K Rosen, Mark N Wegman, and F Kenneth sented in [12] running on the Gzip benchmark; and we achieve a Zadeck. 1991. Efficiently Computing Static Single Assignment Form andthe Control Dependence Graph. ACM Transactions on Programming Languages and smaller execution time and reduce this time almost by half. In 7b Systems (TOPLAS) 13, 4 (1991), 451–490. we compare our algorithm with a flow-sensitive Andersen’s pointer [4] Bernard A Galler and Michael J Fisher. 1964. An Improved Equivalence Algorithm. analysis proposed in [8]. The flow-sensitive Steensgaard’s results Commun. ACM 7, 5 (1964), 301–303. [5] Samuel Z Guyer and Calvin Lin. 2005. Error checking with client-driven pointer were lightly worse on the ex benchmark, this may be explained by analysis. Science of Computer Programming 58, 1-2 (2005), 83–114. the fact that the Andersen’s algorithm iterations end faster than our [6] Ben Hardekopf and Calvin Lin. 2007. The ant and the grasshopper: fast and iterations over all the instruction in the IR in a smaller benchmark. accurate pointer analysis for millions of lines of code. In ACM SIGPLAN Notices, Vol. 42. ACM, 290–299. However our analysis obtained great increase of speed on a larger [7] Ben Hardekopf and Calvin Lin. 2009. Semi-Sparse Flow-Sensitive Pointer Analy- benchmark, SVN, in which there was a speedup of 3.5x. sis. In ACM SIGPLAN Notices, Vol. 44. ACM, 226–238. 7 [8] Ben Hardekopf and Calvin Lin. 2011. Flow-sensitive Pointer Analysis for Millions of Lines of Code. In Proceedings of the 9th Annual IEEE/ACM International Sym- posium on Code Generation and Optimization. IEEE Computer Society, 289–298. [9] Michael HIND and Anthony PIOLI. 2000. Which Pointer Analysis Should I Use?. In ACM SIGSOFT Software Engineering Notes, Vol. 25. ACM, 113–123. [10] Chris Lattner and Vikram Adve. 2004. LLVM: A Compilation Framework for Lifelong Program Analysis & Transformation. In Proceedings of the international symposium on Code generation and optimization: feedback-directed and runtime optimization. IEEE Computer Society, 75. [11] Chris Lattner, Andrew Lenharth, and Vikram Adve. 2007. Making Context- sensitive Points-to Analysis with Heap Cloning Practical for the Real World. In Proceedings of the 28th ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI ’07). ACM, 278–289. [12] Sheng-Hsiu Lin. 2015. Alias Analysis in LLVM. (2015). [13] Vaivaswatha Nagaraj and R Govindarajan. 2013. Parallel Flow-Sensitive Pointer Analysis by Graph-Rewriting. In Proceedings of the 22nd international conference on Parallel architectures and compilation techniques. IEEE Press, 19–28. [14] Nicholas Nethercote and Julian Seward. 2003. Valgrind: A Program Supervision Framework. Electr. Notes Theor. Comput. Sci. 89, 2 (2003), 44–66. [15] Bjarne Steensgaard. 1996. Points-to Analysis in Almost Linear Time. In Proceed- ings of the 23rd ACM SIGPLAN-SIGACT symposium on Principles of programming languages. ACM, 32–41. [16] Yulei Sui, Peng Di, and Jingling Xue. 2016. Sparse flow-sensitive pointer analysis for multithreaded programs. In Proceedings of the 2016 International Symposium on Code Generation and Optimization. ACM, 160–170. [17] Sen Ye, Yulei Sui, and Jingling Xue. 2014. Region-based selective flow-sensitive pointer analysis. In International Static Analysis Symposium. Springer, 319–336.

8