Generic Abstract Interpretation Algorithms for Prolog : Two Optimization Techniques and their Experimental Evaluation

interpretation 5 is a general methodology to obtain, in a systematic way, tools to analyse programs statically (at compile time). The basic idea behind abstract interpretation is to approximate (usually undecidable) properties by using an abstract * This work was done when Vincent Englebert and Didier Roland were visiting Brown University. † Author to whom correspondence must be directed. 0038–0644/93/040419–41$25.50 Received 19 December 1991  1993 by John Wiley & Sons, Ltd. Revised 3 December 1992 420 v. englebert et al. domain instead of the actual domain of computation. As a consequence, the program as a whole can be given an approximated meaning, hopefully capturing interesting properties while leaving out irrelevant details as much as possible. Abstract interpretations of Prolog have attracted many researchers in recent years. The motivation behind these works stems from the need for optimization in Prolog compilers (given the very high level of these languages) and the large potential for optimization since the semantic features of logic languages make them more amenable to optimization. Mellish was probably the first to define an abstract interpretation framework for Prolog motivated by early analysis tools for Prolog programs. Subsequently, many frameworks have been developed 6–13 and a variety of abstract domains have been proposed to cater for various program analysis tools involving modes, 14,15 types, 16 occur-check, 17 garbage collection, 18 static detection of parallelism and program specialization, 20 to name a few. However, relatively little attention has been devoted to generic abstract interpretation algorithms, although several authors have proposed (more or less precise) sketches of some algorithms. 6,8,12,21–23 Moreover, few experimental results are available to assess the practicability of the overall abstract interpretation approach for Prolog. One of the rare papers on the topic 3,4 describes the efficiency and accuracy of two analysis tools and shows that these tools are in fact practical both in terms of efficiency and in terms of usefulness of the results. However, it would be unreasonable to infer from References 3 and 4 the viability of generic abstract interpretation of Prolog programs, as the tools analysed were rather specialized and targeted towards specific applications. Part of our research has been devoted to demonstrating the practicality of this area of research. Our starting point was the design of a generic abstract interpretation algorithm and its complexity analysis. 1 The algorithm, initially motivated by Reference 21, is a top-down algorithm focusing on relevant parts of the least fixpoint necessary to answer the user query. It is polynomial in the worst-case and linear in the sizes of the abstract domain and of the Prolog program in many interesting cases. The algorithm, together with its instantiation to a sophisticated abstract domain (derived from Reference16) containing modes, types, sharing and aliasing, has been implemented in Pascal and run on a large number of programs. The experimental results have shown the practical value of the algorithm and have indicated that abstract interpretation can be competitive with specialized algorithms such as those reported in References 3 and 4. In this paper, we reconsider the problem of implementing efficiently generic abstract interpretation algorithms for Prolog. We propose two new optimization techniques to the original algorithm: dependency on clause prefixes and caching of operations. The first improvement avoids re-evaluating a clause prefix when no abstract value on which it depends has been updated. As a consequence, it generalizes the dependency graph proposed in Reference 1 and avoids re-evaluating clauses and part of clauses unnecessarily. The second improvement consists of caching all operations on substitutions and reusing the results whenever possible. Garbage collection is performed on substitutions which are no longer in use. This improvement subsumes (in some sense) the first improvement because most of the computation time is consumed by the abstract operations. Therefore, executing clause prefixes using cached operations only requires a negligible amount of time. Caching also allows the sharing of results between independent computations, albeit at a higher 421 generic abstract interpretation algorithms cost in memory. Moreover, in the case of finite abstract domains, it implies that the number of operations performed by the algorithm is bounded by the number of program points times the number of times their associated operations can be executed (in the worst case the square of the abstract domain size). The two optimization techniques are, in fact, general-purpose and can be used for other languages and approaches as well. The algorithms (the original algorithm together with each improvement) have been implemented in C (about 8000 lines of code each). They have been tested on a large number of Prolog programs and compared with the original implementation. In conjunction with refinements of the abstract operations implementation, they produce an average reduction of more than 50 per cent in computation time. Extensive experimental results on the programs are given, including computation times, memory consumption, hit ratios for the caches, the number of operations, and the time distribution. As a main result, the improved algorithms exhibit the same efficiency as the specific tools of Warren, Debray and Hermenegildo, 3,4 despite the fact that our abstract domain (including types and sharing) is more sophisticated and accurate. The abstract operations also take 90 per cent of the computation time, indicating that the overhead of the control is small compared to the abstract operations. This means that the algorithm is close to optimality for computing the given abstract semantics. * Results on a simpler domain are also given and show that even extremely basic domains can benefit from the optimizations. The rest of the paper is organized in the following way. Section 2 gives an overview of the original abstract interpretation algorithm and of the main abstract domain used in our experiments. Section 3 presents the clause prefix improvement and Section 4 presents the caching improvement. Section 5 is devoted to the experimental results of the algorithms. Section 6 discusses how to apply the optimizations to other contexts. Section 7 contains the conclusions of this research and directions for further work.