Lecture Notes in Computer Science Edited by G. Goos and J. Hartmanis Advisory Board: W. Brauer
D. Gries
J. Stoer
675
Anne Mulkers
Live Data Structures in Logic Programs Derivation by Means of Abstract Interpretation
Springer-Verlag Berlin Heidelberg NewYork London Paris Tokyo Hong Kong Barcelona Budapest
Series Editors Gerhard Goos Universit~it Karlsruhe Postfach 69 80 Vincenz-Priessnitz-Stra6e 1 W-7500 Karlsruhe, FRG
Juris Hartmanis Cornell University Department of Computer Science 4130 Upson Hall Ithaca, NY 14853, USA
Author Anne Mulkers Department of Computer Science, K.U. Leuven Celestijnenlaan 200 A, B-3001 Heverlee, Belgium
CR Subject Classification (1991): F.3.1, D.3.4, 1.2.2-3
ISBN 3-540-56694-5 Springer-Verlag Berlin Heidelberg New York ISBN 0-387-56694-5 Springer-Verlag New York Berlin Heidelberg
This work is subject to copyright. All rights are reserved, whether the whole or part of the material is concerned, specifically the rights of translation, reprinting, re-use of illustrations, recitation, broadcasting, reproduction on microfilms or in any other way, and storage in data banks. Duplication of this publication or parts thereof is permitted only under the provisions of the German Copyright Law of September 9, 1965, in its current version, and permission for use must always be obtained from Springer-Verlag. Violations are liable for prosecution under the German Copyright Law. 9 Springer-Verlag Berlin Heidelberg 1993 Printed in Germany Typesetting: Camera ready by author/editor 45/3140-543210 - Printed on acid-free paper
Preface Abstract interpretation is a general approach for program analysis to discover at compile time properties of the run-time behavior of programs, as a basis to perform sophisticated compiler optimizations. Several frameworks of abstract interpretation for logic programs have been presented [11, 25, 27, 43, 48, 49, 51, 55, 57, 65, 81, 82]. A framework is a parameterized construction for the static analysis of programs, together with theorems that ensure the soundness and termination of the analysis. To complete the construction, an application specific domain and primitive operations satisfying certain safety conditions must be provided. This book elaborates on an application for such a generic framework. The framework used [11] belongs to the class of top-down abstract interpretation methods and collects the information derived in an abstract AND-OR-graph that represents the set of concrete proof trees that can possibly occur when executing the source program. The starting point of the present work is the previously developed application of integrated type and mode analysis [38]. The purpose of that application was to guide the compiler, based on a characterization of the entry uses of the program, to generate code that is more specific for the calls that can occur at run time. In an attempt to give further guidance to the compiler, we address the problem of compile-time garbage collection, the purpose of which is to (partially) shift run-time storage reclamation overhead to compile time. In applicative programming languages, the programmer has no direct control over storage utilization, and run-time garbage collection is necessary. Garbage collection involves a periodic disruption of the program execution, during which usually a marking and compaction algorithm is employed. Such schemes are expensive in time. Our research shows that at compile time useful and detailed information about the liveness of term substructures can be deduced which the compiler can use to improve the allocation of run-time structures. In fact, it provides a technique to automatically introduce destructive assignments into logic languages in a safe and transparent way, thereby reducing the rate at which garbage cells are created. The resulting system gets near to the methods of storage allocation used in imperative programming languages. The global flow analysis to be performed on Prolog source programs in order to derive the liveness of data structures is constructed in three layers. The
vI first layer, consisting of the type and mode analysis, basically supplies the logical terms to which variables can be bound. The two subsequent layers of the analysis heavily rely on these descriptions of term values. The sharing analysis derives how the representation of logical terms as structures in memory can be shared, and the liveness analysis uses the sharing information to determine when a term structure in memory can be live.
Acknowledgments This book is based on my Ph.D. dissertation [59] conducted at the Department of Computer Science of the K.U.Leuven, Belgium. The research presented has been carried out as part of the RFO/AI/02 project of the Diensten voor de programmable van he~ wetenschapsbeleid, which started in November 1987 and was aimed at the study of implementation aspects of logic programming: 'Logic as a basis for artificial intelligence: control and efficiency of deductive inferencing and parallelism'. I am indebted to Professor Maurice Bruynooghe, my supervisor, for giving me the opportunity to work on the project and introducing me to the domain of abstract interpretation, for sharing his experience in logic programming~ his invaluable insights and guidance. I wish to thank Will Winsborough for many helpful discussions, for his advice on the design of the abstract domain and safety proofs and his generous support; Gerda Janssens for her encouragement and support, and for allowing the use of the prototype for type analysis as the starting point for implementing the liveness analysis; Professors Yves Willems and Bart Demoen, for managing the RFO/AI/02 project and providing me with optimal working facilities; Professor Marc Gobin, my second supervisor, and Professors Baudouin Le Charlier and Danny De Schreye, for their interest and helpful comments, and for serving on my Ph.D. thesis committee. I also want to thank my family, friends and colleagues for their support and companionship.
Leuven, March 1993
Anne Mulkers
Contents Introduction
1
Abstract Interpretation Basic Concepts . . . . . . . . . . . . . . . . . . . . . . . . . . . Abstract Interpretation Framework . . . . . . . . . . . . . . . Overview of the F r a m e w o r k . . . . . . . . . . . . . . . . . . . . Concrete a n d A b s t r a c t D o m a i n s of S u b s t i t u t i o n s . . . . . . . . Primitive Operations ....................... A b s t r a c t I n t e r p r e t a t i o n Procedure . . . . . . . . . . . . . . . . E x a m p l e : I n t e g r a t e d T y p e a n d Mode Inference . . . . . . . . . Rigid a n d I n t e g r a t e d T y p e G r a p h s . . . . . . . . . . . . . . . . Type-graph Environments ..................... P r i m i t i v e O p e r a t i o n s for T y p e - g r a p h E n v i r o n m e n t s . . . . . .
5 5 7 8 10 11 14 16 16 23 25
Related Work 3.1 Aliasing a n d P o i n t e r A n a l y s i s . . . . . . . . . . . . . . . . . . . 3.2 Reference C o u n t i n g a n d Liveness Analysis . . . . . . . . . . . . 3.3 Code O p t i m i z a t i o n . . . . . . . . . . . . . . . . . . . . . . . . .
31 31 38 41
2.1 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.3 2.3.1 2.3.2 2.3.3
3
4
Sharing Analysis 4.1 4.1.1 4.1.2 4.1.3 4.1.4 4.2 4.2.1 4.2.1.1 4.2.1.2 4.2.2 4.2.3 4.3 4.3.1 4.3.2
Sharing Environments . . . . . . . . . . . . . . . . . . . . . . . Concrete R e p r e s e n t a t i o n of Shared S t r u c t u r e . . . . . . . . . . A b s t r a c t R e p r e s e n t a t i o n of Shared S t r u c t u r e . . . . . . . . . . T h e Concrete a n d A b s t r a c t D o m a i n s . . . . . . . . . . . . . . . Order R e l a t i o n a n d U p p e r b o u n d O p e r a t i o n . . . . . . . . . . . Primitive Operations ....................... Unification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Xi :
Xj
Xi :
f(Xil,...,Xij)
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Procedure E n t r y . . Procedure Exit . . . Evaluation . . . . . Example: i n s e r t / 3 Relevance of S h a r i n g
. . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ........................ Edges . . . . . . . . . . . . . . . . . . . .
47 47 48 55 62 66 68 68 69 85 93 98 110 111 114
v lll
CONTENTS
4.3.3 4.3.4
6
Imprecision in the S h a r i n g Analysis . . . . . . . . . . . . . . . Efficiency of the S h a r i n g Analysis . . . . . . . . . . . . . . . .
117 123
LivenessAnalysis 5.1 Liveness E n v i r o n m e n t s . . . . . . . . . . . . . . . . . . . . . . 5.1.1 Concrete R e p r e s e n t a t i o n of Liveness I n f o r m a t i o n . . . . . . . . 5.1.2 A b s t r a c t R e p r e s e n t a t i o n of Liveness I n f o r m a t i o n . . . . . . . . 5.1.3 T h e Concrete a n d A b s t r a c t D o m a i n s . . . . . . . . . . . . . . . 5.1.4 Order R e l a t i o n a n d U p p e r b o u n d O p e r a t i o n . . . . . . . . . . . 5.2 Primitive Operations . . . . . . . . . . . . . . . . . . . . . . . Unification . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2.1 5.2.1.1 Xi = X j . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2.1.2 X i : f ( X i , , . . . , X i j ) . . . . . . . . . . . . . . . . . . . . . . 5.2.2 Procedure E n t r y . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2.3 Procedure Exit . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3 Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3.1 Example: q s o r t / 3 . . . . . . . . . . . . . . . . . . . . . . . . . 5.3.2 Precision of the Liveness Analysis . . . . . . . . . . . . . . . . 5.3.3 T h e Practical Usefulness of Liveness I n f o r m a t i o n . . . . . . . .
127 127 128 133 141 145 147 147 147 153 154 163 165 165 168 171
. . . . .
. .
Conclusion
179
Appendix; Detailed Examples A.1 List of T y p e s . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.2 append/3 ......................... ..... A.3 nrev/2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.4 buildtree/2 and insert/3 . . . . . . . . . . . . . . . . . . . . . . A.5 p e r m u t a t i o n / 2 a n d select/3 . . . . . . . . . . . . . . . . . . . . A.6 split/3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.7 qsort/2 and partition/4 . . . . . . . . . . . . . . . . . . . . . . A.8 s a m e l e a v e s / 2 a n d profile/2 . . . . . . . . . . . . . . . . . . . . A.9 sift/2 a n d r e m o v e / 3 . . . . . . . . . . . . . . . . . . . . . . . .
183 184 185 188 193 196 199 202 205 209
Bibliography
213
Chapter 1
Introduction In conventional languages, such as C or Pascal, the p r o g r a m m e r explicitly controls the utilization of m e m o r y by means of declarations and destructive assignments. For example, when reversing a linear list L, the list cells of the original list can be reused to construct the reversed list in the case that the original list is no longer needed for further computations. It is up to the p r o g r a m m e r to decide whether he needs to preserve the old list intact and construct a reversed list which has only the list elernertLs in c o m m o n with the list L (e.g. Rev_L1 in Figure 1.1), rather than reuse the list-constructor cells of L as well (e.g. Rev_L2).
_1
-,
, I
I
,
-J
-,
,
I
I
,
_1
-~
,
I
i
[
Rev_L2
/
\"
/
\"
-,
RevL 1 /
J e
Figure 1.1: Reversing a linear list. Applicative languages, in their pure form, do not have destructive assignments. Also type declarations are often absent. The declarative nature of these languages is often cited as an i m p o r t a n t advantage, which allows p r o g r a m m e r s to focus on the logic of the problems they have to solve, rather than on more technical aspects such as search control and efficient m e m o r y usage. Unfortunately, the performance of current implementations of applicative languages does not compare well with procedural languages yet. To achieve better utilization of memory, global flow analysis techniques are being developed that are concerned with determining the type and liveness of d a t a structures that are dynamically
2
C H A P T E R 1. I N T R O D U C T I O N
append (nil, _Y, _Y). append([_E I _UJ,_Y,[_E I _W]) :- append(_U,_Y,_W). nrev(nil, nil). nrev([_E I _U], _Y) :-nrev(_U, _KU), append(_KU, [~], ~). P r o g r a m 1.h n r e v / 2 (Naive reverse)
created during program execution. Knowledge about the lifetime of d a t a structures guides the compiler in the generation of target code to reuse heap storage t h a t is no longer accessible from program variables, i.e. to introduce destructive operations and avoid the copying of data structures that have no subsequent references. In this book, we address the problem of liveness analysis for the class of pure Horn clause logic programs. The language considered has a countable set of variables (Vats), and countable sets of function and predicate symbols. A term is a variable, a constant, or a compound term f ( Q , . . . , t , ~ ) where f is a n-ary function symbol and the t~ are terms. An atom has the form p ( Q , . . . , tin) where p is a m - a r y predicate symbol and the ti are terms. A body is a (possibly empty) finite conjunction of atoms, written A1,..., A,~. A clause consists of an a t o m (its head) and a body and is written A : - B. A program consists of a finite number of clauses. A query or goalconsists of a body only, written 7- B. We assume that the reader is acquainted with the basic terminology of logic programming and the execution mechanism of Prolog which is based on unification and backtracking. Features such as assert and retract are not considered, i.e. we assume that any source code for the predicates that can be executed at run time is available to the compiler. The handling of d a t a structures is very flexible in Prolog. D a t a manipulation (record allocation as well as record access and parameter passing) is achieved entirely via unification. An optimizing compiler can translate general unification to more conventional m e m o r y manipulation operations if information is available about the mode of use of the predicates. When at run time a compound term becomes accessible for the first time, we can say the term is being constructed. When a pattern is matched against a compound t e r m that is already accessible, we can say the components of the term are being selected. Integrated type and mode analysis in m a n y cases allows to predict at compile time whether a unification is a selection rather than a construction operation. Selection statements in particular are good candidates to check for the possible creation of garbage cells, i.e. cells that have no further references. Consider the Prolog Program 1.1 for naive list reversal. We use the convention that variable names start with an underscore. If we assume that queries to n r e v / 2 are restricted to have as first argument a list that is no longer referenced after the call, and as second argument a free variable to return the output, then it is possible to generate target code for this program that allocates no new
list-constructor cells, but rather reuses the list cells of the first argument. Indeed, under the assumption, the integrated type and mode analysis will infer t h a t each call to the recursive clause of n r e v / 2 has as its first argument a list, and as second argument a free variable. The unification of the call with the clause head selects the head and tail of the first argument list. The principal list-constructor cell of this list on the contrary has no subsequent references in the clause following the unification of the call with the clause head. This means that the compiler can recognize the principal list cell as garbage and generate target code t h a t reuses it. For instance, consider the caI1 to a p p e n d / 3 m a d e by the same clause. A single element list [_E] needs to be constructed. Instead of allocating a new cell, the compiler can reuse the garbage cell that was detected. Note that the problem is more complex if there may be multiple references to the cells of the input list. Most implementations of unification unify a variable and a compound structure by making the variable a reference to the structure not a copy of the structure. The representations in m e m o r y of the logical terms to which variables can be bound typically share some of their structure: while the denoted terms make up a forest of trees, their representations form a more general directed acyclic graph. This is why in general the sharing analysis plays a crucial part in the liveness analysis. In the above example, we can also infer that, the first two arguments in each call to a p p e n d / 3 will be lists and that the third argument will be a free variable. Again, it is possible to detect that, after invocation of the recursive clause of append/3, the principal cell of the first argument is garbage and can be reused to construct the value of the third (output) argument. Thus, all list constructions in this example can reuse garbage list ceils, eliminating all allocation operations. Since the reused cells would otherwise be garbage, we have eliminated the garbage-collection overhead associated with the n r e v / 2 procedure. Moreover, a compiler can detect that the element field of each reused list cell already contains the value desired in the cells new use. The operations filling in these car fields can be eliminated from the generated target code. The resulting code closely resembles how a p r o g r a m m e r using an imperative language would solve the problem of reversing a linear list of linked records. In the present work, we propose an abstract domain and operations to analyze the liveness of d a t a structures within a framework of abstract interpretation. Chapter 2 presents the principles of abstract interpretation for logic programs, and the application of type and mode analysis on which the domain for liveness analysis is based. In Chapter 3, we discuss work related to the application of compile-time garbage collection in the context of both logic and functional prog r a m m i n g languages. In Chapter 4, we formalize an abstract interpretation for analyzing how the terms to which program variables are bound at run time, can share substructure in storage. We also augment the usual concrete semantics with information about sharing of t e r m structures and discuss whether any implementation c o m m i t m e n t s are implied. As argued above, the sharing analysis constitutes a prerequisite for the liveness analysis. The latter is presented in Chapter 5. In both Chapter 4 and 5, the emphasis is mainly on the precision
4
C H A P T E R 1. I N T R O D U C T I O N
and on the soundness of the results that can be obtained, rather than on the efficiency of the analysis. Due to imprecision that is inherent to the global analysis algorithms, not all garbage cells can be detected in arbitrary cases. We will extensively discuss the strength of the analyses that are proposed. The study of code optimization schemes that explicitly reclaim or reuse garbage cells is beyond the scope of the present book. In [52], Mari~n et al. discussed some preliminary experiments on code optimization based on liveness information. Only opportunities for local reuse of storage cells are considered, i.e. reuse within the same clause where a cell is turned into garbage. Non-local reuse would require extra run-time data areas to keep track of the free space. Although possible in principle, non-local reuse therefore will be less beneficial for code optimization. The reuse of storage also introduces some new requirements on the trailing mechanism of standard Prolog implementations that will affect the performance. We will briefly discuss these issues in Section 3.3 and 5.3.3.
Chapter 2
Abstract Interpretation In this chapter, we first set forth the basic principles of abstract interpretation. Then, we sketch one framework in particular, as it will be used in the remainder of the book. Finally, we describe an application, namely type and mode analysis, which constitutes the first layer of the global flow analysis to derive the liveness of data structures. A more detailed introduction to abstract interpretation of declarative languages can be found in [1].
2.1
Basic C o n c e p t s
Static p r o g r a m analysis is a general technique for deriving properties of the run-time behavior of a program. The information obtained in this way can be used to drive the optimization phase of a compiler, or to guide source level program transformation and program development tools (e.g. for debugging). Often, program analysis can be viewed as executing the program over a symbolic or abstract domain of d a t a descriptions, instead of over the normal concrete d a t a domain, and therefore it is called abstract interpretation. To mimic the concrete execution of a program, the basic operations defined on the standard domain are replaced by abstract operations defined on the abstract domain. The resulting flow analysis produces a so-cMled abstract semantics: for each possible point of control in the program, it gives a finite description of the set of data states that the program could be in when execution passes through that point. The program properties that compiler optimizations are based on are usually undecidable. Since static analyses are expected to be finitely computable, the data descriptions will be imprecise in general. The abstract interpretation is said to be sound if the d a t a descriptions computed for each program point give upper approximations of the set of concrete data states that can occur during program execution. Patrick and Radhia Cousot [24] provided a general framework for data-flow analysis problems of imperative languages, and they defined conditions which ensure the soundness of an abstraction. Based on that work, a variety of abstract
6
CHAPTER 2. A B S T R A C T INTERPRETATION
interpretation frameworks have been developed for the specification and verification of analyses for logic programs [11, 25, 27, 43, 48, 49, 51, 55, 57, 65, 81, 82]. A framework is a formally based, generic construction for program analysis that provides a basis for sound optimizations. To this end, it includes theorems that ensure the safety and termination of the analysis if the application dependent domains and operations supplied to complete the construction obey certain safety requirements. The standard theory of lattices provides the conceptual framework for program analysis. In order to apply the method for a new application, the space of properties (the abstract domain) capturing the information of interest should be a complete lattice (i.e. a set with a partial ordering such that each subset has a least upper bound and a greatest lower bound), and the functions used should be monotone or order-preserving on the lattice. If these conditions are satisfied, then the Tarski-Knaster Fixpoint Theorem [71] guarantees the existence of a solution for the fixpoint problem posed by the abstract semantics of the program. Let (C, E) be the concrete domain, and (A, _<) the domain of descriptions. In the context of logic programs, the concrete domain C will typically be the powerset of the set of substitutions. A substitution is a set a - {X1 +-- K:I,..., Xc *--ice}, where the Xi are distinct variables and the /Ci are terms. The set D = { X 1 , . . . , X~} is called the domain of the substitution a (denoted as dom(a)). An interesting application of program analysis for logic programming languages is mode analysis. Significant performance improvements can be achieved in interpreters if it is known how the logical variables are used in a relation (i.e. as input or output or a mixture of the two). For this application, the abstract domain A will describe the instantiation state of the terms assigned to the variables by the concrete substitutions that are possible at run time. A simple mode analysis can be based on the set (a, g, f}, partially ordered such that f < a and g < a. The modes f, g and a are abbreviations for respectively free, ground and any. An element of the abstract domain A is either • or an abstract substitution of the form {Xi +-- rn~ [ Xi E D & rn/ e {f, g, a}} that assigns a mode to each variable of some domain D. The semantics of the descriptions is given by a concretization function "), : A ---* C, such that ~(z) is the set of concrete objects described by z. Conversely, given a collection of concrete objects, the abstraction function a : C ---* A yields the best overall description of the objects. Suppose that the normal interpretation of a program is defined in terms of an operator F c , which has as abstract counterpart FA (e.g. for the execution of logic programs, unification is the central operation). The traditional abstract interpretation scheme requires that the following conditions are satisfied. 1 . (C, _ ) and (A, <) are complete lattices, 2. q, : A --4 C and ot : C ~ A are monotonic, i.e. 9 z _~ y ::~ a ( z ) < a(y), for all a:, y e C, 9 z < y ~'r(~)
z ~(y), for all ~ , y e A,
2.2. A B S T R A C T INTERPRETATION F R A M E W O R K 3. z = a ( 7 ( z ) ) for all z e
A,
4. 9 E_
C,
for a l l .
7
5. F c : C ---* C and FA : A --~ A are monotonic, and
6.
E
for a l l .
A.
Conditions 2, 3 and 4 ensure that C and A have a similar structure. The last condition is called the safely or soundness requirement, and ensures that the abstract operation mimics the concrete operation such that no false conclusion can be drawn about the concrete operation's behavior. The framework described in the next section requires a set of similar but slightly weaker conditions. Two basic classes of abstract interpretation of logic programs can be distinguished. The top-down analyses are based on the standard operational semantics of Prolog, i.e. SLD-resolution with a left-to-right computation rule which always resolves the leftmost goal in the current resolvent. In order to derive the states in which clauses and their calls can be reached, a query specification is provided that characterizes the entry uses of the program. The bottom-up analyses [18, 53] are based on the fixpoint semantics of logic programs. Because the evaluation of a b o t t o m - u p semantics does not correspond to the operational behavior of a program, it does not readily provide information about call patterns describing how the clauses of a program will be used in actual computations. Furthermore, the b o t t o m - u p computations tend to derive a lot of redundant facts not actually needed to answer a specific query. To overcome both deficiencies, the normal top-down Prolog execution is simulated in recent work by first transforming the program and the query according to the magic templates method and then executing the new program b o t t o m up [18, 66]. This method is adopted from the field of deductive databases, where magic templates are used to provide for efficient b o t t o m - u p evaluation of database queries. The resulting b o t t o m - u p abstract interpretation method seems to be equally suited for all sorts of applications as the corresponding top-down methods using the same abstract domain and operations.
2.2
Abstract
Interpretation
Framework
In this section, we explain the framework of abstract interpretation that forms the base for our analysis and was proposed by Bruynooghe in [11]. It is a topdown analysis method adequate to determine an upper approximation of the set of substitutions that can actually occur while executing the program, given an initial goal. It is a parameterized construction: each application requires the design of the application dependent components, namely the abstract domain and the abstract interpretation operations that are used in the abstract interpretation procedure, which is itself domain independent. The soundness of the elementary abstract operations implies the soundness of the abstract interpretation.
8
C H A P T E R 2. A B S T R A C T I N T E R P R E T A T I O N
2.2.1
Overview
of the
Framework
In the abstract interpretation framework, data descriptions are computed at a fixed number of program points: in each clause just after the head, between two calls in the body and after the last call. Consider, for example, the clause
where the program points are indicated by circled labels. A canonical form of the clauses is used1: procedure calls and headings are normalized such that the parameters are distinct variables. This is done by breaking unifications up into sequences of simple operations. For example, the append app/3 procedure in normal form is as follows. app(_X,_Y,_Z) : - _X=nil, _Y=_Z. app(_X,_Y,_Z) : - _~=_E._U, app(_U,_Y,_W), _Z=_E._W. As a consequence, parameter passing becomes very simple. It reduces to passing values from actual to formal parameters on procedure entry, and vice versa on procedure exit. The real unification work is done by the sequence of primitive unification operations. The information inferred by the abstract interpretation process is gathered in an abstract AND-OR-graph. Such a graph is the finite representation by means of a rational tree 2 of a set of (possibly infinite) proof trees that correspond to the concrete executions of the program. An 'Or'-node represents a call, and its children represent the different clauses matching with the call. The 'And'-nodes on the other hand represent a clause head, and their children the subgoals of the clause body. A graphical representation of an AND-OR-graph for the app/3 program is given in Figure 2.1. Each program point is adorned with an element of the abstract domain, an abstract substitution. The abstract substitution to the left of the root,/30, represents a set of initial queries and is supposed to be specified by the user. For instance, consider the application of mode analysis mentioned above. We would have/~0 -- {_11 ~ g, .12 ~ g, _13 ~ a} to specify that the first and second argument are ground terms in any initial query and the third argument may be any term. The other abstract substitutions,/~1 . . . . . fls, represent sets of concrete substitutions over the variables of the clause or query they adorn, and the soundness of the abstract interpretation procedure will guarantee that they are safe over approximations of the set of concrete substitutions that can actually occur at run time when control reaches that point for any concrete derivation originating from one of the queries represented by the initial abstract substitution. The abstract substitution to the left of a call is often called the abstract call-substitution, and the one to the right is called the abstract success-substitution of the call. E.g. in Figure 2.1,/~s is both the abstract success-substitution for the call _.X=_E._U, and the abstract call-substitution for the call app(_U, _Y, _W). 1 We use a slightly simplified version of the framework developed in [11]. 2 A rational tree is a tree that has only a finite n u m b e r of distinct subtrees [20].
2.2. A B S T R A C T I N T E R P R E T A T I O N F R A M E W O R K
9
~0 a p p ( _ l l , ~ 2 , _ . i 3 ) /~s
app(_X,_Y,_Z)
app(-X, _Y, _z)
/%
~I /=nil B2 _Y=_Z ~s
~4 _X=V.._U~5
app(_U,_g,_w) ,Be _Z=-E._W ~T
1 Figure 2.1: AND-OR-graph for a query ?- a p p ( _ l l ,
-12, _3.3).
The abstract AND-OR-graph provides the information required by program specialization techniques [31, 81, 82]. In general, the AND-OR-graph can contain several instances of a particular clause, but with different abstract callsubstitutions. The compiler can decide to generate target code specialized for each version (taking care that the proper version is called at run time for each program point) or to generate target code that is general enough to be used for all the versions. The abstract interpretation procedure to construct an abstract AND-ORgraph (see Section 2.2.4) follows essentially a top-down strategy, starting from the query form. It takes into account the depth-first left-to-right computation rule of Prolog, but not the search rule nor the effects of cu~ (!). This means that the order of the child nodes of an 'And'-node are relevant, but for an 'Or'-node, the order is irrelevant. Also, there might be calls which are considered possible but will never occur at run time; consequently, some imprecision might result. Typical for the framework is that the abstract substitutions only mention the variables of the query or clause that they adorn. As a consequence, each call requires two unification-based operations, not just one as in standard SLDresolution. By confining attention to the state of the variables in one clause at a time, the analysis can recognize the similarity between clause invocations that differ only in the context of their use. This property is essential for the treatment of recursive calls. It also makes possible an optimization of the implementation such that a repeated computation of the same subgraph in different branches of the AND-OR-graph is avoided.
CHAPTER 2. A B S T R A C T INTERPRETATION
10
When a clause is invoked, the analysis restricts attention to the portion of the state pertaining to the variables in the clause. The restriction is achieved by an operation called procedure entry, which abstracts the usual unification between call and clause head. When control returns to the caller, the analysis must reintroduce the variables in the calling environment 3 and convey the effect of the clause execution upon those variables. This is achieved by an operation called procedure exit, which abstracts a second unification between call and (instantiated) clause head. We further need a third primitive operation to handle the nontrivial unification operations occurring in the source program 4. The safety requirements imposed by the framework (see Section 2.2.3) assure that the outcomes of abstract procedure entry, procedure exit and primitive unification cover the outcomes of their concrete counterparts. In the next section, we first look at the requirements imposed on the abstract domain in order to get a finite analysis. 2.2.2
Concrete
and
Abstract
Domains
of Substitutions
The set of variables in the clause (or query) that a concrete (or abstract) substitution adorns is called the domain D of the substitution. The restriction of a concrete substitution a to a set D of variables is defined as aid = {X~ *---ti I Xi ~ - t i E a & X~ E D}. We denote the set of all idempotent s concrete substitutions having domain D b y ( o n c r S u b D , and the set of all abstract substitutions having domain D by AbstrSubD. The concretization function -), to be defined is a mapping : AbstrSubD --~ 2 c ~
Although the framework does not refer explicitly to the abstraction function a : 2 C~
--~ AbstrSubD,
we assume such a function is given and allows the user to abstract the initial set of queries. The set AbstrSubD is not required to be finite, nor does it need to be a complete lattice; the abstract interpretation framework of [11] imposes a slightly weaker algebraic structure on AbstrSubD that still guarantees termination of the abstract interpretation procedure. There must exist a binary relation < that is reflexive and transitive (a preordering), and that satisfies the following property
V~1,~2 E AbstrSubD : ~1 <~_/~2~ ")'(~1) C ')'(~2). n a n environment represents the part of the run-tlme control structure that records the current bindings of variables to terms. 4When the language is extended with other built-ins (e.g. var/1, is/2, atom/1 ,..), a primitive abstract operation has to be associated with each of them (see [21]). ~A substitution ~ is idempotent if ~ ----~ .
2.2. A B S T R A C T I N T E R P R E T A T I O N F R A M E W O R K
11
We define an equivalence relation on the set AbstrSubD as follows
V/31,/3"~ 9 AbstrSubD :/31 = /32 r /31 <__/~2 ~ /~2 --< ~1" The relation <_ can also be used modulo this equivalence relation, which yields a partial order for the set of equivalence classes. The existence of a uniquely determined representative for each equivalence class may be convenient, but is not required by the framework. Further requisites for the framework are 9 An upperbound operator Upp : AbstrSubD x AbstrSubD --r AbstrSubD, such that
V/31,/32 9 AbstrSubD :/31 < Upp(/31,/32) ~k /32 < Upp(/31,/32). A maximal element/3max E AbstrSubD (i.e. V~ E AbstrSubD : ~ma~ 3 :~
Z~. -8). A minimal element J_ E AbstrSubD (i.e. V~ E AbstrSubD :/3 <_ / =~/3 =_ -L) such that 3,(1) = 0. A subset F-AbstrSubD C_ AbstrSubD, such that d-,~m~ 6 F-AbstrSubD, and there does not exist an ascending chain for < in F-AbstrSubD. An ascending chain for <_ is an infinite sequence of elements ( a l , a 2 , . . . , a n , . . . ) such that al < as < . . . < a , < ... (here < is defined as < but not =). An operator R : AbstrSubD --* F-AbstrSubD must be defined, satisfying the property V/3 E AbstrSubD : ~ _< R(/3). We will use a generalization of the Upo operation for a finite set {/31,... ,/3,) of abstract substitutions, which is defined in the obvious way.
Upp({/31,...,/3,~}) = Upp(/31, Upp(..., Upp(/3~_l,/3,~)...)). Note that for the sake of precision of the analysis, Upp should return a value as small as possible (e.g. the least upperbound if AbstrSubD is a lattice).
2.2.3
Primitive Operations
In this section, we give the correctness conditions for the primitive abstract operations that guarantee the soundness of the abstract interpretation procedure of Section 2.2.4. The input for each operation consists of a partially constructed AND-OR-graph, a (collection of) abstract substitution(s), and a call represented by one of the nodes of the AND-OR-graph. The output of the operation consists of a (set of) abstract substitution(s) and the updated AND-OR-graph. We consider the AND-OR-graph as a global data structure for which the updating is an implicit aide-effect of the primitive operations. As in [38], we benefit from the simplifications that are possible, compared with the framework developed in [11], due to the normal-form of the source
12
C H A P T E R 2. A B S T R A C T I N T E R P R E T A T I O N
H 1
9.,
,.,
~n t
H p
-o,
Figure 2.2: Procedure entry extends the abstract AND-OR-graph.
programs. Below, the most general unifier of two terms K1,/C2 is denoted by mgu(/C1, ~2). We denote the set of variables that occur in a particular term/C as Vars(/C). . Procedure-Entry(/~t=, P ) : { ~ . , . . . , / ~ . } Applied to a call P with abstract call-substitution ~i,=, the procedure-entry operation extends the abstract AND-OR-graph at the 'Or'-node representing the call P, by adding a branch for each clause Hj :- BJ , . . . , B ~ (1 < j _ p) defining the predicate P (see Figure 2.2), and it computes the abstract substitutions 3~,=,...,~,~. The domain of the abstract substitution ~',~ is the set of variables Dj in the jth clause. The computation of the abstract substitutions is clone in two steps out of convenience. First, the abstract call-substitution/~i,~is restricted by the call AbstrRestrict(~i,,,P) to a substitution ~.~tr E F-AbstrSubDp, only referringto the domain Dp of the call p6. This step is independent of the set of clauses defining P. In the second step, /~.,t,is renamed for the domain of variables for each of the clauses, and the local variables of the clause are initialized.For instance, for the application of mode analysis mentioned above, the local variables of the clause (i.e.the variables not occurring in the head) are initializedto the mode f (free). The safety requirements that are sufficient for the correctness of the two steps of the procedure-entry operation are respectively: 6 T h i s i s n o t e s s e n t i a l , b u t i t i m p r o v e s t h e p e r f o r m a n c e ; we a s s u m e t h a t t h e p r o c e d u r e - e n t r y o p e r a t i o n u s e s e l e m e n t s of F - A b s t r S U b D p for t h e r e c u r s l v e p r e d i c a t e s .
13
2.2. A B S T R A C T I N T E R P R E T A T I O N F R A M E W O R K
C
CI
HI
B~
B~,, p w',t, I't o ~ t #o,,t
...
...
C~
n p
B~
1 1 B,~,Bo,,
9..
v P B,,~,flo,,,
Figure 2.3: Procedure exit computes ~o~,t. (a) If a E "r(/~,) then aiD P E "r(fl~,~t'). (b) For 1 < j < p, if a E 7(fl~'~") and 0 = mgu(Pa, Hi), with H i the head of the properly renamed jth clause defining P, then 0tD j E 7(fli,~). Due to the normal form of the source programs, proving the safety of the second step is rather straightforward for most applications. The first step may be more involved, especially for the application of liveness analysis. For the example of Figure 2.1, procedure entry computes/31 and f14 from the call app(_11,__12,__13), represented by the root node, and the callsubstitution/30. 2. Procedure-Exit(/31,,
1 {#o,,,,
...,/3o,,,},
P ) = ,fi'o,,,
Procedure-exit is illustrated in Figure 2.3. It can be applied when the final abstract success-substitution ~J,t has been derived for each clause defining P. The initial step computes an abstract substitution arst; that has as domain Dp, the set of variables of the call P. We have a,,t~ P'o~t = Upp({/31,...,13.r}), where /~ (1 < j < p) is the renaming to Dp of AbstrRestrict(/3sout, HJ). In the second step, the eztensio~ operation AbstrExtend reintroduces the other variables in the domain DP.nv of the calling environment (note: Dp C_ DBnv, where DEn, is the domain of/3i, and the calling clause C :- C1,... ,P,... ,Co), and makes explicit the effect on those variables of the execution of the procedure: /3ou, = AbstrExtend(/3i,,
#,,,, p). O~t '
CHAPTER 2. A B S T R A C T INTERPRETATION
14
The safety requirements that are sufficient for the correctness of the two steps of the procedure-exit operation are respectively: (a) If for some j such that 1 < j < p, a~,~ E 7(~/~,~) and aj E 7 ( ~ , t ) , and (3a over Vars(Pai,~) : Pai,.,a = HJaj), with HJ the head of the properly renamed jo, clause defining P, and the only variables of (C :- C1,...,P,...,Cc)a~r, occurring in a also occur in Pa~,~, then { f'~ratr ~ o'inO'[D p E 3"k~'out/" {f4vstr~J, and (b) If a ~ 9 3 " ( ~ ) and (3a over Vars(Pa~)) : a ~ a l D p 9 3"x~-o~t the only variables of (C :- C 1 , . . . , P , . . . , C c ) a i n occurring in a also occur in Pa~,~, then a~,:[Dzn" 9 7(Dour). Proving the safety of the first step follows almost immediately from the properties of the Upp operation and the monotonicity of the concretization function 3'. We will illustrate this in Section 2.3.3. For the example of Figure 2.1, procedure exit computes ~s from the-call represented by the root node, a p p ( - 2 1 , - / 2 , - 2 3 ) , the abstract call-substitution/~0, and the final abstract success-substitutions of the two clauses, /~3 and ~7. . AbstrUnify( ~,,, P ) -- ~o~,t
For programs in normal form, the unification call P can have either of two forms: X~ - Xj or X~ = f(X~,,...,Xi~). Let DBnv be the domain of ~i,~. Th e safety requirement that is sufficient for the correctness of the abstract unification operation is: 9 If cr 9 3"(~i,,) and executing P a instantiates the call by 0, then For the example of Figure 2.1, abstract unification computes/35 from the call ~=.E. _U, and the call-substitution ~4.
2.2.4
Abstract
Interpretation
Procedure
We now sketch the abstract interpretation procedure to construct an abstract AND-OR-graph. It is the application independent kernel of the framework for abstract interpretation. It is convenient to assume that queries consist of a single call; this does not incur any loss of generality. The abstract AND-OR-graph for a set of queries, described by the goal Q and the abstract call-substitution/~/, is initialized with the root node representing the call Q and adorned to the left by the abstract call-substitution 8. The recursively defined abstract interpretation procedure, outlined below, is then called for this initial abstract AND-OR-graph: Abstrlnterpretation (~,Q).
2.2. A B S T R A C T I N T E R P R E T A T I O N F R A M E W O R K
15
A l g o r i t h m 2.1 Abstrlnterpretation(/3i,~,P) I n p u t : A program (a set of clauses), a partially constructed AND-OR-graph and a call node in the AND-OR-graph representing a predicate P and adorned to the left with art abstract call-substitution/3i,. Output: The abstract success-substitution /3o~t, and an updated A N D - O R graph such that/3o~t adorns the call node for P to the right. We distinguish between four cases. C a s e 1: If P is a primitive unification operation, then call AbstrUnify(/3i,,,P). C a s e 2: If P has no ancestor node in the partially constructed AND-OR-graph for a call to the same predicate, then
9 call Procedure-Entry(/3i.,P); 9 call Abstrlnterpretation for each of the calls in the bodies of each of the clauses HJ :- B~,..., B ~ , ( 1 < j _< p) added to the AND-OR-graph by Procedure-Entry in the previous step, always working on a call whose abstract call-substitution is already computed; 9 call erocedure-Exit(/3i., {/3so,,t I 1 _ j < p}, P). C a s e 3: If P has an ancestor node in the partially constructed AND-OR-graph with a call P' for the same predicate, and if they have equivalent abstract call-substitutions ._i,, (B ~'t~ - _i,, 6/~'t~' up to renaming of variables), then f~,t~' which is t-'o,,tm'~ is a renaming of the abstract success-substitution ,-'o,,t computed by the first step of the Procedure-Exit procedure for the call P', Pr~ 't'', {/3So,,t' I 1 _~ j S p}, P'), using l as successj i substitution for all the branches j for which ~3out is not yet computed. Next, /3o,,t is computed by means of the extension operation: 3 ~ =
AbstrExtead(/3,,,/3o~;,7, P). At some point, the Procedure-Exit procedure will be called for P', and a new value will be obtained for a'~'t~' Let /31 be the old and/32 be the I~ o~t 9 new value. If/32 ~/31, then the computation proceeds as usual with the old value 131 and node P refers back to ancestor node P'; otherwise the computation starts over at the call P using R(Upp(/31,/32)) as new value for ~st~ and arsw' /~oltt /~oltt 9 C a s e 4: If P has an ancestor node with a call P' to the same predicate, but the l~rStrl up to renaming abstract call-substitutions are not equivalent (/3~'~t~ ~ ~i,, of variables), then if ~-i,,A~'t~<_ ~-,,,Br't~',the computation proceeds as in C a s e 3, otherwise, the subgraph of P' is recomputed using n(Upp(/3~.~,t~,/3..,,t~' )) as new value for/3[,~t~'.7 ~'The application of R corresponds in fact to the use of a widening operation as discussed by P. Cousot in [23].
16
CHAPTER 2. A B S T R A C T I N T E R P R E T A T I O N
Note that the iterations in Case 3 and Case 4 terminate because there does not exist an ascending chain in F-AbstrSUbDp. For proofs of the correctness and termination of the above procedure, as well as for further details, we refer the reader to [11].
2.3
Example: Integrated Type and Mode Inference
In this section we discuss the type-inference system of Janssens and Bruynooghe [12, 13, 38, 40] to illustrate the application of the abstract interpretation framework. The type system also forms the base of our own abstract domain for deriving the liveness of data structures. The type graphs inferred by the system integrate the functionalities of both modes, which are descriptions of instantiation states, and of types which are descriptions of sets of ground terms. The mode information derivable from the type graphs allows to distinguish the selection operations from the construction operations at compile time, and the type graphs themselves provide the structure information necessary to express liveness of subterms at an adequate level of granularity. 2.3.1
Rigid and Integrated
Type
Graphs
Logic programming languages generally do not contain any type declarations, in contrast with procedural languages. Well-known compiler techniques exist to exploit type information to generate highly optimized code or to perform static type checking. In the literature, several approaches to derive type information automatically by means of a static program analysis have been suggested (for instance in [44, 58, 69, 83, 85, 86]). Usually, types are seen as descriptions of sets of ground terms. This is not the case for the type-inference system developed by Janssens and Bruynooghe. It introduces the concept of type graphs, which are essentially deterministic top-down tree automata [75], and describe sets of partially instantiated terms as well as ground terms. If combined with information constraining how the same variable can occur at various positions in an environment, they provide a very expressive domain for the analysis of the structures to which clause variables can be bound at various points in a program. In [38, 40], two kinds of type graphs are defined: rigid types and integrated types. In this section, we summarize the basic results that were derived for both type systems. The rigid types have the property of being closed under substitution application and they have simple semantic operations. The integrated types on the other hand provide a greater expressivity, and are therefore more suited for the application of code optimization. In the sequel, we assume that the reader is familiar with the basic terminology of graph theory (full definitions of the concepts presented here can be found in I38]). Definition 2.3.1 A rigid type graph is an ordered triple, T = (NodesT-, ForwardArcsT-, BackArcsT-),
2.3. E X A M P L E : I N T E G R A T E D T Y P E A N D M O D E I N F E R E N C E
TT~.~ nil
T2
Or
T30f~ M
fa C / ~ g lnt
O ~ a
17
ax lit
b
Figure 2.4: The graphical representation of some type graphs. where NodesT is a finite, non-empty set of nodes, ForwardArcs~- and BackArcsT are disjoint sets of directed arcs over Nodessr. ~ree -- (Nodes:r, ForwardArcssr), is a tree called the underlying tree of the type graph. Each backarc, (n,n') E BackArcs~r, is a directed arc connecting a node n to one of n's proper ancestors, n', with respect to "Ttr~.. Each node o f t is labeled by a function symbol, 'Or', 'Maz', _l_ or a primitive type name (Int, R e a l , . . . ) . Each node labeled with a function symbol has ordered children corresponding to the function symbol's arguments s. Each node labeled 'Or' has two or more unordered children. Other nodes have no children.
Recall that a tree is a directed acyclic graph (DAG) satisfying the following properties: There is exactly one node, called the root, with indegree 0. Every node except the root has indegree 1. There is a path from the root to each node. The root of the tree Yt,~ (and of the type graph 7-) is denoted Root(T). The label of a node n C NodesT is denoted Label(n). The i th child ofa functor node n (with respect to ForwardArcs~r U BackArcs:r) is denoted Child(i, n). The elements of ForwardArcsT are called forward arcs, the elements of BackArcs:r backward arcs. The information described by a type graph can also be expressed by a context free grammar. We use the convention that (non-terminal or primitive) type symbols start with a capital letter, and that the other functor node labels consist of small letters only. E x a m p l e : Figure 2.4 gives the graphical representation of the following type graphs: T1 ::= nil ] '.'( Int, T1), T2::=a[f(a[b[W2)[g(Int), T3 ::= f(T3) [ Max. In the figure, the nodes of a type graph are represented by their label. We do not explicitly indicate the direction of the forward arcs. We use the convention that S A unique natural n u m b e r n is associated with each function symbol f and referred to as the arityof the symbol. A function symbol of arlty 0 is referred to as a constant. T h e outdegree of a node labeled with a function symbol is equal to the arity of the function symbol.
C H A P T E R 2. A B S T R A C T I N T E R P R E T A T I O N
18
forward arcs are drawn downwards, while backward arcs are drawn upwards. The root of the type graph is the topmost node. There exist several approaches to specify the meaning of a type graph, i.e. the set of concrete terms that it represents. It is the set of terms recognized by the type graph considered as a finite automaton, or the language accepted by the context free grammar that can be derived from the type graph. Here, it is understood that the nodes labeled 'Int' represent the set of integers, the nodes labeled 'Real' the set of real numbers. The node labeled _1_represents the empty set of terms. The node labeled 'Max' represents the set of all terms, including variables and partially instantiated terms. Another approach is related to the scheme presented by Yardeni and Shapiro in [85] where a subclass of logic programs, the Regular Unary Logic (RUb) programs, is used as a specification language for a class of regular tl/pes. A R U L program is a logic program satisfying certain syntactic rules: every predicate is unary, no two head arguments of clauses of the same predicate are top-level unifiable, every body goal is of the form p(_X) with p a predicate name and _.X a variable, every variable in a clause occurs exactly once in its head and once in its body. Given a type graph as defined above, a unary Prolog predicate can be associated with every node of the type graph, and a set of defining clauses can be constructed according to the structure of the type graph. Consider for example the type graph T1 of Figure 2.4, and number the nodes in a depth-first left-to-right manner, assigning number i to the root node. If we associate with node i the predicate Pi, then we get the following Prolog program. pI(-X) : - ( p ~ ( _ x ) ; p s ( - x ) ) . p2 (nil). p z ( ' . ' ( - X , -Y)) : - integer(1),
PI(-Y).
After a few partial evaluation steps, we obtain a RUL program. pl ( n i l ) . pl('.'(-X,
-Y)) : - i n t e g e r ( _ X ) , PI(-Y).
P2 (nil).
pS('.'(-X,
-Y)) : - i n t e g e r ( _ X ) , pI(-Y).
The predicate associated with a node labeled .J_ would be 'fail',and a node labeled 'Max' would bc associated with a predicate generating all the terms that can be constructed from the functors and constants in the source program, the variables, and the terms in the primitive types (e.g. Int, Real). The meaning or denotation of the type graph T1 coincides with the meaning of the predicate p l / 1 , namely, the set of all lists of integer elements. A formal definition of the denotation of a type graph based on fixpoint semantics can also be given directly, i.e. without the intermediate step of associating a Prolog program with the type graph. A related approach is taken for instance by Mishra [58], although in a different formalism, to define the interpretation of the regular tree8 that form the basis of his type system. Let Vars be the set of variables that can occur in the terms, and SMa m the set of all the terms that can
2.3. E X A M P L E : I N T E G R A T E D T Y P E A N D M O D E I N F E R E N C E
19
be constructed from the functors and constants in the source p r o g r a m P, the elements of Vars and the terms in the primitive types 9. We assume t h a t each primitive type T represents a set ST of ground terms with depth one, and t h a t these sets are m u t u a l l y disjoint. A n interpretation of a type graph 7- consists of the d o m a i n SMa= of terms and a m a p p i n g t h a t associates a set of terms to each node. I : NodesT ---* 2 SMaz. Equipped with the pointwise defined operations tO and N, and the subset ordering C_, the set of interpretations of a type graph 7- forms a complete lattice. Ii LAI~
:
Nodes:?- --* 2SM~" : n ~ I i ( n ) to 12(n),
Ii FqI2: NodesT- --+ 2SM'~" : n H It(n) Fq12(n),
c_ h r vn E Nodes- : I (n) C_ T h e e m p t y interpretation, defined as
0 : NodesT ---* 2 SMax : n w-~ O, is the b o t t o m element. To define the m e a n i n g of a type graph using fixpoint concepts, we need a m o n o t o n i c operator on the complete lattice of interpretations for this type graph. The next definition first introduces an auxiliary function: given a node of the type g r a p h and an interpretation I, it associates a set of terms with the node depending on its label and the set of terms associated with its child nodes by the given interpretation I. D e f i n i t i o n 2 . 3 . 2 For a type graph T , a node n E NodesT- and I an interpreta-
tion of T ,
E)7-(n, I) =
if else else else else
if if if if
else if
Label(n) Label(n) Label(n) Label(n) Label(n)
= -= = = =
l
lnt Real Maz Or
Label(n) = f
then then then then then
r SInt
SReat S Maz U { I ( m ) I m is a child of n}
then
f(Q,...,tk) [ } tj E I(Child(j,n)),
for all j : l <_j <_ k, k the outdegree of n
"
We get an operator on interpretations as follows. g)~-(I) : Nodesz --~ 2 S M " ' : n H / D z ( n ,
I).
9Note that the Herbrand universe Up of a program P only contains ground terms, so
Up # SMax.
20
C H A P T E R 2. A B S T R A C T I N T E R P R E T A T I O N
We now use this operator in a way that is similar to the use of the immediate consequence operator Tp when defining the meaning of logic programs [50]. The operator ~gT can be shown to be monotonic and continuous (i.e. it preserves the least upperbound of each increasing sequence). The powers of the operator are defined as usual.
~97- T 0(I) -= I, ~9~r T i(I) = ~9:r(~gcr T (i - l)(I)), T ,,,(I) = T i(I).
Here, w is the first infinite ordinal and U is the pointwise operation mentioned above. According to the Fixpoint Theorem of Knaster and Tarski, the operator has a least fixpoint, and because it is continuous, the least fixpoint is given by ~9~r T w(0). Note that K)~- T w(0)(n), called the denotation of the node n, can be an infinite set of finite terms if the type graph T contains backward arcs. D e f i n i t i o n 2.3.3 ( C o n c r e t l z a t l o n o f a t y p e g r a p h ) For a type graph T, we define
TGConc(T) = //97- T w(0)CRoot(T)). E x a m p l e : For the type graphs of Figure 2.4, we have
TGConc(T1) = {nil, [1, 2, 3], [4, 4],...}, TGConc(T2) = {a, f(a), f(b), f ( f ( a ) ) , f(f(b)), g(7), f ( g ( 5 ) ) , . . . } , TGConc(T3) - {a, f(Y), X, [a, Y, b], f ( Z , Z, 3),...} = SMax. When using rigid types, it is not possible to distinguish the set of free variables from the universe of all terms: both are represented by a node labeled 'Max'. Consequently, when the value of a term is changed because of the instantiation of some free variable subterm, then the old type is still a correct description for the new term value. This property significantly simplifies the definition and the correctness proofs of the operations for rigid type graphs. For instance, the unification of two type graphs can be stated in terms of the intersection of the underlying finite automata, for which well-known algorithms exist. P r o p e r t y 2.3.4 The concretization of a rigid type graph is closed under substitution, i.e. for a rigid type graph T and a substitution O binding variables to terms in SMax, we have
t e ZGConc(T) :=~t8 e TGConc(T). The proof is given in [38]. In general, several distinct type graphs can have the same concretization. For instance, both T1 defined above, and T4 defined as T4 ::= nil I '.' ( Int, T4) I'.'( Int, nil), represent the set of all lists of integer elements. This causes the algorithms that check for equality or inclusion of type graphs (relations which are needed by the abstract interpretation algorithm), to be quite complex and inefficient.
2.3. E X A M P L E : I N T E G R A T E D T Y P E AND MODE I N F E R E N C E
21
In order to reduce the cardinality of the domain of type graphs (which is infinite), a number of restrictions on the structure of type graphs are introduced. Some restrictions merely prohibit redundant nodes in the type graphs, while others reduce the number of sets expressible by type graphs. The restricted class of type graphs is better behaved than the unrestricted class, while still highly expressive. In a first step compact type graphs are defined. Essentially, these are type graphs having no nodes with an empty set as denotation, no circular paths consisting only of nodes labeled 'Or', no 'Or' nodes with only one predecessor also labeled 'Or', and no 'Or' nodes having a child labeled 'Max'. The formal definition and a compaction algorithm can be found in [39]. For example, the type graph T3 in Figure 2.4 is replaced by a single node labeled 'Max'. Another restriction that our algorithms rely upon is based on the next definition. D e f i n i t i o n 2.3.5 For a type graph 7- and a node n E Nodes,z-,
PrincipalNodes(n)
=
if Label(n) ~: 'Or' then {n} else U{erincipalNodes(m) I m is a child of n}.
The principal label restriction states that, for all nodes n labeled 'Or',
ml,m2 E PrincipalNodes(n): ml # rn~ ::~ Label(m1) :fl Label(m~). Note that the compactness of the type graph assures that neither rnl nor m2 are labeled 'Max'. The principal label restriction limits the expressive power of the type graphs, but it makes them deterministic as recognizers of terms. For instance, the set of terms {f(a, b), f(c, d)} cannot be represented; it will be approximated by the tuple-dis~ributive set {f(a, b), f(c, d), f(a, d), f(c, b)} [58, 85]. Compact type graphs satisfying the principal label restriction are called
normal type graphs. In the examples, we will also refer to the depth restriction for type graphs. For every function symbol, the depth restriction indicates the maximum number of nodes having that function symbol as label that can occur on a path in the underlying tree Tt~,, of the type graph and that starts from the root node. The restriction is required in order to get a finite subdomain of type-graph environments, and to guarantee termination of the abstract interpretation procedure. The depth bound will also turn out to be an important parameter to tune the precision of the sharing and liveness analysis. The particular value of the bound can be fixed for the whole program, or chosen on a per clause and functor basis according to some heuristic. Normal type graphs satisfying the depth restriction are called restricted type graphs. An algorithm to construct a restricted type graph with a denotation that contains at least the denotation of some given normal type graph, can be found in [39]. The report also presents a formal correctness proof for the algorithm. Unfortunately, rigid types are not sufficiently precise to deal with partially instantiated terms. For instance, in Figure 2.5, T1 is a rigid type graph representing open-ended trees; an empty open-ended tree is a free variable, so its
CHAPTER 2. A B S T R A C T INTERPRETATION
22
T2 Max
T3 Or_
lnt
lnt
Figure 2.5: Some type graphs representing open-ended binary trees. type is 'Max'. However, T1 is not a compact type graph. The compaction algorithm transforms the type graph T1 into the type graph T2, and causes all structure information to be lost. The compact representation, corresponding to mode a (any), does not provide any useful information for code optimization. Therefore, rigid types are generalized to integrated types which are defined in the same way as rigid types, except that nodes with the label 'V' are allowed additionally (see type graph T3 in Figure 2.5). The symbol 'V' represents the set of all variables (i.e. if Labd(n)= V, then /Dr(n, I) = Vars). In general, integrated types are not closed under substitution, and the operations become more complex. Information capturing the dependencies between the values of variables is needed to obtain sufficiently precise operations. We return to this topic in Section 2.3.2, and in the sequel, by type we will mean an integrated type, unless stated otherwise. The structural properties of compactness and depth restriction are also imposed on integrated type graphs. In spite of these properties, there are still different restricted type graphs representing the same set of terms. For instance, there exist several alternative representations for the set of all terms. "TMazI ::-----Max TMaz2 ::---- V l i n t I Real [ a [f(TMaz 2) [ . . . I t(T~=x 2, TMaz2) Here a/0, f / l , ..., t/2 enumerates all the functor labels that occur in the source program. Also, the set of restricted type graphs ordered by set inclusion of the concretizations does not form a lattice, because there does not exist a least upperbound operation. Fortunately, the set of normal type graphs has the algebraic structure required by the framework of abstract interpretation. There exists a preorder relation < for normal type graphs satisfying T1 _< "2"2 4=~ TGConc(T,) cC- TGConc(T2).
Algorithms implementing an Upp and a restriction operation R can be found in [38], both for rigid and integrated types. It is also proved that there are no ascending chains under < in the subset of restricted type graphs. It is beyond the present scope to discuss those operations in detail. The type graphs that we use as the basis for the abstract domain for sharing and liveness analysis, are a special kind of restricted integrated type graphs, in the sense that we assume that none of the nodes is labeled 'Max' (a subgraph equivalent with some explicit representation such as 7"M,~2 must be used instead).
2.3. E X A M P L E : I N T E G R A T E D T Y P E A N D M O D E I N F E R E N C E
23
We also assume that the type graphs have as their root a node that is not labeled 'Or'. Each type graph that we use to represent an execution environment will have its root labeled by either the tupling functor, /), or _1_. 2.3.2
Type-graph
Environments
The type graphs are augmented with sets of constraints expressing dependencies between variables. The constraints play an i m p o r t a n t role in the precision that can be achieved by the primitive abstract operations. The abstract domains that we propose for the sharing and liveness analysis in Chapter 4 and 5, do not rely on any particular representation for the variable dependencies. In order to give the reader some intuition about the issues involved, we describe the sets of constraints as they are used in [40] for the domain of integrated types. Another approach can be found in [83]. First, we introduce some additional notational conventions to select nodes in a type graph. D e f i n i t i o n 2.3.6 For a type graph 7- and n C NodesT-,
h i e selects the node n itself, n/(fl,il)...(fv_l,ip_l).(fp,iv) (with 1 < il < a r i t y ( f ~ ) , . . . 1 < 9th child of the principal node with i v < arity(fp)) selects the ~v label fp of the node selected by n/(fl,il)...(fp_l,iI~_l ). We call e and (fx, i a ) . . . ( f v _ l , i v _ x ) . ( f p , i p ) selectors 1~ and "." denotes the operation of selector concatenation. If there are no 'Or'-nodes on the path between n and the selected node, then the selector ( f 1 , i l ) . . . ( f p - l , i v - x ) . (fp, iv) can be abbreviated as i l . . . i p - l . i v, and is called a determinate selector. If there are 'Or'-nodes on the path, then the selector is called non-determinate and cannot be abbreviated. To select a subterm in a particular concrete t e r m /C, an abbreviated determinate selector can be used, e.g. for ~: = f(g(a, b)), the expression ~ / 1 . 2 selects b. We only use selectors that are well defined for the type graph (or term) considered. A type-graph environment 7- e E AbstrSubD, i.e. an abstract substitution over a domain D = { X ~ , . . . , X,}, is a tuple (7-, SVaI~-, NUniT-, PShrT-) such that 9 The Type component 7- is a normal integrated type graph with its root labeled by either the tupling functor () of arity c, or _1_. In the former case, the value of a variable Xi in a concrete substitution represented by 7-' is m e a n t to belong to the denotation of the node Child(i, Root(7.)). In the latter case, the e m p t y set of substitutions is represented. 9 The Same Value component SValT- is a set of SVal-constraints. An SValconstraint represents information a b o u t what subterms of the values for the variables Xi in a concrete substitution represented by 7-' are known to 1~
C h a p t e r 4, we will u s e a different d e f i n i t i o n o f s e l e c t o r s , u n r e l a t e d t o t h i s o n e .
CHAPTER 2. ABSTRACT INTERPRETATION
24
be identical. An SVal-constraint is of the form {Xi/sl,Xj/s=}, where i r j, and sl, sa are determinate selectors in the subgraphs n of 7" with roots respectively Child(i, Root(7")), Child(j, Root(TT)). The expression {X~/sl, Xi/s2} is allowed if sl r s2. The Not Unique component NUni~r is a subset of the domain D. If Xi E NUni~, then the value of Xi in the concrete substitutions represented by 7-' can have multiple occurrences of some free variable (i.e. internal sharing of a variable). The Possible Sharing component PShr~r is a set of elements of the form {Xi, Xj}, where i r j. An element {Xi,Xj} E PShr~r expresses that the values of Xi and Xj in the concrete substitutions represented by 7-* can have occurrences of the same free variable (i.e. external sharing of a variable). The domain F-AbstrSubD is defined to be the subset of type-graph environments of which the type component consists of a restricted integrated type graph. The SVal constraints are used in the type analysis to prohibit a potential l o s s of precision due to the normal-form of the source programs. The SVal constraints express equality of structured terms, which does not necessarily imply a shared representation of those structures. It is precisely information about shared representations that plays a key role in the application of liveness analysis. The N0n; and PShr constraints provide information about possibly shared free variables only. Therefore, we will develop a more specific formalism of constraints in Chapter 4, concerning the sharing of structured terms. As explained in Section 2.2, a concretization function TGEnvConc : AbstrS-bD --* 2C~ has to be defined. D e f i n i t i o n 2.3.7 ( C o n c r e t l z a t l o n o f a t y p e - g r a p h e n v i r o n m e n t ) Given a domain D { X I , . . . , Xc}, and a type-graph environment 7 . e - (7", SValT",
NUni~-, PShr~r) E AbstrSubD,
TGEnvConc(7.') = 8 = {X~ ~--/C~ I 1 < i < c} for/C -- t/C1,... ,/Cc) ] 0 E ConcrSubD E TGConc(7.) and 8 satisfies the SValT-, NUniT I " and PShr~ constraint8 The fully formalized definitions can be found in [39]. Here, we restrict ourselves to some examples of type-graph environments over the domain D = {X1, X2}. Examples:
1. 7.~ -- (7-1, 0, 0, 0) with 7-1 ::= (f(alb, alc), g(alb, Intlv)). Although the SVal-constraint does not enforce any dependencies between the values of 11An algorithm for constructing the selected subgraph is in [40]. Ln some cases, nodes are duplicated.
2.3. EXAMPLE: I N T E G R A T E D T Y P E AND MODE INFERENCE
25
X1 and X2, identical subterms may occur; e.g. the following substitutions belong to TGEnvConc(T~):
01 = ( x l ~- f(a, a), x 2 ~- g@, 3)}, 02 = ( x l ~- I(~, c), x2 ~- g(~, v)}. 2. T~ = (T1, {{X1/1, X2/1}}, g, g)9 The SVal-constraint enforces the first arguments of the values of X1 and X2 to be identical, so 02 E TGEnvConc(T~), but 0x r TGEnvConc(T~)9
3. T~ = (7-3, { { x l / 1 , x2/1}}, { x l } , { { x l , x 2 } } ) where 7-3 ::= (g(V, ~IV, V), h(V, V, b)). For the free variables U, W, Y E Vars, the following concrete substitutions belong to TGEnvConc(T~): 03 = {X1 +---g(U, a, W), X2 4-- h(U, Y, b)}, 04 = { X l ~-- g(W, W, U), X2 ,-- h(W, U, b)}. Note that the SVa]-constraint enforces the sharing of free variables and that the NUni-component does not allow the value of X2 to have multiple occurrences of a free variable. Note: We use the symbol ::-- if we specify a type graph by means of a grammar rule instead of a triple as in Definition 2.3.1. In general, if b o t h {Xi/$1,Xj/82} and {Xi/819 , Xj/$2.$} belong to some SVal-constraint set, then the latter is in fact redundant. Similarly, if the SValconstraint set enforces the sharing of free variables, it may be possible to simplify the NUni- or PShr-constraint sets9 A normal form of the type-graph environments can be computed such that most of the redundant components are removed, while the denotation is left unchanged. It is beyond the present scope to discuss the issue in detail (see [39]). Also in [39], it is proved that the algebraic structure imposed by the framework is satisfied for the domain of type-graph environments. For instance, a preorder relation _
and T~ $uch that
arit~Label(Root(T1))) = arity(Label(Root(T2))), I9 T~ <__ToT~ ::~ TGEnvConc(T~) C_TGEnvConc(T~),
~. T~ ~TO TGUpp(T~,T~) & T~ <_TOTGUpp(T~,T~). A maximal element for AbstrSubD is T~r = (TT, @,D, D2), where ~VT : : : ("1"-Mare, .., 7-M=x), with TMax an explicit representation of the maximal type graph, and D2 = {{Xi, X j ) [ X i , X j E D & i r j}. A minimal element of AbstrSubD is 7-~. -- (7-• @,@,$), where 7-• ::-- _L. 9
2.3.3
Primitive
Operations
for Type-graph
Environments
In [38, 39] primitive operations AbstrUnify, Procedure-Entry and Procedure-Exit for type-graph environments are defined and proven to satisfy the safety requirements as imposed by the framework of abstract interpretation. In this section,
26
C H A P T E R 2. A B S T R A C T
INTERPRETATION
we summarize some of the basic results. The operations do not deal explicitly with the trivial case of T~, because the result of any abstract operation on the bottom element is again 7"~.. The bottom element represents that the program point it decorates is never reached during program execution. W e firstfix the notation for concrete substitutions that will be used in the remainder of the book. For a fixed enumeration of program variables, Xx,..., X~, a concrete term environment is a term K = I]~1, .... E n ) , which represents the concrete substitution 0 = ~XI *'- El, ..., X,~ ~-- En}. The subterm Ei is also represented as Eft. Recall that a unification call P can have either of two forms: Xi = Xj or Xi = f(Xil,...,Xi#). Therefore, AbstrUnify is defined as a function that is
polymorphic in its third argument: TGUnify(T/n', i, j): returns the type-graph environment 7-o,f resulting from abstract unification of the i th and jth component of T/n. This corresponds to the basic operation X i = X j . TGUnify(~n e, i,f(Q,..., ij)): returns the type-graph environment To~te resulting from abstract unification of the ith component of T/n and a type graph
having root node labeled f and as sons the i~h,..., i~h components of T/,~. This corresponds to the basic operation 3 ( / = f ( X i l , . . . , X i , ) . The unification of type-graph environments is a rather complex operation where the constraint sets play an important role. We do not present any details here, but we will need the following safety results for type-graph environments when defining the abstract operations for sharing and liveness analysis. T h e o r e m 2.3.9 ( S a f e t y of TGOnify(-, i, j) [38]) Let Tin ~ be a type-graph environment and ICin E TGEnvConc(T/ne). Let I < i < j <_ arity(Label(Root(Tin))). I r a = m g u ( E i n / i , E i n / j ) i~ not fail, E o . t = Ein~ r, and 7"o.t' = TGUnify(T/n', i , j ) , then Eont E TGEnvConc(Tont'). T h e o r e m 2.3.10 ( S a f e t y o f T G U n i f y ( - , i , f ( i t , . . . , i j ) ) [38]) Let 7"i,~~ be a type-graph environment and Ein E TGEnvConc(T/ne). Let 1 <_ i, i t , . . . , i j arit~Label(Root(Tin))) such that i, it, . . . . ij are pairwise distinct, and f is a functor of arity j . I f a = rngu(ICi,/i, f ( E i n / i l , . . . , E i n / / j ) ) is not fail, Eo~t = Ei,~ a, and To,,,e =TGUnify(T/n e, i, f ( it, . . . , ij ) ), then Eo=tE rGEnvConc(To,,te).
Before considering the Procedure-Entry and Procedure-Exit operations, we introduce an auxiliary function. Recall that all Prolog programs are assumed to be in normal form, so the arguments of a call (or of the head of a clause), can be given as a subset (Xil,...,Xi..} of the domain of the clause environment {Xz,..., X,~}, and an injection i: {I,..., m } --, {1,..., n}: k ~ i~ can be used to formally define the restriction of the clause environment to the arguments of the call (or the head of the clause).
Definition 2.3.11 For a concrete term environment E = ( E l , . . . , E n ) , and an injection i : { 1 , . . . , m} -~ { 1 , . . . , n) (for m <_ n), we define Proj(E,i) =
(E~(1),..., E~(,~)).
2.3. E X A M P L E : I N T E G R A T E D
TYPE AND MODE INFERENCE
27
In the first step of procedure entry, Procedure-Entry( T/,~e, P ), the type-graph environment T,,,' is restricted by means of a function call TGRestrict(Ti,~', ir to a type-graph environment Tr,t, e for the domain of the call P. Assume T,,,' = (T~,,, SVal~q,,, NUniT-~,~,PShrT"~,~), and T~,, ::= (Tx,... ,T,~). Assume that the call, P ( X i , , . . . , X i . . ) , is determined by the injection i~u. Then T, ot," =
X~aL.
{{W:lsl, Wbls~} E SVair~. I W:, Wb E {X,,,..., X~..)}, NUniT",,tr = {W= E NUnir~,, I Wo E { x , , , . . . , x . . } } , PShrcrr,t. -- {{Wa, Wb} e PShrcq,~ I Wo, Wb e {X~,..., X,..}}.
SPain. ~ =
The safety requirement of the first step of procedure entry is stated in the following theorem. T h e o r e m 2.3.12 ( S a f e t y of TGRestrict [38]) Let ~ , t be an type-graph environment and /C,,, E TGEnvConc(Ti,,'). Let ari(l/(Label(Root(T,,))) = n, and i~o, : 0 , . . . , , ~ , } --. { 1 , . . . , ,~} ~,~ i,qe~o,~ (fo,. m _< ,q, ~,~d Jc~,~ = p~o~(~:,., i~u). If T,,t~' = TGRestrict(Ti,', i~u), theft/E~,t~ E TGEnvConc('Trstr'). In the second step of procedure entry, T~,t~ ~ is renamed for the domain of variables for each of the clauses matching with the call P and the local variables of those clauses are initialized. Assume that T~,t,~ = (T,,t,, SValTrstr, NUniT, str, PShrT- t,), and T~,t~ ::= (Tt . . . . , T,~). Assume that the cai~ is P ( X 1 , . . . , X,~,), and the jth clause P(Z{ , .. . g ~ ) : .- s t , . .~ BJq j '. with domain , {Z~, , Z,~,J Z,,,+I, . . . , Z ~ , } . Then T~ = (7-j, SVal~rj, NUni~-j, PShrT-j), where Tj ::= (Tx,... ,Tin, V, .. ., V), which reflects that the local variables {Z,,~+x,..., J Z~,} are free variables on procedure entry. Also the constraint sets are renamed for the domain of variables of the clause. 9
.
sv~% = ((Z~/s~, g ~ l ~ } l I X ~ l ~ , X ~ l ~ ) NUniT-j = {Z~ I X~ E NUniTrst~ },
~ sv~ln. A,
PShrT-, = {{Z~, Z~} I {Xo, X~} E PShrT.,t~}. This means that the sharing and same-value relationships existing for the actual parameters are passed on to the formal parameters, but that the local variables of the clause are not involved in any new sharing, nor in the same-value relation. Proving the safety of the second step of procedure entry is straightforward. Procedure exit, Procedure-Exit( T~,', {To~d . . . 7 - o ~ } , P), can be applied when the final type-graph environment To,t~ has been derived for each clause defining P. The initial step of procedure exit computes a type-graph environment 4,0t~~ for the domain of the call P. We have T~,t~" - T G U p p ( { T ~ , .. . , T p r} ) , where T~ is the renaming to the domain of the call of TGRestrict(To,t~, i~,,dj), for 1 < j < p. The injection ih,,dj specifies how to restrict the environment of the jta clause defining P to the head of that clause. Note that for the type
28
C H A P T E R 2. A B S T R A C T I N T E R P R E T A T I O N
component and for a concrete term environment the renaming is done implicitly by the injection. The next theorem reformulates the safety requirement as introduced in Section 2.2.3 in terms of type-graph environments and concrete term environments. It states that if P is called with a concrete substitution represented by the term environment /Ci,, E TGEnvConc(Ti,i e) and is solved by means of the k th defining clause for P which returns the term environment/C E TGFnvConc(7-ont~), then the projection of the latter term environment on the arguments of the head of that clause is described by the abstract type-graph environment T~~ e. The safety of the first step of procedure exit follows from the safety of TGRestrict, the properties of the TGUpp operation and the monotonicity of the concretization function TGEnvConc. T h e o r e m 2.3.13 ( S a f e t y of first step o f p r o c e d u r e exit) Let ~,=', and Toft~,..., To,,t~ be type-graph environments, and/Ci,~ E TGEnvConc(~,=e), /C E TGEnvConc(7"o=t~) for some k such that 1 < k < p. Let arity(Label(Root(~,,))) = n and for 1 _< j < p : mj = ar/tl/(Label(Root(Toffitj))). Let m be the arity of the call P, iaan: { 1 , . . . , m } --* { 1 , . . . , n } an injection defining how the environment of P is mapped into the environment of the calling clause, and i~,adj : {1,...,ra} ---, { 1 , . . . , m j } the injection relating the environment of the jth clause defining P, to the environment of its head. Suppose that (3or over Vars(Proj(/Ci,=, icail)) : Proj(/Cin, ir a = Proj(/C, ih,adt=), and the only variables of ICi~ occurring in a also occur in Proj(/Ci,,, ir Then if ~,t~ ~ = TGUpp({7-~.,...,~r~}), where 7-~ is the renaming to the domain of the call of TGRestrict(7"o,,t~,ih,adj) for (1 < j < p), it/ollows that Proj(g~,ih,=dk) E TG EnvConc(7",.~ Proof. By the safety of TGRestrict (Theorem 2.3.12), we have Proj(/C, ih,,=dk) E TGEnvConc(7-~). By the properties ofTGUpp, 7-~=
In the second step of procedure exit, the extension operation TGExtend, reintroduces the other variables of the domain of the calling environment and makes explicit the effect on those variables of the execution of the procedure: 7-o,f TGExtend(7~,, e, ~,t~', it=n). Due to sharing existing prior to the call, the value of variables not involved in the call can be changed too. Hence, the extension operation has to derive how their type component and the constraint sets are affected by executing the call. We do not present any details here. The safety requirement of the second step is stated in the following theorem. T h e o r e m 2.3.14 ( S a f e t y of TGExtend [38]) L e t ~ , , ' , 7-~,t," be type-graph environments and IC~,= E TGEnvConc(~,=*), /C~.a, E TGEnvConc(7"ra,'). Let arity(Label(Root(~,=))) -- n & arity(Label(Root(T~,,~))) "- m & m < n, and i t , n : { 1 , . . . , m } - * { 1 , . . . , n } an injection such that (3a over Vars(Proj(/Ci,=, icali)) : Proj(/Q,, it=n)a - IC,,t,) and the only variables of ICi,= occurring in cr also occur
2.3.
EXAMPLE:
INTEGRATED
TYPE AND MODE INFERENCE
29
in Proj(/Ci., ic~zz). I f ICo,,t = lC,~,, a and ~o.t' = TGExtend('Ti.',/V.,t.', icau), then /Co~t E TGEnvConc(To.t'). For the development of an abstract domain for sharing and liveness analysis (Chapters 4 and 5), we regard the set of type-graph environments and the operations TGUnify, TGRestrict and TGExtend as an abstract data type and its operations. The safety of the abstract operations for procedure entry and procedure exit will be based on the safety results for type-graph environments.
Chapter 3
Related Work In this chapter, we review some of the papers that inspired our design of the abstract domain. In the first section, we consider several approaches for 8hating analysia, the prerequisite for liveness analysis. In the second section, we summarize related research on determining the likeness of data structures, both in the context of logic programming languages and for functional languages. The last section discusses experimental results of code optimization based on run-time properties derived by abstract interpretation.
3.1
Aliasing and Pointer Analysis
Aliasing (or sharing) information provides the basis for a variety of program analysis applications such as occur-check reduction [43, 56, 57, 68, 70], groundness analysis [17, 19, 22], mode and type analysis [28, 38, 40, 44, 57, 83], independence analysis [32, 37, 46, 63], and liveness analysis [9, 13, 15, 62]. Previous work on aliasing in logic programming languages mainly addressed the sharing of (unbound) variables. Indeed, for applications such as occur-check reduction, mode, type or independence analysis, tracing the dependencies between free variables plays a key role in preserving the soundness of the analysis, because variable bindings propagate through shared variables. On the other hand, for the application of liveness analysis, the sharing of structured terms becomes relevant as well. The sharing of free variables was first studied for the application of occurcheck reduction. Before unifying a variable X with a term $, according to the general unification algorithm, it must be checked whether X occurs in t. If X is a proper subterm of t, the unification process should halt with failure. If the unification algorithm omits the occur check, e.g. for reasons of efficiency, then structures having loops may be created. For instance, when t is the term J(X), the infinite term f ( f ( f ( . . . ) ) ) is produced. Colmerauer [20] introduced a theoretical model of Prolog for using infinite trees, but such a new semantics of terms is not always desirable for ordinary Prolog applications (see [54] for fur-
32
CHAPTER 3. RELATED WORK
ther discussion). Plaisted [68] described a general method for detecting places in a Prolog program where structures with loops might be created and proposed a preprocessor for adding tests at such places that cause subgoals to fail if they return infinite terms. W e discuss Plaisted's method more extensively than others because it first introduced the idea of alternating paths (a closure operation for propagating the sharing relation without full transitive closure). This concept inspired our definitions of the primitive operations (Section 4.1.2) for the derivation of sharing information that is sufficiently precise for the application of liveness analysis. The abstract domain used by Plaisted is based on the so-called Binary Term/Literal Schemata. In essence, these schemata provide a finite way to represent infinite sets of terms/literals. The schemata retain partial information about term instances: some of their subterms are represented by term locators r~, causing information about substructures and the multiplicity of variables to be lost. A given term locator may occur more than once in a binary term schema, indicating more than one occurrences of some subterm. The information that is relevant for detecting possible infinite loops, namely the pairs of positions in the subterms that contain common variables, is represented by means of functions returning (an approximation of) the multiplicity of variable occurrences. 9 Rep(ri,rj): returns the number of repeated variables between locators ri, rj (i and j may be identical). 9 Is(ri): returns the number of isolated variables of term locator r~, that is, variables that occur only in r~ and nowhere else. 9 VC(ri): returns the total number of occurrences of variables in ri.
These values m a y be integers in the range 0 through b, where b is some bound. A value of b indicates the possibility of b or more such variable occurrences. Multiplicities of variables above this bound are not distinguished. A term t is a binary schema instance of binary schema s, if all the locators ri in s are systematically replaced by terms t~, such that the number of distinct c o m m o n variables between t~ and tj, for i r j, is less than or equal to Rep(ri, rj), unless Rep(r~, rj)--b, in which case the number of distinct c o m m o n variables is unspecified; and similarly for the Is and V C functions. W e illustrate the concept by means of an example. Consider the binary literal schemata P(rl, r4, rs, c) and P(g(r2, f(rs)), rs, rs, rz), where the r~ are term locators. Suppose that the functions Rep, Is, and V C are specified by: Rep(ra, r4) = Rep(r2, rs) = Rep(r2, rs) = Rep(rz, rl) - 1, and VC(rl) = VC(r2) = 2, VC(r3) = VC(r4) --- VC(rs) = VC(re) = VC(rz) = 1, IS(rl) = Is(r3) = Is(rz) = i, and all other values zero. The literals P(g(X, U), X, Y, c) and P(g(h(V, Y), f(Z)), V, Z, W) are instances of the above binary literal schemata respectively. We now informally describe how the general unification algorithm is extended i n order to unify term schemata containing term locators. The first part of the algorithm applies the usual unification algorithm [47] until no more can be done. The next part of the algorithm deals w i t h term Iocators. The first
3.1. ALIASING AND POINTER ANALYSIS r4
rl
/ ## ##
/ #
## I -
#l #
r3'
1#
II # r5
33
-
I
I
r3"
r2
Figure 3.1: Detecting loops by constructing alternating cycles.
and second step consist of several rules to adjust the Rep, Is and VC functions reflecting their behavior under unification. For example, when a term locator is unified against a ground term, its variable count VC is set to zero. This in turn will influence other Rep relations. When unifying the example binary literal schemata given above, Rep(rT, rl), Is(rT) and VC(rT) will be set to zero, because the term locator'r7 is unified against the constant 'c'. We do not discuss these rules in further detail as there is no direct relationship with our work. The last step of the algorithm proceeds by constructing a graph, the nodes of which are t e r m locator occurrences of the term/literal schemata being unified, and edges representing the Rep relation. Note that a term locator m a y label more than one node in the graph if it has several occurrences in the term schemata being unified. In the example, there are two occurrences of the term locator r3; we distinguish them as r~ and r~'. The resulting graph is augmented with unification edges (connecting nodes labeled with t e r m locators having non-zero variable count VC, such that one of the locators occurs in a term that the other locator is unified against) and label edges (connecting nodes labeled with the same t e r m locator). Figure 3.1 shows the result of this step when unifying the above example binary literal schemata. Unification edges are represented as dashed edges, Rep edges as full lines, and label edges as full lines marked 'lb'. The alternating cycles of Rep and label edges on the one hand and unification edges on the other hand, indicate places where circular terms might be introduced by the unification. For Figure 3.1, we have such a cycle r4 - r5 - r2 - rl r4. Indeed, this reflects the loop that is created when unifying the instances P(g( X, U), X, Y, c) and P(g( h(V, Y), f( Z) ), V, Z, W) mentioned above. The loop rl - r2 - r6 - r~' - r~ - rl is not an alternating cycle, but it could be interpreted as indicating the possible creation of internal sharing in terms represented by the t e r m locator rl. Unfortunately, Plaisted's paper does not address soundness proofs for these operations; the intuition behind the graph transformation rules is not always apparent. We do not use the concept of term locators in our abstract domain, because it does not provide a proper abstraction for representing sharing of term structures. However, for the specifications of our abstract operations, we borrow the idea of alternating paths to propagate the sharing relation. Roughly speaking, the Rep and label edges correspond with the sharing (or old) edges in our application, the unification edges to the binding (or new) edges (see Section 4.1.2).
CHAPTER 3. RELATED WORK
34
The paper of SCndergaard [70] is concerned with the same problem of determining circumstances under which the occur check may be safely dispensed with. His method is a simplification of Plaisted's within a framework of topdown abstract interpretation for Prolog programs. He does not use the concept of term locators. Informally, variables are aliased or share if in some execution of the program they may be bound to terms which contain a common variable. Abstract substitutions are pairs of the form A = ( G , R ) where G is a set of variables and R is a binary relation on variables. The intended meaning is that A describes the set of all substitutions, each of which makes (at least) all the variables in G ground and has no more sharing than that specified by R. A pair (X, Y) E R, where X ~ Y, allows X and Y to share a variable, while (X, X) 6 R allows X to have some multiple variable constituent. Note that R is s y m m e t ric but not necessarily reflexive. No sharing of ground terms is represented (if X 6 G then for any variable Y, (X, Y) ~ R). When restricted to a finite set of variables (e.g. the variables of some program clause), the domain yields a lattice of finite height under the lexical ordering based on set inclusion. Sondergaard further introduces the concept of preunification. It is a kind of partial evaluation of the unification of predicate arguments which derives templates for each pair of subgoal and clause heading. The abstract unification algorithm is only described informally via a series of examples. Codish, D a m s and Yardeni [17, 19] use the same notion of abstract substitution as defined by SOndergaard, but in a framework for b o t t o m up abstract interpretation of logic programs. Their contribution provides a rigorous semantics for the analysis and a soundness proof for the abstract unification algorithm. We include some of the formal definitions here. The set Sub represents the set of idempotent concrete substitutions, defined in the usual way. The set Vats denotes the set of variables and PVar C_ Vats denotes the set of variables that may occur in programs. The set of abstract substitutions, ASub, is the set of pairs (G, R) G 2 pvar x 2 (PwrxPvar) for which 1. G is finite 2. R is symmetric 3. R n ( G x P V a r ) = O Let ASub_l_ = ASub U {A_}. The abstraction function, a : 2 s~b ---, ASub• defined by a(0) = _1_,and for @ :/: 0, a ( | = (G, R) where G
=
["] { x
I va
s(xo) = o},
BE|
R
=
U 0co
(X,Y)
(X r Y A vars(XO) N vars(YO) r O) V (X = Y A XO contains two occurrences of a variable)
.
is
3.1. ALIASING AND POINTER ANALYSIS
35
The ordering E on ASub• is defined by A_ _E (G, R), and (G1, R1) _E (G2, R2) iff G1 _D G2 and R1 C_ R2. The concretization function, q, : ASub• ---* 2 s~b, is defined by -y(_l_) = 0, and for A r _k,
For example, let X , Y 9 PVar, U E Vats \ PVar, and consider the concrete substitutions 0 = {X ~-- f(U, U), Y ~-- g(U)} and ~o = {X ~- b}, then c~({O, ~a}) = (0, {(X, X), (X, Y), (Y, X)}). Note that the internal sharing in the variable X is represented by a reflexive pair (X, X) in the sharing relation. Also, the sharing relation is not transitive; e.g. an abstract substitution A = {(X, Y), (Y, Z)} specifies potential sharing between X and Y and between Y and Z, but not between X and Z. The core of the analysis is the abstract unification algorithm, which is a transition system mimicking the concrete algorithm for finding the most general solution of a set of equations. It is divided into two parts. The input is a set of equations, together with an abstract substitution. The first step (called preunification in [70]) solves the concrete part of the equations and corresponds to the standard Herbrand algorithm for solving a set of equations. The second step of the algorithm solves the abstract part of the equations. The solution of a single abstract equation cA, where e =_ tl = t~ and )~ = (G, R), is approximated by considering the variable multiplicity of tile equation. For example, if one side of the abstract equation is ground then the solution grounds all other variables and causes no sharing; i.e. if vats(t1) C_ G or vats(t2) C G, then e,~ results in the abstract substitution ,V = (G tJ vats(e), R \ vats(e)), where R \ S is a shorthand for R \ ((S x PVar) tJ (PVar x S)). In this way, the propagation of groundness is reflected. In the other cases, aliasing propagation is required. For example, if e - P ( X , Y , Z ) = P(A, f(A, B), B) and A = (0,0), then we get the abstract substitution ,V = (0, R), where R = {(A, X), (A, Y), (B, Y), (B, Z), (X, Y), (Y, Z)}. Note that no sharing between A and B is derived. Indeed, such sharing could only be introduced by the unification if there was internal sharing in Y prior to the unification. We refer to [17, 19] for the formal definitions of the abstract unification algorithm and its soundness proof. The concept of alternating sequences (or mixed paths) is implicitly used in tile algorithm. In the sharing analysis approach of Jacobs and Langen [37] and Muthukumar and Hermenegildo [63], a concrete substitution 0 for the set of variables in a clause, PVar, is abstracted by means of a set of sets of variables. First, each variable U in the universe of all variables, UVar, is associated with the set of variables through which it occurs in the given substitution 0.
occs(o, u) = { x I x 9
u e var4XO))
The substitution 0 is then approximated by an abstract sharing defined as follows. A:
--, 2
:0
U) I V c UVar}.
CHAPTER 3. RELATED WORK
36
For example, the substitution 8 = {X *-- I(U, V), Y ~-- g(V), Z ,-- a} is represented by the abstract sharing A(8) -- {0, {X, Y}, {X}}. Intuitively, variables that occur together in a set have a variable in common, variables that do not occur in any set are ground. The abstraction function .4 is extended to sets of substitutions as follows.
2
- , 2 Pv ' : e H L3,4(0). 060
The set of abstract sharings can be ordered by set inclusion C, and set union U yields a safe least upperbound operation. The concretization function 7 maps an abstract sharing to the set of all substitutions it approximates.
. . 7 : 2 2Pva ~ 2
: A H {0 I A(0) c_
The abstract substitution which makes all program variables in a clause ground is {0}. The bottom element in the lattice is 0, representing failure. The top element in the lattice is the powerset of all the clause variables. In this abstract domain, internal sharing within a variable is not represented explicitly. This causes a loss of precision in the unification algorithm, as was pointed out by Codish et al. in [17]. Consider the unification X = f(Y, Z) and an abstract sharing Ai,~ = {{X},{Y}, {Z}}, i.e. neither of the variables is ground, nor shares any free variable with the others. A substitution such as 8 = { X ~ f(U, U), Y *-- g(V), Z ~-- W}, belongs to the concretization of Ai,~. Consequently, sharing between Y and Z has to be derived by the abstract unification, even for those cases where no internal sharing in the variable X is possible (Ao~,, = {{X, Y}, {X, Z}, {X, Y, Z}}). A formalization of the abstract unification algorithm and the other primitive operations required in the framework of Bruynooghe [11], is given by Muthukumar and Hermenegildo in [64]. There they argue that the combined determination of sharing and freeness information yields operations that are more precise than if they were computed separately. Freeness information allows to distinguish between variables that are just bound to another variable and variables possibly bound to a complex term. For that purpose, they use abstract substitutions consisting of two components: a sharing component as described above and a freeness component given by means of a mapping from PVar into the set {G, F, N F } , where G, F, N F represent respectively ground, free, and potentially non-free. The second component thus provides the mode information discussed in the previous chapter. The interaction between the two components in the unification algorithm increases the accuracy of the analysis to some extent. For instance, if X, Y, Z are known to be free before the unification
3.1. A L I A S I N G A N D P O I N T E R A N A L Y S I S
37
X = f ( Y , Z), then no sharing between Y and Z will be derived. However, if X has mode N F , but no internal sharing, imprecision will still be introduced by the abstract unification.
The combined sharing and freeness analysis presented by Muthukumar and Hermenegildo is intended to be applied for the determination of run-time goal independence. Many parallel Prolog implementations restrict to Independent/ Restricted And-Parallelism (IAP). In IAP subgoals in the body of a clause are executed in parallel provided they are independent: that is, if further binding of one goal cannot cause further binding of another goal, they can be executed in parallel. Compile-time knowledge about the variables that may share, can be used in such systems to eliminate run-time goal-independence checks. In stricL independent and-parallelism, only goals which do not share variables are executed in parallel. Freeness information is used for the detection of non-stricf goal independence, which allows parallelism among goals that share variables, provided they do not compete for the bindings of such variables. This will be the case for instance if for one of the goals it is derived that variables that are free before the call are still free after the call [84]. The basis of the sharing analysis presented in our work is the integrated type and mode inference system discussed in the previous chapter, which was developed by Janssens and Bruynooghe [38, 40]. In that system, types represent sets of possibly non-ground terms. Groundness as well as freeness information is obtained by the analysis. In order to handle variable dependencies, sets of 5Val, PShr and NUni constraints are used. The SVal constraints represent program variables having the same, possibly non-ground, (sub)term values. Such a constraint can imply sharing of free variables, but does not provide an indication for the sharing of structures. The PShr and NUni constraints abstract possible shared representations of free variables, between different clause variables (external sharing), and within a clause variable (internal sharing), respectively. The sharing of (partially) instantiated structures is not represented. However, the type graphs derived by the analysis, provide us with the structure information that is essential for expressing the sharing of term values with an adequate level of granularity. Such structure information is not derived by any of the sharing analysis methods discussed earlier. Bruynooghe et al. [9, 10, 13] discuss the kind of analysis needed to enable compile-time garbage collection and introduce a formalism for representing sharing of term structures and corresponding inference rules. The technique differs from previous methods in that the abstract domain captures information about the sharing of general term structures: sharing of ground terms and partially instantiated terms, as well as variable sharing. A relation A[. (alias) is introduced such that XAI.Y represents possible sharing of tile instantiations of program variables X and Y. The expression Comp(7-, X) represents parts of instantiations of X having type 7- (for T a component type of the type of X)'. It can be used to express potential sharing of parts of instantiations (e.g. Comp(T, X) Ak Y represents possible sharing of the instantiation of Y and a part of the instantiation of X having type T). The nesting of expressions, e.g. Comp(T1, Comp(T2, X)),
38
C H A P T E R 3. R E L A T E D W O R K
is not allowed because of the finiteness requirement for abstract domains. The inference rules are transitive-like operations propagating the alias relation. For example,
Comp('2"l, X) AL Y Comp(T2, X) AL Z
& &
Comp(']-2, Y) AL Z.
T2 is a component type of T1 Such rules can cause imprecision because they do not fully exploit the structure information available in type graphs for relating the (component) types. The formalism is suited for liveness analysis if expressions of the form Live(X) and Live(CompCT, X)) are added to represent the (parts of) instantiations that may not be deallocate& The correctness of the method was argued only informally. In the paper [60], we formalized an abstract domain based on type graphs, and an abstract unification operation to analyze how the terms to which program variables are bound, can share substructures at run time. Formal proofs of the soundness of the operation are given in [61]. The abstract domain in the present work is an enhanced version of the one given there, yielding more precise results for the extension operation. The problem of structure sharing is significantly different in the context of functional or imperative languages, primarily because the sharing introduced by unification is significantly more complicated to analyze at compile time than parameter passing, term construction and subterm selection. Nevertheless, solutions to related structured-data dependency problems have appeared in the literature. For instance, in order to transform Lisp programs for concurrent execution, Larus and Hilfinger [46] describe a technique for detecting conflicts between pairs of read and write accesses to structures linked by pointers. Their method is based on alias graphs that model a collection of linked structures in memory: the nodes correspond to instances of a structure type; the directed edges represent pointers between the structures; summary nodes are used to join several nodes of the graph into a single node, thereby make it possible to represent unbounded chains of run-time objects. The alias graphs finitely represent the potential sharing of structure, visible at any point in the program. Horwitz, Pfeiffer and Reps [32] describe a similar technique to determine an approximation of the actual layouts of memory that can arise at each program point during execution. Their method is also intended for languages that have pointer-valued variables, heap-allocated storage and a destructive update operation.
3.2
Reference Counting and Liveness Analysis
For functional programming languages as well as for logic programming languages, run-time garbage collection is an expensive process. The substantial prior work on liveness analysis for functional programming, however, does not transfer effectively to logic programming, because of tile differences in the parameter passing mechanisms.
3.2. R E F E R E N C E C O U N T I N G A N D L I V E N E S S A N A L Y S I S
39
Hudak [33] addresses the problem of efficiently implementing aggregates (e.g. arrays) in functional p r o g r a m m i n g systems. He uses abstract interpretation to infer at various points in the source program upper bounds on possible reference counts for structures. Reference counts larger than a certain chosen bound are a p p r o x i m a t e d by the special value oo (with increment ooq-1 --, oo, and decrement oo 1 --* oo operations). A reference count operation is considered as a sideeffect on a store that emulates the real store of an interpreter. The abstraction of the store consists of a finite set of locations, one for each occurrence of a location generatir~g function in the source program (e.g. 'cons' in Lisp-like languages and 'new' in Pascal). The idea is that each such occurrence can be a p p r o x i m a t e d by an operator t h a t generates the same location every time it is called. On function call, the reference count of each actual p a r a m e t e r is increased by one less than the number of occurrences of the corresponding formal parameter in the body of the function. New cells allocated in the store have initial reference count one. This approach is inadequate for logic p r o g r a m m i n g because whether and where new references are introduced upon unification depends on the run-time values being unified. -
Similarly, the work of Inoue, Seki and Yagi [35, 36] handles the d a t a manipulation operations of cons, car, cdr, and p a r a m e t e r passing, but is insufficient to handle general unification. The approach consists of deriving from the functional p r o g r a m text the set of strings of list-type primitive functions (cons, car, cdr) that are possibly applied to program variables. These strings of primitive operations are translated into 'occurrences' that are similar to the 'occurrences' that we use to locate subterms relative to the root of a term (Section 4.1.1). The occurrences (for list-type values only) are expressions of the form {0, 1}*, where 0 selects the head and 1 the tail of a list. Overlapping (shared) cells are cells that have more than one occurrence relative to the root. Sets of concrete occurrences are approximated by a c o m m o n prefix that is a regular expression. They do not aim at introducing destructive assignment locally in one clause but (more generally) at embedding, into the executable program, code to reclaim garbage cells by means of operations that link the detected garbage cells to the list of available cells. However, no full sharing analysis is performed; input arguments are assumed to be non-overlapping. Some sufficient conditions are discussed for newly created cells to be non-overlapping. In this way, the cells of the input arguments cannot be reused and no information is derived a b o u t sharing between the results of different functions with shared input arguments. Another shortcoming of the method is that it derives assertions efficiently for the selectors 1" and 0* only. Jensen and Mogensen [42] define a domain of contezt8 to count the usage of expressions in a functional language and discuss a strategy for reusing cells via a compile-time free-list of cells that are no longer used but still addressable by the variables in the current scope. Operations that request new store cells should then be replaced by operations that reuse the cells in this free-list. The contezt semantics consists of the standard semantics instrumented with a mechanism to compute the exact number of uses of expressions. The standard semantics
40
CHAPTER 3. RELATED WORK
is then abstracted away to yield an approximating analysis. They consider the following domain, associating counts to terms and their subterms. D' = {ABS, 0} U {1(dl, d2)ld,, d~ 6 D'} U {2(dl, d2)ldl, d2 6 D'}. ABS represents an undefined context or a value not used in the remaining computation, 0 represents an atomic value, n(dl, d2) means that the value may be structured and the top-level cell may be used n times (one time if n = 1, many times if n = 2), the substructures are used as described by dl and d2. To guarantee termination of the analysis, they use approximations based on the concept of a grammar: The identifiers of an environment are used as nonterminals, and each (context) binding is represented by a production rule. Vataja and Ukkonen [79] consider the problem of garbage collection in the context of backtrack-based Prolog implementations. The key-concept of their analysis is that of a temporary term, which is a structured term that is not shared at some program point with the calling environment. Intuitively, a temporary term is a term needed for the local purposes of some clause. A term is strongly temporary if it is not shared with any term of a backtrack point in the search tree for the clause activation. At the end of the clause activation, the storage space reserved for the strongly temporary terms can be reclaimed. This concept is rather strong and prevents the introduction of destructive assignments in many cases. Also, it assumes some determinacy analysis. On the other hand , the trailing mechanism does not have to be adapted, as is the case in our proposal. Although their approach includes a full sharing analysis (based on depth-k abstractions and color variables), it is described only informally and no correctness proofs are provided. An approach to liveness analysis for the significantly restricted ease of socalled Ground Prolog has been developed by Klu~.niak [45]. Ground Prolog is a restricted form of pure Prolog in the sense that the query must be known, mode declarations must be given for each predicate and the groundness restriction is imposed such that whenever a call is executed, its input parameters must be ground and the output parameters must be free. These restrictions imply that general unification must not be handled and that programs can be converted to a normal form, which is a notation that makes explicit the uses of unification possible in Ground Prolog (term comparison, assignment, subterm selection, term construction). Run-time representations of variable instances are characterized by means of sets of variable names that occur at the left-hand side of construction operations in the source program. Different term construction operations are assumed to create different objects in memory, as is indeed the case in standard Prolog implementations. Based on some elementary observations about the run-time flow of information in a proof tree, inaccessibility criteria for the term representations of a variable instance can be derived. Klu~niak proposes an algorithm for checking whether a program construct likely to release storage (e.g., a selection operation that has in its left-hand side the last occurrence of a variable _V in the clause) does indeed release it. The analysis performs a systematic search for variables which will still be used and from which the value of _V might be accessible.
3.3. CODE OPTIMIZATION
41
The work of Chase, Wegman and Zadeck [15] is not directly applicable to logic programming either. They look at liveness analysis for imperative languages, where full unification is not an issue. Their approach summarizes the linked data structures allocated in a heap by a 8forage shape graph, in which one node corresponds to possibly many nodes in the heap. As in the approach of Janssens and Bruynooghe [12, 38, 40], the authors avoid the loss of information incurred in prior contributions [32, 46] by imposing a k-depth bound beyond which the structure is collapsed to a single node. As for the type inferencing used as the first layer of our liveness analysis, they rather seek to fold the structure into something like a rational tree or tree automaton. The heuristical method they use to fold the tree consists of superimposing only structure cells that are generated by the same instruction. The approach of ]anssens and Bruynooghe has been to impose a fixed bound on the depth beyond which folding is required. The approach of Chase, Wegman and Zadeck in effect enables different bounds to be used for different programs, while preserving the finite ascending chain property of the abstract domain, needed to ensure termination. To derive information useful for compile-time garbage collection, the storage shape analysis is followed by a heap reference counting analysis, during which the nodes of the storage shape graphs are annotated with reference counts from the lattice (0, 1, oo) similar to the approach of Hudak [33]. An extended model of heap reference counting is also suited to reason about acyclic data structures represented by cyclic storage shape graphs.
3.3
Code Optimization
An exhaustive study of the code optimizations that come within reach of the compiler, thanks to the information about the run-time behavior of programs derived by static program analysis, is beyond the scope of the present book. However, in order to evaluate the sharing and liveness analyses, an understanding of some elementary notions of code generation for WAM (Warren Abstract Machine) based Prolog systems [2, 41, 80] and the possible use of liveness information, is necessary. The Warren Abstract Machine is a standard Prolog implementation technique. We briefly sketch the instruction set and the data areas that the instructions operate on. The data areas are the code area, containing the source program, the control area, containing the abstract machine registers, and three areas operated as stacks. The local stack contains execution environments and choice-points and represents the goals still to be executed. The heap contains structures, lists, and value cells created during program execution. The trail records the bindings that have to be undone on backtracking. A value cell consists of a tag and a value. The possible types are references (REF), variables (UNDEF), structures (STRUCT), lists (LIST), integers (INT) and constants (CONST). The registers of the control area determine the computation state. They include argument registers (A~) and temporary registers (X~), used for argument passing and unification, the program pointer P, the continuation pointer
42
C H A P T E R 3. R E L A T E D W O R K
CP, the (top of) heap pointer H, the (top of) trail pointer TR, the current environment E, and the current choice-point B. A n environment corresponds to an activation of some clause and contains information about bindings for the clause variables and about how to continue the execution. For each call to a predicate with more than one defining clause, a choice-point is created on the local stack, containing the information neededon backtracking. In order to reduce the space needed for an environment on the local stack, clause variables are classified according to their local life-time in the clause: variables that occur only once in the clause are called void; variables that have more than one occurrence, but do not need to survive a call, are called temporary; all other variables are called permanent. Only the bindings for the permanent variables have to be stored in an environment on the local stack. Prolog programs are compiled into intermediate code, approximately one instruction per Prolog symbol. The instruction set is classifiedinto get, put, unify, procedural and indezing instructions. The put-instructions put the arguments of a call into the argument registers. The indexing instructions determine the set of clauses that possibly match with the call and create a choice-point if needed. The procedural instructions deal with control transfer and environment handling. The get-instructions take care of unifying a call and the head of some clause. Components of compound terms are encoded by means of the unify instructions. Unify instructions operate in either of two modes: in write mode, they create a new structure on the heap, and in read mode, they perform unification with an existing term. The put_listand put_struct instructions always switch to write mode, the get_Hst and get_struct instructions switch to write m o d e if the corresponding argument register contains an uninstantiated variable. If the corresponding argument register does not contain an uninstantiated variable, then the get_listand get_struct instructions switch to read mode. For instance, if the instruction unify_atom cte is executed in write mode, it allocates a new value cell on the heap, initializesit with tag C O N S T and value field cte, and increments the heap pointer H. Likewise, the instruction u~ify_val Xi copies the dereferenced value cell determined by Xi on top of the heap if it is not a free variable. If the dereferenced value cell represents a free variable, then a reference to that variable is copied on top of the heap. The instruction unify_var Xi allocates an U N D E F value cell on top of the heap and sets Xi pointing to it. Mari~n et al. [52] describe an experiment set up to assess the impact of the information about modes, types, length of reference chains and liveness of data structures on the extent of code optimizations that can be performed by a compiler. The paper illustrates how m o d e information can be used to enhance clause indexing and to specialize the unify instructions generated for arguments known to be input or output (thus eliminating run-time checking). Type information can be used to remove tag-checking instructions, for instance to improve the processing of arithmetic expressions. Knowledge about the reference chain length can be used to deal more efficiently with dereferencing. Liveness information makes it possible to recognize at compile time whether storage occupied by a data structure can be reused to create new data structures in the same
3.3. CODE OPTIMIZATION
43
procedure. The experiments discussed consider only a restricted case of local reuse of garbage cells, namely reuse in a construction operation within the same chunk of the clause where the garbage is created. A chunk is defined as either a chunkwithout-head or a head immediately followed by a chunk-without-head. A chunkwithout-head is a number, possibly zero, of in-line calls followed by an out-of-line call. In-line calls are calls which use only argument registers of the WAM (e.g. built-in primitive predicates). Consider the following clause. append(l/,
_Y, _Z) :- _X = [E I _U], _Z = [_E I _W], append(_U, _Y, _W).
The clause consists of one chunk only, so the method will be applicable. However for the recursive clause of n r e v / 2 , the selection and the construction operation belong to different chunks because nrev(_U, _RID is an out-of-line call.
nrev(l/,
_Y) :- _X = [_E I _U], nrev(_U, _RU), -Last = [_E], append(_RU, _Last, _Y).
Within a chunk, a t e m p o r a r y register suffices to keep track of the garbage cell until it is needed. Because the contents of these registers does not survive the call of a Prolog predicate, a more sophisticated technique is needed in general to benefit from the garbage detected by program analysis. For instance, by changing the classification of the clause variables, a permanent variable on the environment stack can be used to keep the address of the garbage cell until it is needed; or m a y b e some kind of inter-procedural register allocation can be useful. We will discuss some examples in Section 5.3.3. The scheme of code generation proposed by Marii~n et al. [52] for reusing garbage cells is as follows. 9 If there is a t e r m which can be reused, save a reference to that term in a t e m p o r a r y WAM register before doing the unification. 9 Before constructing the new term, set a register, D, to the saved structure. D is the destructive overwrite register. 9 Proceed in destruct mode: - If an argument is the same, generate a destr_unify_skip instruction. - If it is different, trail if needed (both value and cell address) and perform the unification as if it were write-mode, but using D instead of H (generate destr_get, destr_unify instructions instead of the usual get and unify instructions). The best results are obtained when a term must be constructed with the same functor as the garbage cell and which has m a n y arguments in common. Basically, some additional move instructions are introduced a n d / o r replace instructions for pushing new terms on the heap.
44
CHAPTER 3. RELATED W O R K
The code was tested by means of a straightforward extension of a commercial Prolog compiler [4]. According to the tests, there is little or no measurable time overhead caused by the new instructions for local reuse and a significant gain can be expected from avoiding run-time garbage collection. Because in this scheme the garbage collection is only partial, we will briefly speculate in Section 5.3.3 on other opportunities for reuse. Further investigations and concrete experiments are needed to evaluate the effectiveness in an implementation. Note that the reuse of storage introduces some new requirements on the trailing mechanism of standard Prolog implementations. The trail records actions that must be undone explicitly on backtracking. Our notion of liveness considers only forward execution of the source program, completely ignoring backtracking. Before overwriting the cell, the unify instructions working in destructive mode have to perform a trail test and, if necessary, trail not only the address but also the content of the field to be overwritten, so that the cell can be completely restored on backtracking. Such an extension of the trail has also been proposed, for example, to assist implementation of delay mechanisms in logic programs [6, 76] and for a scheme allowing the removal of dereferencing code in an optimizing compiler [73, 74]. Of course, value-trailing will be more expensive than the WAM's address-trailing; in fact heap space is traded for space on the trail. A technique of destructive update therefore should avoid reinitializing structure links that are already set correctly, so that they need not to be trailed and a net gain in memory usage will result. Debray [26] considers a code improvement scheme that is complementary to the method described by Mari~n et al. [52]. It is suited to address low level implementational details (at the level of the intermediate code) such as the reduction of tag manipulation, environment allocation and redundant bounds checks. The transformation scheme proposed consists of a pair of dual transformations on flow graphs (called code hoisting) and three generic transformations (code introduction, code elimination, and code migration). Code migration consists of moving a sequence of instructions from one point of a block to another provided certain conditions are satisfied such as the absence of externally visible side effects. The conditions ensure that the behavior of the program is not altered. In Section 5.3.3, we will discuss a few examples where code migration is useful to allow local reuse of garbage cells. Performance measurements from a few prototype optimizing compilers have been reported in the literature. Van Roy and Despain [77, 78] presented the Aquarius compiler for the BAM (Berkeley Abstract Machine), which is a generalpurpose machine extended with a carefully chosen set of instructions to support Prolog implementation. The compiler incorporates a simple analysis scheme deriving modes that distinguish between ground, non-variable, dereferenced and uninitialized variables. Because no alias information is deduced, non-trivial modes are obtained for only part of the predicate arguments (about 56 %). Nevertheless, the global analysis seems to be effective and allows the compiler to remove much of the overhead of the powerful features of logic programming, when they are not used. For larger programs, the quality of the results is re-
3.3.
CODE OPTIMIZATION
45
stricted, and suggests that further work should be done to enhance the abstract domain. In [72, 73, 74], Taylor describes an abstract domain suited for mode, reference chain and choice-point analysis. The analyzer differentiates between different categories of constants, list and compound term types, and keeps track of alias information. The P a r m a compiler developed by Taylor is a native (machine) code compiler for the MIPS RISC architecture; it is not based on the WAM instruction set, but uses a similar memory model. The information derived by global analysis is used to reduce the cost of dereferencing and trailing operations. The statistics presented in the paper on performance improvement and code size reduction are very promising and indicate that sophisticated native code compilers using global data-flow analysis can lead to high-performance Prolog implementations.
Chapter 4
Sharing Analysis This chapter consists of three parts. In the first section, we motivate the design of the sharing environments by first identifying the concrete information of interest and then constructing an appropriate abstraction. The second section contains the formal definitions of the primitive operations for the domain of sharing environments and their soundness proofs. The final section provides a few examples and discusses the strength of the sharing analysis.
4.1
Sharing Environments
The central problem in program analysis of logic programs for compile-time garbage collection is detecting the sharing of term substructure that can occur during program execution. The sharing of structure is a property not of the standard semantics but of the language implementation. To justify our analysis using abstract interpretation, we need a concrete semantics that reflects shared structure. Toward this end, we instrument the standard domain of substitutions with information about the shared structures that would be created by the implementation, and we augment the primitive operations to maintain this information. Implementations [7] share structure among all occurrences of the same variable and introduce new shared structure when unifying two free variables or when unifying a free variable with a term. We show that our instrumented versions of the concrete domain and operations characterize the sharing that takes place in standard implementations. Then we sort out an abstraction of the instrumented concrete domain that leads to sufficiently precise abstract operations for the application of compiletime garbage collection. Next, we give the formal definitions of these concrete and abstract domains, which we consider promising for the purpose of liveness analysis, and not too complex to formally prove the soundness of the operations (see Section 4.2). Finally, we show that the domain of sharing environments has the algebraic structure imposed by the framework of abstract interpretation.
48
C H A P T E R 4. SHARING A N A L Y S I S
4.1.1
Concrete
Representation
of Shared
Structure
In this section, we propose an instrumentation of the standard concrete domain such that it captures an aspect of logic programming language implementations normally not expressed, namely the sharing of structure. Primitive operations for the instrumented domain, mimicking the way concrete implementations introduce sharing, are informally described and illustratedby means of an example. The formal definitions and specifications are postponed until Section 4.1.3 and Section 4.2. W e begin by introducing some auxiliary notions that allow us to discuss the sharing of structure in the representation of terms. Following [34],an occurrence in a term /C is a finite sequence of integers describing a position in K. The indicated position is arrived at by following a path from the root, selecting each arc in the path according to the successive integers of the occurrence. W e use unordered pairs of occurrences to indicate the positions of subterms whose representations are shared. In our notation, c denotes the empty sequence, "." denotes the operation of sequence concatenation, Vats denotes the (infinite)set of variables that can occur in terms, Vats(K) denotes the set of variables that occur in a particular term K, and = denotes syntactic identity. Definition 4.1.1 For a (possiblynon-ground) term IC, the set of occurrences in
~, 0(~), is defined as foUows. o(/c)
_- { d u
{
i.s
I /C - f(/Ct'"" "'/C*)' for s o m e f ~ n c t o r f of ~ i t v ~, (1 < i < ~), and s ~ 0 ( ~ , )
1
"
O(/C) is finite because we consider only finite,non-circular terms. A n occurrence in/C determines a subterm and a functor or variable in/C.
Definition 4.1.2 For a (possibh./ non-ground) term IC and s E O(IC), the subterm IC/s and functor or variable K[s] determined by s are defined as follows. ~c/s
=
if (8 = ~)
then cue ~:~/s', where (s = i.8') and (~: - f ( ~ : l , . . . , ~o)),
K:[s]
=
if (~:18 ~ va,s) then ~ / s cue f, where (~:/s - f ( # c , , . . . , SCo)).
TermShift is a closure operation on sets of pairs of occurrences. W e use it in correspondence with the idea that if two terms occupy the same storage cells in an implementation, then their corresponding subtcrrns also occupy the same storage cells. (Note, ~:/r - ~:/, ~ (r.t e O(r) ~ ,.t e O(~:)).)
Definition 4.1.3 For K a term and P~z a set of unordered pairs (r,s) such that r , s ~_ a(IC) and IClr - ICIa, we define VermShift(/C, Px:) = {(r.t,8.01 (r, s) (~ Px: & r.L s.t E O(/C)}. Some subterms cannot share their representation, while in the implementations for which our analysis is intended, multiple occurrences of the same free variable always share their representation. So, we introduce two concepts to characterize meaningful sets of sharing pairs for a given term/C.
49
4.1. S H A R I N G E N V I R O N M E N T S
D e f i n i t i o n 4.1.4 For a term IC, we call a set Pp: of unordered pairs (r, s) such that r, s E O ( E ) , a pre-sharing component for K: if the following conditions are met.
I. (r, s) E P~ :~ IC/r ___IC/s Only identical subterms can share. ~. (r, s) E Px: ::~ r r s Reflezive pairs are meaningless and disallowed out of convenience.
S. (r, s) E P~: :~ TermShift(/C, {(r, s)}) C P,: The set is closed under TermShift. 4. ((r, s), (s, t) E P~c) & (r ~ t) :~ (r,t) e P~ The relation is almost transitive. D e f i n i t i o n 4.1.5 For a term K:, we call a set P~ of unordered pairs (r, s) such that r, s E O(IC), a concrete sharing component for K: if Px: is a pre-sharin# component for E and it satisfies the following condition, which corresponds to the property that multiple occurrences of a free variable share their representation.
(/C/r = t:/s) ~ (t:/r E Vars) ~ (r 9~ s) ::~ (r, s) E PIc
(4.1)
Recall that we use concrete term environments to represent substitutions. A concrete sharing environment is a concrete term environment augmented with a sharing component. The sharing component gives the set of pairs of occurrences indicating subterms that occupy the same storage cells in the language implementation. We will now illustrate the concrete primitive operations of unification, restriction and extension, for the domain of concrete sharing environments. They compute the sharing of structured terms, as created by standard implementations of logic programming languages. The auxiliary operation TransitiveCIosure is a closure operation used to join pre-sharing components. D e f i n i t i o n 4.1.6 For a term IC and a set Ppc of unordered pairs (r, s) such that r, s E O(IC), we define
TransitiveClosure(Px:) =
(rl,
sn)
3(rx, n x ) . . . (rn, sn) a finite sequence over P~c such that (rl ~ s,~) and I ( V l E ~ : 1 <_l < n ~ st = r l + a )
We first consider the operation Unify(I/Cin , CSharingx:,,,), i, j ) for unifying the i th and jta terms in /Cin in the presence of the sharing component, CSharingx:~,,. If we assume IC,,,/l is the binding of X t , for each g, 1 _< t _< arity(/Ci,,[e]), then Unify((K:i,~, CSharingx:,.),i,j) corresponds to the basic operation Xi = Xj employed in [11]. (The other basic unification operation, X~ = f ( X i l , . . . , X i , ) , is considered in Section 4.2.) Let Unify((K;in, CSharingK:,~), i, j) = (ICo~t, CSharing~:.~,). Then both the term part (ICo,,t) and the sharing part (CSharlngt:..,) must reflect the effect of
50
C H A P T E R 4. SHARING A N A L Y S I S
E
O
h
A
X
h
A
f
g
h
Y
g
I IY.'"
Skeletons
f
g
9
Z
(a)
h
Ihl
/ ~ .... . / N f
I
, LI
Z ....... Z (b) Environments
(c) Skeletons
(d) Environments
I Figure 4.1: Concrete input (a) and output (b) sharing environments for the Unify operation, the DAGs in structure-copying implementations before (c) and after (d) unification, and the representations (e), (f) in structure-sharing implementations.
unification. For the term part, this means applying the most general unifier of ICi,,/i and ICi,~/j, defined for example as in [47] and denoted by mgu(lCi,Ji,ICi,~/j). (Note that, from the definition of substitution application, we have the property, lCo,. = Ic,,,~ =~ o g q ~ ) c_ OgCo.,).) For the sharing part expressing the effect of unification means adding pairs indicating positions where unification has introduced new sharing of subterms. The language implementations that we address create new sharing when binding a variable to an existing structure. The sharing existing in the input term prior to the unification is preserved and extended downwards in the case that the term is further instantiated by the unification (cfr. the TermShift operation). Finally, the new sharing propagates through the old sharing because, for the representations of terms that we consider, sharing is transitive. For example, consider the following term and sharing component (see also Figure 4.1.a).
4.1. SHARING E N V I R O N M E N T S Jc~. CSharingjc,,,
=
(11( _x,
51
f C z ) ), ~ ( g ( _ y ) , _y ))
---- {(2.1.1, 2.2)}
When computing Unify((/Ci,, CSharing~c,.), I, 2), a set BindingPairs is constructed by locating each variable occurrence in the input terms that are to be unified, /Ci,/1 and K:i,/2, and introducing a pair giving the position of that variable occurrence and the corresponding position in the other term being unified (the other of K:~,/1 and /C~,/2 ). Then both the sets CSharing~:,. and BindingPairs are shifted according to the instantiated term ICo,,t. The result is given by the pre-sharing components Old and New respectively. Finally the function TransitiveCIosure generates the concrete sharing component CSharingjco., by propagating the new sharing according to the old sharing existing in the terms.
~oV.t BindingPairs Old New
=
(~C g(~(_z)), ~(_z) ), hC g(~C_z)), ~(_z) ))
---- {(1.1, 2.1), (1.2,2.2)} = C.Sharingjcr U {(2.1.1.1, 2.2.1)}
=
BindingPairsU {(1.1.1, 2.1.1), (1.1.1.1, 2.1.1.1), (1.2.1, 2.2.1)}
CSharingjco,,, = =
TransitiveClosure(Old U New) Old U New LJ {(1.1.1, 1.2), (1.1.1, 2.2), (1.2, 2.1.1),
(1.1.1.1,1.2.1), (1.1.1.1, 2.2.1), (1.2.1, 2.1.1.1)} Figures 4.1.a and 4.1.b show these (/C,,~, CSharing~c,,) and (ICo,,t, CSharingpco.,); sharing edges are represented by dotted lines. The language implementer wishing to use our analysis must know which implementations create only sharing captured by the Unify operation, since these will be the only implementations for which our analysis is suited. In Section 4.2 we give an abstract characterization of these implementations. Here we show that our analysis is suitable for the two most common structure representation schemes, structure sharing and structure copying. The case of structure copying is straightforward. Terms are represented as directed acyclic graphs (DAGs) in memory. The term occurrences are directly applicable on DAGs with the understanding that K:/s selects the dereferenced storage cell. A pair (r, s) in CSharingjc expresses that /C/r and K:/s are represented by the same storage cell in the DAG. This is a variable cell, the address of a record on the heap, or the address of a constant. Conditions 4.1.4.1-4.1.4.4 express the obvious properties of shared storage cells. Condition (4.1) requires that a single variable cell be used to represent all occurrences of the same variable. A typical unification algorithm traverses IC/i and IC/j synchronously until it hits a variable cell and inserts a pointer from the variable to the other element (which can be a variable or a structure). From that point on, the dereferenced variable refers to the same storage cell as the other element. This fact is reflected in the addition of a pair to BindingPairs. See Figures 4.1.c and 4.1.d for an illustration. The case of structure sharing is more complex because a bound variable is represented by a pair of pointers, the first to a skeleton, the second to an environment. This makes the meaning of IC/s a bit tricky. Such a pair of
C H A P T E R 4. S H A R I N G A N A L Y S I S
52
x,
o
h
f
o
h
Z..Z
o
h
f
I
I
g
U
h
f~2_.-f-.5:f
I "'--~'"
h
f
I
U -: .~_-..- U : . . - U --_.-U --i-'~
f
I
h
f~
I
.f-._..-f
I ""k j
V -~~- -__. - Y ~ _. .~- u' . ~
I .~
Figure 4.2: The order in which unification subproblems are solved affects the sharing created.
pointers is selected when /C/s is a non-variable term, while the address of a free variable is selected when /C/8 is a free variable. For example, if/C = (h(_X,f(_Z)) ,h(g(_Y),_Y)) (see Figure 4.1.e), /(;/1 selects the skeleton h(_X, f ( _ g ) ) and the (local) environment with _g and _2, whereas /C/2.2 selects the address of the free variable _Y. The pair (r, 8) 6 CSharing~c expresses that IC/r and /C/s either return the same pair of pointers or return the same free variable address. As in the structure-copying case, Conditions 4.1.4.1-4.1.4.4 and (4.1) express properties of structure-sharing implementations; the creation of a pointer during unification in a structure-sharing implementation is soundly reflected in our instrumented unification by the addition of a pair to BindingPairs. See Figures 4.1.e and 4.1.f for an illustration. The order in which unification subproblems are solved affects the sharing created. An illustration is given in Figure 4.2. In'the top figures, a possible memory layout is sketched for a WAM-based implementation. On the left, a binding state is shown prior to the unification of two program variables X1 and X2. Depending on whether the arguments of corresponding functors are unified in the order from left-to-right or vice versa, we end up with different representations as is shown in the middle and right top figures respectively. In the bottom figures, the corresponding concrete sharing environments are shown. The concrete instrumented unification operation captures the sharing that can be introduced by unifying such dynamic structures in any order. The set of sharing arcs derived for this example is the transitively closed union of the sets of arcs shown in the bottom middle and right figures. In fact the Unify operation yields an upper ap-
4.1. S H A R I N G E N V I R O N M E N T S
53
proximation of the sharing created in any particular implementation. We expect the redundancy introduced by this strategy to be neglectable. Also note that the order in which static substructures are unified is predetermined by the normal form of the program. For instance, the program fact p(-Xa,-.Xx,-Xx) could be transformed to the normal-form clauses p(-Xx, -X2,-X3) :- --Xl=--X2, -X2=-X3, or p(-Xl,-.X2,--Xa):- -.X2=..Xs, -Xl=-X2. We assume that the normal form used reflects the program transformations (e.g. reordering of argument unifications) as performed by the optimization algorithm of the underlying compiler. Next we consider the call Restrict((K:~,,, CSharingjc,~), c a l l ) for restricting the concrete term environment K:i,, and its sharing component CSharing~c,,,, describing the binding state of the program variables at the program point preceding some call, to a description of the binding state for the variables pertaining to the call. This operation is quite simple as we assume that all programs are in normal form, wherein parameter passing is separated from unification. For the calling term environment, K:i,~ = (K:i,,/1,..., I(.in/c), describing the substitution {X1 ~ K:~,~/1,...,Xc *-- K.~,,/c), and the set of variables occurring in the call, e.g. ~ X ~ , , . . . , X i , , , } where 1 < i a , . . . , i m < c and i l , . . . , i r n are pairwise distinct, the restriction operation computes a concrete term environment ICr,t,. = (IC~,~/ia,..., K.~n/i,,~). For the sharing part CSharing~:,,, the restriction operation implies a translation (and restriction) of pairs of occurrences for the term K:i,, into pairs of occurrences for the term K~rstr. For example, consider the following concrete term environment and sharing component (Figure 4.3.a). it,. CSharingjc,.
= =
(~(_u), h( _X, _Y ). _Y, hC gC_z), _z {(2.2,3),(4.1.1,4.2)}
))
Suppose that the call to be executed is P(-X2,-X4), where the predicate P/2 is defined by the single clause P(-Yx,-Y2) :- -YI=-Y2. Computing Restrict((K:i,,, CSharing~:,,),P(_X2,_X~)) then results in the sharing environment (K:r,t,, CSharing~:,,,,) of Figure 4.3.b. lc,,t, = CSharingjc..,, --
(h( _X, _Y ) , h ( g ( _ Z ) , .[(2.1.1, 2.2))
_Z
))
The Restrict operation described is intended for implementations that maintain a stack of active execution environments. When a call is resolved with a matching clause, the current execution environment (with domain the set of variables of the current clause) is pushed onto the stack and a new execution environment (with domain the set of variables of the matching clause) is created. No more sharing is created between the old and new environments than follows from unifying the arguments of the call and the head of the matching clause. The Restrict operation forms the first step in such a switch of execution environments. It reduces the current execution environment to the arguments of the call that are needed for the second step, which consists of the unification of the call and the head of the matching clause. For programs in normal form, this unification is trivial, and merely boils down to parameter passing. In the third step, where
54
C H A P T E R 4. SHARING A N A L Y S I S o
f
h
I XA
U
o
<>
Y
h
,.." gA
Y.."
h
A
Z
X
I/
A
g
I/
Z..."
Z..'
(a)
h
h Y
(b)
o
Z
h
A ..... A
I :-. ...t..'.
z,..9.: ..... :. z. " . . . . . . :: ." (c)
f
h
Z
h
I M-':" :....z.'. (d)
Figure 4.3: Concrete input Ca) and output (b) sharing environments for the Restrict operation. Concrete input (a), (c) and output (d) sharing environments for the Extend operation. the execution environment is extended to the variables of the matching clause not occurring in the head, no sharing is created. Together, they implement an application of SLD resolution, where the matching clause is first renamed such that none of its variables occur in any of the previous derivation steps. Finally, we consider the call Extend((/Ci,, CSharingjq~), (/Cr,~r, CSharingjc..,.), c a l l ) . The binding state (ICr,,r, CSharing~.,.) represents the output concrete sharing environment that resulted from executing some c a l l for the concrete input sharing environment (/C~., CSharing~,~). However, (/C~,~, CSharingg.,,.) is restricted to the variables (the arguments) of that call. The Extend operation extends this restricted binding state to a binding state for all the variables in the concrete calling environment. The operation is similar to Unify. It applies a substitution to the concrete term environment/q,, to obtain the output term environment/Co~t. It then propagates the new sharing (resulting from executing the call) through the old sharing that existed prior to the call. The new sharing is obtained by translating the sharing component CSharingpc.... for the term environment /C,,~ to the term environment /Co~t. The sets of old and new sharings need not be disjoint. For example, consider the following concrete sharing environments (see Figure 4.3.a and 4.3.c). /q,, CSharing~ /c.t~ CSharingjc ....
= = = =
(~(_v), ~( _x, _Y ), 2 , h(gC_Z), _ z ) ) {(2.2,3), (4.1.1,4.2)} ( ~ ( g C _ z ) , _z ), ~(gC_Z), _z )) {(2.1.1, 2.2), (1.1.1, 1.2), (2.1.1, 1.1.1), (2.2, 1.2),
(1.1,2.1),(1.1.1,2.2),(1.2,2.1.1)} The concrete sharing environment (/C~,t~,CSharing~:....) describes the result of executing the call P(-X2,14) used above to illustratethe restriction operation. When computing Extend((Ki,,CSharing~q.,), (K,,~,,CSharingjc..,.), P(-X2,-X4)), the translation of the sharing component CSharingtc_,. back to the term environment/Co~t gives the pre-sharing component New. The function TransitiveCIosure is used to propagate the new sharings according to the old sharings. The result, (Ko~,, CSharing~:o.,), is shown in Figure 4.3.d.
55
4.1. SHARING E N V I R O N M E N T S
/Co.t
=
(~( _U ), h ( g ( _ Z ) ,
Otd
=
((2.2, 3), (4.1.1, 4.2)}
New
=
{(4.1.1, 4.2), (2.1.1, 2.2), (4.1.1, 2.1.1), (4.2, 2.2),
CSharingx:..,
= =
TransitiveCIosure(OId U New) Old U New tJ {(3, 2.1.1), ( a, 4.2), (3, 4.1.1)}
_Z ), _Z, h ( g ( _ Z ) ,
7. ))
(2.1, 4.1), (2.1.1,4.2), (2.2, 4.1.1)}
Note that the sharing edge (4.1.1, 4.2) is both an edge existing before the unification and an edge present in the translation of the sharing component CSharing~: .... to the term environment ICo,,t (i.e. (4.1.1,4.2) E Old n New). 4.1.2
Abstract
Representation
of Shared
Structure
In this section, we discuss the design of an abstract domain for sharing analysis. W e start from a structure that is analogous to the structure of the concrete sharing environments used so far. However, we argue that, in order to get abstract primitive operations that are sufficiently precise for the application of liveness analysis, some further enhancement is needed for the concrete and abstract domains. A set of concrete term environments can bc approximated with a type-graph environment, as introduced in Section 2.3. In the present discussion we augment these abstract term environments with a sharing component. The notions of occurrence and subterm for concrete terms are extended to type graphs, yielding selectors and subgraphs. A selector is a sequence of ordered pairs (i,l). Each pair tellshow to move from one non-Or node to another: i is an integer selecting a child of the first node and I is a non-Or label selecting a (unique) principal node of that child.
D e f i n i t i o n 4.1.7 For a type graph 7-, an integer i E 1N, and f a functor, a constant or V, define 7./(i, f) =
The connected subgraph rooted at n where Label(n) = f and n E PrincipalNodes(Child(i, Root(7.))), if such an n ezists, undefined, otherwise.
An algorithm for constructing the selected subgraph is in [38, 40]. In some cases, nodes are duplicated to obtain a normal type graph. D e f i n i t i o n 4.1.8 For a type graph 7-, define the set of selectors in 7- as follows. S(7.) : {c} U {(i, f ) . S I S ~ s(7.') where 7: = 7./(i, f) is not undefined}. Notice that because of the principal label restriction, 7./(i, f) is unique when it is defined. Also, S(7.) can be infinite if 7" contains backarcs. D e f i n i t i o n 4.1.9 For a type graph 7- and a selector S E S(7.), define the subgraph 7 . / S and the node 7.[S] determined by S in 7-, as follows. f 7(7./(i, f ) ) / S '
7./S
7.[S]
=
Root(T/S).
if if
s = e, S = (i, f ) . S ' ,
C H A P T E R 4. SHARING A N A L Y S I S
56
Notice that selectors always determine nodes not labeled 'Or'. The type-graph analogue of the TerrnShift operation is a closure operation on sets of pairs of type-graph nodes. D e f i n i t i o n 4.1.10 For T a type graph and PT" a set of pairs (r,s) such that r, s E NodesT and Label(r) = Label(s) and Label(s) ~ 'Or', define TGShift(T, P~-) =
{(T[R.T],T[S.T]) J 3R, S , T :
(T[R],7-[S]) E PT & R.T, S.T G S(T)}.
D e f i n i t i o n 4.1.11 For a term IC and s E O(]C), define
l if else
Sel(K:, s) =
s= e let s = i.s I in if ~ l i ~ Vars else if IC/ i E S1.t else if IC/i E SR,=l else
then e
then (i, V) then (i, Int) then ( i, Rea 0 (i, IC[i]).Sel( IC/ i, s').
The Sel function translates the term occurrence s for a term/C into a type-graph selector, e.g., Sel((b,f(a, g(c))),2.2) = (2,f).(2,g). Type graphs are the basis of our abstract term environment. We use type graphs having their root labeled by the tupling functor 0 with arity corresponding to the number of program variables in the domain of the abstract term environment. As is explained in Section 2.3, for the purposes of a type inference system a type graph is augmented with information about what positions in the denoted terms can be occupied by the same variable. D e f i n i t i o n 4.1.12 An abstract term environment 7-e has the form (7-, VSharingT-). 7" is a type graph. The root node o f T , T[e], has label 0. VSharingT"
is an abstract variable-sharing component. The type graph 7" captures the structure of the terms in the set denoted by 7-'. The sharing of free variables within those terms is captured by the sharing component VSharingT-. We do not require any particular representation for VSharingT. We regard T e as an abstract data type and TGUnify, TGRestrict, and TGExtend as its operations. This abstract data type hides the variable sharing component but exports the type-graph environment, 7". For this abstract data type, we assume an operation TGEnvConc, which maps T e to the subset of TGConc(7-) that conforms to VSharingT-. In the sequel, we will also use the following property. P r o p e r t y 4.1.13 For an abstract term environment T e and a concrete term /C E TGEnvConc(T'), we have
4.1. S H A R I N G E N V I R O N M E N T S
57
The property can be proved by induction on the length of the path s, by using
the inclusion TGEnvConc(T') C TGConc(T) and the definition of TGConc. In order to represent abstract sharing of structure, we borrow such an existing abstract term environment and instrument it by analogy to the concrete sharing environment. The instrumentation needs the portion of the abstract term environment that represents term structure. It does not need the portion that represents variable sharing. The borrowed abstract term environment and operations are used to infer what terms program variables can be bound to. The instrumentation is used to infer shared structures in the representation of those terms. D e f i n i t i o n 4.1.14 For art abstract term environment T" corttainirt9 the type graph 7- = (NodesT, ForwardArcs:T, BackArcsT>, we call a set of unordered pairs
(re, n) such that m , n E Nodes~r, Label(m)= Label(n), and Label(m) r ' O r ' art abstract sharing component for T . An abstract sharing component, ASharingcr, for a type graph T, constrains the sharing that may be possible in the terms represented by the type graph T. An abstract sharing environment, consisting of a type graph and an abstract sharing component, characterizes the current binding environment in a Prolog9style computation: free variables in a binding must match variable nodes (nodes labeled 'V') in the type graph; multiple occurrences of the same variable must match type-graph nodes that are connected by a sharing edge (an element of ASharing7-), since the language implementation is assumed to give them a shared representation. Unlike concrete sharing components, which are TermSbift closed, abstract sharing components need not be closed w.r.t. TGShift. The concretization function, defined in Section 4.1.3 below, assures a correct interpretation by first calling the TGShift operation. Note that, while a concrete sharing component contains pairs of occurrences indicating identical subterms, the definition of an abstract sharing component requires only that sharing edges connect nodes having the same label. We opted for the weaker condition in the case of abstract sharing components because it simplifies the primitive operations and safety proofs, while retaining enough of the precision for the liveness analysis. As an alternative, we could define sharing edges to connect nodes determining subgraphs with non-empty intersection (as defined in [38, 40]). An even stronger condition is described below in Section 4.3.2. The weaker condition allows edges in an abstract sharing component that may not be relevant with respect to the type graph they are associated with. We will discuss the consequences for the precision of the sharing analysis in Section 4.3.2. While CSharings: does not contain reflexive pairs, ASharingT- possibly does. Because of backarcs, a node in a type graph can correspond to several subterms in a concrete term represented by the type graph. Therefore, self-edges are significant in an abstract sharing component. E x a m p l e : Figure 4.4.a shows an abstract type graph and sharing component representing open-ended lists whose free-varlable elements may share (note the
CHAPTER 4. SHARING ANALYSIS
58
O
:::" (a)
V
v
V
''"-..= .... .o.-*~176"*..o.........-o~ Co)
Figure 4.4: An abstract type graph and sharing component (a) and an abstract sharing environment (b).
self-edge). In the type-graph representation, all the elements of the list are represented by the same node. The following are among the concrete terms represented: _0, [ I I _U], [_X, _I I -Y], [_3[,_Y, _Z,_Y I _W]. [_X I _X] is not. Also, while CSharingx: is (almost) transitive, AShadngT is not. For example, if -Lz, _L=, and -L3 are (concrete) lists, it is possible for -L2 to share elements with both -L1 and -1,3, although -Lz and -L3 do not share any element. The abstract sharing environment of Figure 4.4.b represents such a concrete term environment ( -L1,-L2,..L3 ). Recall that in the case of concrete term environments, we used the transitivity property for the sharing relation and the transitive closure operation to mimic the propagation of sharing. One of the main consequences of abstracting concrete terms and their definite sharing pairs, by means of type graphs and corresponding possible sharing edges, is that the transitivity property is no longer appropriate. It introduces imprecision that would make the abstract domain inappropriate for the application of liveness analysis. By dropping the transitivity requirement and using an alterr, atin# closure operation, we obtain a level of precision that is satisfactory for several simple data manipulating programs. As discussed in the chapter on related work, the idea of alternating paths was first used by Flaisted [68] in a technique for automatic detection of points in Prolog programs where the occur check might be violated. Later it was also used by Sendergaard for a similar flow analysis [70]. Unlike TransitiveCIosure, AIternatingCIosure adds only the pairs of nodes connected by a sequence of edges in which the sharing edges of the input type graphs and the new edges created by an operation on those graphs (e.g. unification) alternate. The alternating closure of two sets of edges is formally defined as follows.
Definition 4.1.15 For a type graph 7", and two sets B, C of unordered pairs ( R, S) such that R, S E Nodes:T, toe define
AlternatlngCIosure(B, C) =
4.1. SHARING E N V I R O N M E N T S
59
Abs~rUnify(OT,t(L,F,R)) Tin
Or
V
Int
<>
Tout
<>
V
t
V
Or
[st
empty~/~ empty~/~...2t_..." Int
Or
Int
Int
Figure 4.5: Abstract unification: _OT = t ( _ L , _.F, _R).
insert(-E, insert(E, insert(_E,
_0T, _.NT) : - _ 0 T = empty, / = e m p t y , X = e m p t y , _NT = t(_X, _E, _0T, _NT) :- _0T = t(_L, I , _It), _E _NT = t(_RL, _F, _R), insert(_E, _L, _OT, A~T) : - _ O T = t(_L, _F, _R), _E _NT = t(_L, _F, _NR), insert(_E, _R,
_Y). =< I , _NL). > _F, _NR).
Program 4.h i n s e r t / 3
(R1, Sn) I 3(R1, E1)...(P.r,, Sn) a finite sequence over B U C such that (Vg 6 IN : 1 < t < n =~ & = Rt+I) and [Vk6]PC: ( ( l < 2 k _ < n ~ ( R 2 k , Sak) E B ) & (1 < 2 k + l < n ==~(R2t+I, Saj,+I) 6 C ) ) v
((t < 2k <
(l<2k+l
&.) e c)a
=>(Rak+I,S2t+I) eB))]
Consider the first recursive clause of Program 4.1 for inserting an (integer) element into a binary tree. Figure 4.5 shows the abstract sharing environments before and after the selection operation _0T = t (_L, _F, _R), for the case where there is no internal sharing in the input binary tree _0T and that alternating closure is used 1 . If the abstract unification operation were based on the transitive closure operation, the new edges resulting from the unification, (1)-(2), would interact with themselves, and the output type graph would have self-edges in the functor nodes labeled t of both the subgraphs for the program variables _L and _R. So, the recursive call insert(_E,
_L, _//L) would pass an input binary
tree to the program variable _L for which possible internal sharing between its subtrees is derived; hence the opportunity for in-place reuse of tree cells would not be recognized for the recursive call. Of course, using alternating closure does not cure all causes of imprecision, as will be illustrated by the examples in Section 4.3.3. We now discuss the abstract versions of the unification, restriction and extent Depth bound two is used for the tree constructor nodes.
60
C H A P T E R 4. S H A R I N G A N A L Y S I S
T
mO, o
ml ,V
m2,Or
T'
m3,f m6,a
t%
m7,f A
"" mg,V
m9,a mlO,a ~~
(a)
nO,o
nl ,Or n2,f /X n4,V n5,a n6,a n7,f ":..... :,.
n3,f ng,a
n9,f
nlO,a nll,a nl2m nl3,a
..~ ..
"-~176176
(b)
Figure 4.6i Abstract input (a) and output (b) sharing environments for the AbstrOnify operation.
sion operations. The definitionof the abstract unificationis similarto the definition of the concrete unification. Let AbstrUnify((T/,,',ASharingT,,,),i,j) = {To,,t', ASharing~- ,). For the type-graph part, we take To,,t" = TGUnify('T/,,',i,j), where the function TGUnify returns the abstract term environment resulting from abstract unificationof the ith and jth components of T. (See [38, 40, 83].) Let us consider the sharing component. As in the concrete case, structures that are (possibly) sharing before unificationremain (possibly) sharing after unification. However, the abstract sharing component for T/n must be converted into an abstract sharing component for To,,t.To this end, we will define (see Section 4.2) a function Convert that takes the input sharing TGShift('T/,~,ASharingT-,,~),expressed in terms of the nodes of T/,,,and reexpresses it in terms of the nodes of To,,t. Special care has to be taken with edges in TGShift(T/,,ASharingT-,,)that involve nodes labeled 'V'. As the output type graph To,,treflects the effect of unification,nodes labeled 'V' in 7~,~can correspond to nodes in Toat not labeled 'V', indicating variables that got bound to some structure. As in the concrete unification, AbstrUnify introduces new sharing where a variable is unifiedagainst some other structure. These new sharing edges are collected and mapped onto the output type graph To,,tby a function BindingEdges. This function returns an edge for every node in 7"o,,tcorresponding to a node labeled 'V' in the gth or jth component of T/,~. The main differencewith concrete unificationis that the abstract unification uses AlternatingClosure,rather than TransitiveClosureto propagate the sharing relation. The AlternatingClosureoperation adds only the pairs of nodes connected by a sequence of edges in which the old and new edges alternate. Example: Figure 4.6.a represents a type graph T and an abstract sharing component ASharingT- -- {(ml, m8), (m9, m10)}. For the nodes of the type graph, we show both their name (e.g.,m3) and label (e.g.,f = Label(m3)). Note that (..X, f(_X,~(_g,a)), f(a,f(a,a)))and (_~, f(_Y,f(-X,a)), f(a,:f(a,a))) are both represented by (T', ASharingT), but (l, f(_X,f(_X,a)), f(a,f(a, a))) is not, because m8 is not self-sharing. To compute AbstrOnify((T', ASharingT), 2, 3), we first use TGUnlfy(T t, 2,3) to construct the output type graph T' (see Figure 4.6.b). Then we compute
4.1.
SHARING
ENVIRONMENTS
61
Convert(T, ASharingT, T') -- ~[(n5, n6), (n5, nl0), (n12, n13)} -- C, BindingEdges(T, T', 2, 3) = {(n6, n8), (nl0, n12)} = B, AlternatingClosure(C, B) = C U B U {(nS, nS), (n5, n12), (nl0, n13), (n5, n13)}. In Figure 4.6.b the elements of BindingEdges are represented as dashed lines and the elements of Convert as dotted lines. Note that the sharing edge (ml, m8) in T corresponds to two sharing edges (n5, n6), (n5, nl0) in T'. The definition of the abstract restriction operation AbstrRestrict is also very similar to the definition of the concrete restriction. Let AbstrRestrict((Ti, ", ASharing~r~,,), c a l l ) = (Tt,tr', ASharing~r ,.). For the type-graph part, we take T~~ ~ = TGRestrict(T~, ~, call), where the function TGRestrict returns the abstract term environment resulting from the restriction of the term environment T~,," to the variables of the call. (See [38, 40].) For the sharing component, we need a function that takes the input sharing TGShift(Ti,~, AShafing~r~.,), expressed in terms of the nodes of T~,, and reexpresses the edges pertaining to the call in terms of the nodes of T~,tr. Figure 4.7 shows an example input (a) and output (b) of the AbstrRestrict operation for some call p(_Y,_Z,_U). Finally, we consider the call AbstrExtend((Ti,,',ASharingT-,~),(T~,t,', ASharingT_,~), c a l l ) = (To~t ~, ASharingyo.,), for extending the abstract binding state (T~,t~', ASharing=r_,.), which is the result of the abstract interpretation of some call, for the variables of that call, to an abstract binding state for the variables in the calling environment (Ti,', ASharingT~). For the typegraph part, we take To,,t ~ = TGExtend(Ti,~,T,,t,',call), where the function TGExtend returns the abstract term environment resulting from extending the term environment T.~,tr" to the variables of the calling abstract term environment T~,,t. (See [38, 40].) The sharing component is obtained by propagating the n e w sharing (resulting from executing the call), through the old sharing existing prior to the call, using the AlternatingCIosure operation. However, computing the new sharing as in the concrete case, by translating the sharing component ASharingT.,,. for the term environment T , , t , e to the term environment Tout', introduces imprecision. E x a m p l e : ( m o t i v a t i o n for using a t w o f o l d sharing c o m p o n e n t ) Consider the environment of Figure 4.7.a, involving the program variables _.X, _Y, _Z, _U, and the environment 4.7.c representing the result after a call involving three of those variables, e.g. p(_Y,-Z,_U). Suppose the definition of p/3 consists of the single clause p(_Y,_Z,_U):- _U=f(_Z). In Figure 4.7.d, the edges (2), (3) and (4) are the result of translating the sharing component ASharingT.,, of Figure 4.7.c to the term environment 7-o,,te. Treating these edges as new, results in redundant edges (5,6,7,8). (Note that edges (7) and (8) are irrelevant edges in the sense defined below in Section 4.3.2.) Subtracting the old (input) sharing edge (2) from the translation of ASharingy..,. would avoid redundant edges (5,7,8) in this case, but is not safe in general. Suppose the definition of p/3 consists of the single clause p(_Y, _2, _U):- _U=f(7.), _'/=_7,. Then edge (2) corresponds to newly created sharing and edge (5) is not redundant. Remember that abstract sharing edges constrain the sharing possible for a set of terms, while
62
C H A P T E R 4. S H A R I N G A N A L Y S I S
Or
V
V
V
V
V
V
V
V
or ,:~-.y 2
f
/
Iat
V~4
~
"~
Int
\
l~ .
V,.s ~ \ . . "
v...~-) f ..' ,3 I a x
~/
%....~....-" ~ ~.,"".. 6 ..." -..........9
Ca)
(b)
(c)
(d)
Figure 4.7: Abstract input Ca) and output (b) sharing environments for the AbstrRestrict operation. Abstract input Ca), (c) and output (d) sharing environments for the AbstrExtend operation.
concrete sharing pairs represent definitesharing in one particular term. On the other hand, subtracting the new sharing edges from the old can be proved to be sound, but stillcauses imprecision (e.g. the edges (5) and (6)). Hence, the extension operation needs rather precise knowledge about the sharing edges from ASharingT-,.,.that correspond to the structure sharing possibly created by the call,and those that do not. The AbstrUnify operation can provide such information. So, we change the abstract sharing environments proposed so far, such that they preserve the distinction between old (input) and new (created) sharing edges, returned by the differentprimitive operations. The full set of sharing edges, consisting of the alternating closure of these sets, can be leftimplicit most of the time. For example, assume again that the definition of p/3 consists of the single clause p(_Y,-2,_U):- AJ=f (_Z). In Figure 4.7.d, only edge (3) will be returned as a new edge by the AbstrExtend operation. The set of old edges contains (1) and (2). W h e n computing the fullset of sharing edges, we get edge (4) from the alternating path (2)-(3),but no redundant edges such as (5,6,7,8). W e will prove the soundness of the abstract primitive operations described above in Section 4.2, by relating the abstract to the concrete operations, and showing that each sharing pair created by one of the auxiliaryfunctions TermShift, BindingPairs, and TransitiveClosure has an abstract counterpart created by the abstract operations TGShift, BindingEdges, and AlternatingClosure and that the abstract counterparts of the initialsharing pairs are preserved by the Convert operation.
4.1.3
T h e C o n c r e t e and A b s t r a c t D o m a i n s
Having introduced the basic concepts, we are now in the position to formally define the concrete and abstract domains for the sharing analysis. We use a twofold sharing c o m p o n e n t to m a i n t a i n detailed information a b o u t w h a t sharings are passed down from a calling environment and w h a t sharings are created in a local environment. T h e distinction plays a m a j o r role in the precision of the extension operation, as was illustrated in Section 4.1.2.
4.1. SHARING E N V I R O N M E N T S
63
D e f i n i t i o n 4.1.16 A concrete sharing environment has the form (/C,CSharing~:). IC is a term that has principal functor O; the arity corresponds to the number of program variables described by the environment. The sharing component CSharin~c = (CShr~, CShr~:) is such that CShr~r CShr~c are pre-sharing components for IC, and TransitiveCIosure(CShr~c U CShr2) is a concrete sharing component for IC. D e f i n i t i o n 4.1.17 An abstract sharing environment has the form ( T e, ASharingT-) where 7-~ is an abstract term environment containing the type graph 7- = (Nodes7, ForwardArcsT-, BackArcsT-) with () or _L labeling its root. The sharing component ASharing7 - (AShr~-,AShr~-) is such that AShr~-, AShr~-, and AlternatingCIosure('FGShift(7-,AShr~-),TGShift(7-,AShr~-)) are abstract sharing components for 7-. The abstract interpretation procedure computing abstract AND-OR-graphs (Section 2.2.1), will associate with every program point an abstract sharing environment. The component CShr~c (resp. AShr~-) stands for the sharing pushed down from the calling environment to the local environment (associated with the clause being interpreted); the component CShr~c (resp. AShr~-) stands for the sharing created in the local environment up to the current program point. So, the complete set of sharings for a program point is obtained by the TransitiveCIosure (resp. AlternatingCIosure) operation. The concretization function InstrEnvConc for abstract sharing environments is defined as follows. D e f i n i t i o n 4.1.18 For an abstract sharing environment (7-', (AShr~-, AShr~-)),
InstrEnvConc((7-', (AShr~, AShr~-))) = (CShr , CSh ?c)) I E TGEnvConc(7- ~) &~ CShr~o CShr~c are pre-sharing components f o r IC & TransitiveCIosure(CShr~c LJ CShr~c) is a concrete sharing component for IC
((r, s) E CShr~: ~ (7-[Sel(/C, r)], 7-[Sel(/C, s)]) E TGShift(T, AShr~-) )
& ((r, s) E CShr~ :=>. (7-[Sel(/C, r)], 7-[Sel(/C, s)]) (5 TGShift(7", AShr~-) ) Note: In the sequel when there is no confusion possible, we use CSharing~c to denote either TransitiveCIosure(CShr~c U CShr~) or (CShr~c , CShr~), and similarly ASharing:r to denote either AlternatingClosure(TGShift(7-,AShr~-),TGShift(7-, AShr~-)) or (AShr~-,AShr~-). It will be clear from the context what is meant. We also introduce some notation that is convenient for proving the safety resuits. D e f i n i t i o n 4.1.19 For an abstract term environment 7-e, a term IC E P~: a set of unordered pairs of the form (r, s) where r, s E
TGEnvConc(7-'), and defne
AbsPairT,K:(Px:) = {(7-[Sel(K~,r)], 7-[Sel(/C,s)]) I(r, s) e PK:}.
64
C H A P T E R 4. S H A R I N G A N A L Y S I S
The AbsPairT,iC function maps a concrete sharing pair for a term K: into an abstract sharing edge for the type graph 7- recognizing the term K. The concretization function for abstract sharing environments can now be reformulated as follows. D e f i n i t i o n 4.1.20 ( R e f o r m u l a t i o n of D e f i n i t i o n 4.1.18) For an abstract
sharing environment (7-', (AShr~-, AShr,~)),
{
InstrEnvConc((7-', (AShr~-, AShr~-))) = (K:,(CShr~:,CShr~:)) I JC 9 TGEnvConc(7-') '] & CShr~, CShr~: are pre-sharing components for ]~ & TransitiveCIosure(CShr~c U CShr~:) is a concrete sharing component for IC & AbsPairT-,lc(CShr~c ) _C TGShift(7-, AShr~
& AbsPairT-,/c(CShr~) C_ TGShift(7-, AShr~)
The following lemma states that the alternating closure operation of abstract sharing components safely approximates the transitive closure operation of concrete pre-sharing components. L e m m a 4.1.21 ( S a f e t y o f AlternatingClosure) For an abstract term environment 7-e and a term /C E TGEnvConc(7-e), let Ppcl, Plc~ be two pre-sharin# components for ]C. Then
AbsPairT-,/c(TransitiveClosure(Pjc 1 U Px:2)) C AlternatingCIosure(AbsPairT-,/C (P~c1), AbsPairT-,/C (PJc~)). Proof. An element of the first set has the form (7-[SeI(/C, r)], 7-[SeI(/C, s)]), where r r s and (r, s) E TransitlveCIosure(P~:l U P~:~). We must prove that
(7-[Se=(~:, r)], 7-[Se=(~:, s)]) AlternatingClosu re(AbsPairT-,K~ (Px: 1), AbsPairT-,/C (PJc2)). By the definition of TransitiveClosure, there exists a finite sequence E over P/cl U P~c2 such that E = (r~, s d , . . . , (r., s.) for some ~ C ~V, r = rz, s = s,~, VlEM :I
(4.2)
Given such a finite sequence, we can transform it into an "alternating" sequence E' satisfying E' = (r~, s~), . . . , (r~, s~) for some m 9 ~W, r
:
r~,s
:
! am,
VeeIV:l<e<m~slt: Vk 9 ( ( l <_2 k < r_n
r l'+ 1 '
=:~(r2k ' ,s~k ' )
9 PJcl)&
( l <_2 k + l < m _ ((1 _< 2/~ < m
~ ( r = k' + l , s2~+l ' ) / / ~ (r2k, s2~)
PJc2))V 9 e~:2)&
( l <_ 2 k + l < m _
::r
9
'
'
em))-
(4.3)
4.1.
65
SHARING ENVIRONMENTS
We first introduce two reduction steps for finite sequences satisfying (4.2). At least one of these reduction steps will be applicable if (4.3) is not satisfied. r e d u c t i o n 1: Let E = (rt, s t ) , . . . , (r,~, s,~) be a sequence satisfying (4.2), and /r 9 1~r such that 1
(4.4)
Because P~cl is a pre-sharing component, property 4.1.4.4 is satisfied, so it follows from (4.4) that there exists an element (rk, sk+t) E P~=t. In the finite sequence E, we replace (rk, s~), (r~+t, sk+t) by (rk, s~+t). Again, we get a new finite sequence E' (of shorter length) satisfying (4.2). r e d u c t i o n 2: The same as r e d u c t i o n 1, but P~:t replaced by Ppc~. Clearly, the reduction steps can only be applied a finite number of times and lead to an alternating sequence E' = (r~, s t ) , . . . , (rim, S'm) satisfying (4.3). For every k 9 such that 1 < k < m, let (Rk, Sk) = (T[Sel(]C, r~)], 7"[Sel(]C, s~)]). From the definition of AbsPairT,/C , it follows that
(r~,s~,) 9 PJct =:> (Rk,Sk) 9 AbsPairT,/c(Pjct ), r'k, s'k J~ 9 PJc~ =:~ (Rk, Sk) 9 AbseairT-,K;(Px:2). Using the sequence (Rt, $ 1 ) , . . . , (R~, Sin) in the definition of AIternatingCIosure, we have
(R,,
=
r)], T[Sel( :,
e
AlternatingClosure(AbsPairT, K: (Px:t), AbsPairT,K:(Px:2)), []
as desired.
Based on the previous result, we can now show that the full sharing relation of some concrete environment described by an abstract sharing component can be safely approximated by the AlternatingCIosure operation. L e m m a 4.1.22 For an abstract sharing environment (Te,ASharing:r) and a concrete sharing environment (]C, CSharing~:) 6 InstrEnvConc((7-', ASharingT)), we have
AbsPairT-,/c(CSharing~c ) C_ ASharingT. Proof.
From the definition of InstrEnvConc, we know that AbsPairT,/c(CShr~c ) C TGShift(T, AShr~-)& AbsPalr,T,/c(CShr~: ) C TGShift(7-, AShr~-).
We derive that
(4.5)
C H A P T E R 4. SHARING A N A L Y S I S
66
AbsPairT,)C (CSharingK:) -- AbsPair]-,/c(TransitiveCIosure(CShr~c U CShr~c)), by the definition of CSharingK:, C_AlternatlngCIosure(AbsPairT,lc(CShr~c), AbsPairT, lc(CShr~c)) , by Lemma 4.1.21 (the safety of AlternatingCIosure), C AlternatingClosure(TGShift(~, AShr~-), TGShift('/', AShr~-)), by (4.5), and the monotonicity of AlternatingClosure, C_ASharingT, by the definition of ASharlngcr. Summarizing, we have AbsPairT,ic(CSharingK: ) c_ ASharing~-, as desired. 4.1.4
Order
Relation
and Upperbound
[]
Operation
In this section we prove that the domain of sharing environments has the algebraic structure imposed by the framework of abstract interpretation. We first define a (pre)order relation and an upperbound operation for abstract sharing environments having the same arity. In the definitions we use the (pre)order relation _
Translate(T1, Or1, 7-2) = {(7-2[R],'/'215"]) 1 3R, S E S(Tx) : (TI[R], 7"115'])e TGShift(T1, P7"i) }. D e f i n i t i o n 4.1.24 ( < s h ) F o r abstract sharing environments (7-~,(AShr~l , AShr~l)) and (7-~, (AShr~2 , AShr~2)) such that arity(Tl[e])= arity(T2[e]),
( ,(AShr I,AShr I))
<_sh
C_ _C
&~ Translate(T1, AShr~-l,'/'2) &~ Translate(7"1, AShr~z , 7"~)
TGShift(T2, AShr~-2) TGShift(T~, AShr~-~)
Note that in the case where (7"~ --- T~) 2 it follows from the definition that
(T~, (AShr~-l, AShr~-l) ) ~Sh
(T~, (AShr.~2 ,Ashr~-2) )
TGShlft(T1, AShr~- 1) & TGShift(7"l, AShr~-,)
TGShift(T2, AShr~-2) TGShift(T2, AShr~-2)
C C
~By T[ _----7"~ we mean syntacticalidentity,not a shorthand for T~ _
4.1. SHARING ENVIRONMENTS
67
L e m m a 4.1.25 For abstract sharing environments (T~, ASharing=rl) and (T~,
ASharingT2) such that arit~Tl[r (T~, ASharing:r 1)
InstrEnvConc((T~, ASharingsr1))
arity(T2[r <Sh (T~,ASharing~2)
C
InstrEnvConc((T~, ASharing~%))
Proof. Assume that (TI, ASharing:rl) -<Sh (T~, ASharingT-2) and t/C, CSharingpc) 9 InstrEnvConc((T~., ASharingc%)). By the definition of -<sh, we have T; -
&
&
Translate(T1, AShr~l , T2) C TCShift(T2, AShr~-2) Translate(T1, AShr~-, T2) _CTGShift(T2, AShr~2 ).
From Property 2.3.8 of
(r, s) E CShr~: ~ (TI[SeI(/C,r)],'/'I[SeI(/C,s)]) E TGShift(T1, AShr~-1), (r,s) E CShr~c =~ (Tl[Sel(/C,r)],Tl[Sel(/C,s)]) E TGShift(T1,AShr~-l). For (r, s) E CShr~: (resp. CShr~c) it follows from Property 4.1.13 that SeI(K:, r), Sel(/C, s) E S(T1)V~ S(T2). So using the definition of Translate we derive (r, s) 9 CShr~c
=:~ (T2[SeI(K~, r)], T2[SeI(K~, s)]) 9 Translate(T1, AShr~-l, T2),
(r,s) 9 CShr~c ::~ (T2[SeI(/C,r)I,T2[SeI(/C,s)]) E Translate(T1,AShr~-l, T2 ), and by the definition of -<s~ (r, s) E CShr~c ::> (T2[SeI()C,r)], T2[SeI(/C,s)]) E TGShift(T2, AShr~-2), (r, s) E CShr~: =~ (T2[SeI(/C,r)], T2[SeI(/C,s)]) E TGShift(T2, AShr~2). Hence, we have (/C, CSharingx:) E InstrEnvConc((T~,ASharingT-2)), as desired. [] The minimal element of the abstract domain of sharing environments is (T~L, (0, O)), with T~_ as defined in Section 2.3.2. The maximal element of the abstract domain is (T~-, (AShr~r,.,AShrPr,_)) , with T~- as defined in Section 2.3.2, and AShr~- _, AShrPr T the maximal set of sharing edges, containing all pairs of function nodes of TT having the same label. The subdomain of sharing environments based on restricted type graphs does not contain any infinite ascending chains, because the domain of restricted type graph environments is finite, and for each finite type graph, there are only finitely many different sharing components. D e f i n i t i o n 4.1.26 (Upp)For abstract sharing environments (T~,(AShr~-l, AShr~,)) and ITS, (AShram,AShram)) s~ch that arity(T~[~])= arity(T~[~]),
CHAPTER 4. SHARING ANALYSIS
68
Upp((T;, (AShr~-l, AShr~-l)), (T;, (AShr~-2, AShr~-~))) = (T', (AShr~-, AShr~-)) where ~
= AShr~r = AShr~- =
T
TGUpp(T~, T~), Translate('Tl, AShr~l , T) U Translate(T2, AShr.~2, T), Translate(T1, AShr.~l , 7") U Translate(T2, AShr~-~, T).
Note that when T~ -~ T[ - T ~, we can define Upp as follows Upp((T~, (AShr.~l , AShr~-l)), (T~, (AShr.~, AShr~-2))) = (T', ((AShr~- 1 U AShr~-2), (AShr~- I U AShr~-a))). Definition 4.1.26 can straightforwardly be generalized for a finiteset {)01,...,I~} of abstract sharing environments. For all I satisfying 1 <_ t < n, let /~r = (T~, ASharlngTt), then we define
Upp(.[... ,~l,...}) = Upp(~I, Upp(... Upp(,0n-l,,~'~)...)). Lemma 4.1,27 For abstractsharing environments (T~,ASharing:rl)and (T~, ASharingT2) such that aritl/('Tl[c]) = arity('T2[c]), let Upp((T~.,ASharing'ri), ('T.~, ASharing~-2) ) = (7- *, ASharing~). Then
(T~, ASharingT-1) <_sh (T', ASharing./-) (T~, ASharingT2) <_sh (T e., ASharlngT-).
&
Proof. This follows from Property 2.3.8 of T G U p p and the definitions of <sh and Upp. []
4.2
Primitive Operations
In this section, we specify the primitive operations for the concrete and abstract domain of sharing environments (unification, procedure entry, and procedure exit) and we formally prove the safety of the abstract operations with respect to the concrete operations. The specifications are based on the operations for the type-graph environments (see Section 2.3.3).
4.2.1
Unification
First we give the specifications of the concrete and abstract unification corresponding to the basic operation X~ : Xy. Then we reformulate these specifications for the basic operation X~ : f(X~,...,X~#). For Prolog programs in normal form, these are the only two forms of unification that can occur.
4.2. PRIMITIVE OPERATIONS 4.2.1.1
69
Xi = Xj
D e f i n i t i o n 4.2.1 For a concrete sharing environment (ICi,,, (CShr~c,.,, CShr~c,,.) )
and 1 <_ i < j <_ arity(Igi,~[r
Unify((/Ein, (CShr~c,,,, CShr~:,.)), i,j)) = if mgu(JC,./i,/c.,/j) is fail then fail else (]Co,a, (CShr~c.,,,, s where ~ o . t =-- Idi. o', f o r a = mgu(lCin/i, ]Cin/j),
and
AccNew
= =
TermShift(/Co~,t, CShr~:,.), TermShift(/Co,,t, CShr~:~.),
New
=
mermShift(/Co.t, BindingPairs(/Ci,,, i, j)),
CShr~c.. '
CShr~:.~, = TransitiveCIosure(AccNew U New), BindingPairs(/Ci,~, i, j) { [ i.p,j.pE O(ICi,) & } = (i.p, j.p) (]Cin/i.p E Vars V ]gln/j.p e Mars) " The following lemma states that the initial sharing is preserved. L e m m a 4.2.2 Let (/Ci., CSharingx:~.) be a concrete sharing environment. Let (ICo~t, CSharingjco.,) = Unify((KSi., CSharingjq.), i, j) (Unify does not fail), where 1 < i < j < arity(ICi,,[r Then CSharing~c~ C_ CSharingic**,. Proof.
From the properties of TermShift and the definition of Unify, we have CShr~,.
CShr~c,~
C_ TermShift(/Co.t, CShr~q,.) = CShr~c..,, C_ TermShift(/go,,t, CShr~,.) = AccNew.
(4.6) (4.7)
From (4.7), the properties of TransitiveCIosure and the definition of Unify, we have
CShr~,.
C TransitiveClosure(AccNewU New) = CShr~o..
(4.8)
From (4.6), (4.8) and the monotonicity of TransitiveCIosure, it follows that
TransitiveClosure(CShr~:,,, U CShr~:,,,) C_ TransitiveClosure(CShr~:.., u CShr2_,) or shortly,
CSharingjq~ C CSharingK:o~t. F1 To prove that Definition 4.2.1 guarantees the concrete unification operation U n l f y ( - , i , j ) to be well defined on the domain of concrete sharing environments, we proceed as follows. First we demonstrate that New -- TermShlft(/Co,a,
70
C H A P T E R 4. S H A R I N G A N A L Y S I S
BindingPairs(/Cin, i, j)) is a pre-sharing component for lCo~,t. Second, using the previous result, we prove that CShr~c.~,, AccNew, CShr~c.. ' and CSharingpc.., = TransitiveCIosure(CShr~c.~ ' UCShr~.~,), are pre-sharing components for ICo~,t. Third, we introduce the high-level unification algorithm. Fourth, we demonstrate that CSharingpc.., is a concrete sharing component for ](.out. Using the fact that it is a pre-sharing component, we still need to show that it satisfies Condition (4.1), concerning multiple occurrences of a free variable in Eout.
L e m m a 4.2.3 Let (/(:in, CSharingK:~) be a concrete sharing environment. Let (/C~,~,t, CSharingJco..) = Unify((/C,,~, CSharingx:,~), i, j) (Unify does not fail), where 1 < i < j < arity(ICin[e]). Then New = mermShift(/Co~t, BindingPairs(ICi,~,i,j)) is a pre-sharing component for 1Cont.
Proof. From the definitions of TermShift and BindingPairs, it is clear that New is a set of unordered pairs of the form (r, s) where r, s E C0(~o~,t). We must prove that New satisfies Conditions 4.1.4.1-4. C o n d i t i o n 4.1.4.1 is satisfied by New: Given (r, s) E New, we must prove that Eo~,t/r =- lCo~,t/s. The property holds for the set BindingPairs(K;i,~, i, j) as, after unifying ICi,~/i and ICi,~/j, we have that ICo,,t/i.s - K.o~,t/j.s for all paths s E (9(lCo~,t/i). The function TermShift preserves the property because corresponding subterms of identical terms are identical. C o n d i t i o n 4.1.4.2 is satisfied by New: Given (r, s) E New, we must prove that r ?~ s. The property holds for the set BindingPairs(i~in, i, j), as i :fi j. The function TermShift preserves the property. C o n d i t i o n 4.1.4.3 is satisfied by New: Given (r, s) E New, we have to prove that TermShift(Eo~t, {(r, s)}) C New. This follows from the idempotence of TermShift w.r.t. ~o~,t. C o n d i t i o n 4.1.4.4 is satisfied by New" Given (r, s), (s, t) E New where r :fl t, we must prove that (r,t) E New. From the definitions of TermShift and BindingPairs, we know that (r, s ) a n d (s, t) are both of the form (i.p.q, j.p.q) where ( i . p , j . p ) i s a member of BindingPairs(Ei,~,i,j). Suppose that s = i.p.q, then r = j.p.q -- t, i.e. there are no two different consecutive pairs in New.
L e m m a 4.2.4 Let (l~i,~, (CShr~:,~, CShr~,~)) be a concrete sharing environment. Let (]Co,,, (CShr~co.,, CShr~c.~,) ) -- Unify((E,n, (CShr~q~, CShr~q~)), i, j) (Unify does not fail), where 1 <_ i < j < arity(ICin[e]). Then CShr~c..,, AccNew, CShr~c.~~ and CSharingjco., are pre-sharing components for the term lCo,,t (where CSharingjc.., = TransitiveCIosure(CShr~c.~ ' U CShr~~
4.2. P R I M I T I V E OPERATIONS
71
Proof. From the definitions of Unify and its auxiliary functions, it is clear that s AccNew, s and s are sets of unordered pairs of the form (r, s) where r, s E O(/Co~t). We must prove that s AccNew, s t and s satisfy Conditions 4.1.4.1-4. C o n d i t i o n 4.1.4.1 is s a t i s f i e d b y t h e sets s176
AccNew, s
, and
CSharingjc~ Given (r, s) E CShr~co., (resp. AccNew, CShr~c~ prove that /Co~t/r --/Co~t/s.
CSharing~:~
we must
By the assumptions of the lemma, CShr~q. and CShr~. satisfy Condition 4.1.4.1. The function TermShift preserves the property because corresponding subterms of identical terms are identical. So, for (r,s) E CShr~c~ ' = TermShift(K:o~t, CShr~c,~) (and similar for the set AccNew --
TermShift(/Co~,t, CShr~c,,,)), we have ICo~,t/r =- ICo~,t/s. From Lemma 4.2.3 we know that for (r,s) E New, we have ICo=t/r -~ ICo~,t/s. Using the definition of TransitiveClosure and the transitivity of term equality, the property follows for CShr~c.. ' = TransitiveCIosure(New U AccNew) and CSharingjc~ -- TransitlveCIosure(CShr~co~' U CShr~o~,). C o n d i t i o n 4.1.4.2 is s a t i s f i e d b y t h e sets CShr~co~,,AccNew, CShr~co~,, and
CSharingjco~,: Given (r,s) E CShr~:o., (resp. AccNew, CShr~c~ CSharingKo.,), we must prove that r ~ s. By the assumptions of the lemma, CShr~q. and CShr~c~. satisfy Condition 4.1.4.2. The function TermShift preserves the property, thus passing it on to CShr~c~ ' and AccNew. From Lemma 4.2.3 we know that for (r,s) E New, we have r r s. Using the definition of TransitiveCIosure it follows that the property holds
for both CShr~:~ ' = TransitiveCIosure(AccNew U New) and CSharingjcol= = TransitiveCIosure(CShr~:~ U CShr~:~ C o n d i t i o n 4.1.4.3 is s a t i s f i e d b y t h e sets CShr~c~ AccNew, CShr~:~ and
CSharingjc~ Given (r, s) E CShr~co~, (resp. AccNew, CShr~co~,, CSharingjCo~), we must prove that TermShift(K:o.,, {(r, s)}) C_ CSh,~c~' (resp. AccNew, CShr~co.,, CSharlngJc~ For (r, s) CShr :~ = TermShift( :o.. CShr ,.), ( and resp. AccNew = TermShift(/Co~t, C S h r ~ ) this follows from the idempotence of TermShift. Consider (r,s) E CShr~c~ , = TransitiveC[osure(AccNew U New). Expanding the definition of TermShift, we must prove that for each t such that r.t, s.t E O(Ko=t), (r.t, 8.t) E CShr~c~ .. Fix arbitrary such r, s,t. Let (rz, s z ) , . . . , (r,~, s,~) be the finite sequence over AccNew U New that leads to the inclusion of (r, s) in CShr~~ according to the definition of the operation TransitiveCIosure. Using Condition 4.1.4.1 proved above, we have Ko=t/r =-- Ko,t/rz -- Ko~,t/Sl =---... -- Ko=t/r,~ = Ko,,t/s,~ =- /Co=t/s. F r o m r.t, s.t E O(Ko=t) we can conclude that rt.t,st.t E O(Ko,,t) for 1 < I < n.
CHAPTER 4. SHARING ANALYSIS
72
For (rt, st) E AccNew ----TermShlft(/Co~,t, CShr~r it follows from the properties of TermShift that also (rt.t, st.t) E AccNew. For (rt, st) E New it follows from Lemma 4.2.3 that (rt.t, st.t) 9 New. Thus, there is a finite sequence in AccNew U New of the form (rl.L s l . t ) , 9 9 (r,~.t, s~.t) with rl -- r, s,~ -- s, st = rt+l for 1 <_ l < n. So by the definition of TransltiveClosure, also (rA, s.t) 9 CShr~co.~. Similarly, we can prove the property for the sharing component CSharingjCo.~ : TransitiveCIosure(CShr~c.. ' U CShr~o.,). C o n d i t i o n 4.1.4.4 is s a t i s f i e d b y t h e sets CShr~co**, AccNew, CShr~c~ and
CSharingpCo.,: Given (r, s), (s, t) E CShr~co.t (resp. AccNew, CShr~co.,, CSharing~c~ where r # t, we must prove that (r,t) 9 CShr~co.' (resp. AccNew, CShr~o.,,
CSharing~Co.,). By the assumptions of the lemma, CShr~c~ and CShr~,. satisfy Condition 4.1.4.4. For (r, s), (s, t ) 9 CShr~r
., r r m : (r, s) = (r
= TermShift(/Co,a, CShr~q~) where r#~, 3r', #,
r
~ % t) = (r r ~ (r r (r e') 9 CShr~,~ ~ ~'.~ # e'.m. We have r = s = r 9 0 ( ; o , , ) ~ r S II 9 O(/Ci,). Assume, without loss of generality, that # --- s'.p, hence p.n = m & r' 9~ E'.p. From (d',E') 9 CShr~q. & s' = #'.p 9 O(/Ci,,), we have (s".p,E'.p) 9 CShr~q~, because CShr~q., satisfies Condition 4.1.4.1 and 4.1.4.3. From (r',s'),(s".p,t".p) 9 CShr~c,,* & # = s".p & r' # t".p, we have (r', t".p) 9 CShr~q,,, because CShr~q,, satisfies Condition 4.1.4.4. Finally, (r, t) = (r'.n, t".m) = (r'.n, t".p.n) 9 CShrTc~ ' = TermShlft(/Co,,t, CShr~q~), from the definition of TermShift. In a similar way we can prove the property for AccNew = TermShift(/Co=t,
CShr~q~). For (r, s), (s, t) 9 CShr~r
= TransitiveCIosure(AccNew U New), let (rl, sl),
. . . , (r,~, s,~) be the finite sequence for (r, s) as specified in the definition of TransitiveCIosure, and (r~, s ~ ) , . . . , ( r ' , s ' ) the finite sequence for (s, t). Since s,, = s = rl, the finite sequence (r~, s~),..., (r,,, s,), (r'~, s'z),..., (rk, s ' ) then assures that (r, t) 9 CShr~co.,. Similarly, we can prove the property for the sharing component CSharinglc~
= TransitiveCIosure(CShr~co. ' U CShr~:o.,). [] The algorithm for finding a most general unifier in Definition 4.2.1, is the Solved Form Algorithm for simplifying sets (systems) of equations given in [47]. Based on Herbrand's original unification algorithm, this algorithm views unification as a process of transforming a solvable equation set into an equivalent solved form equation set. When applied to an unsolvable equation set, the algorithm halts with failure.
4.2. P R I M I T I V E O P E R A T I O N S
73
A l g o r i t h m 4.1 Solved Form Algorithm [47] Non-deterministicMly choose an equation from the set to which a numbered step applies. The action taken by the algorithm is determined by the form of the equation: i. f(t1,...,tn) = f(rl,...,rn) replace by the equations tl = rl,..., t,~ = r,~
2. f ( t l , . . . . tn) : g ( r t , . . . , r m ) where f ~t g or n ~ m ---* halt with failure 3. X = X ---* delete the equation 4. t = X where t is not a variable ---* replace by the equation X = t 5. X = $ where t ~ X and X has another occurrence in the set of equations --~ i f X appears in t then halt with failure; otherwise replace X by g in every other equation The algorithm terminates when no step can be applied or when failure has been returned. 1:3 The following lemma states that the set of concrete sharing environments is closed under the Unify(-, i, j) operation. L e m m a 4.2.5 Let (]Ci,~,CSharingx:~) be a concrete sharing environment. Let (lCo,,t, CSharingx:..,) = Unify((K:i,,, CSharingjc,~), i, j) (Unify does not fail), where 1 < i < j < arity(Ein[e]). Then (ICout, CSharingx:.,,) is a concrete sharing environment. Proof. By the definition of Unify, we have K:out ~ K:inor , where a -: mgu(ICiJi, K;i,~/j) is not fail. Because (K:i,,, CSharingjc,.,) is a concrete sharing environment, it follows from the definition of substitution application that the term K:o~t has principal functor 0, and that O(X:,,,) C_ O(X:o,t). From Lemma 4.2.4 we
know that CShr~c.~,, CShr~c..,, and CSharingjc.~, = TransitiveCIosure(CShr~c.. ` u CShr~..,) are pre-sharing components for/Co,,t. So, we still have to prove that CSharing~:.~, satisfies Condition (4.1), i.e.
-
a
E Vats)
(r r s)
(r, s) e CShari,g
..,.
From the relationship between mgu's and solved form equation sets [47] we know that mgu(K:~,,/i, s can be obtained by transforming the solvable equation set Eo = { q li = P q . l j }
into its equivalent solved form (a is a most general unifier of Eo). The Solved Form Algorithm throws away the upper structure of terms when the latter does not occur in the variable bindings that solve the equation set. We
74
C H A P T E R 4. S H A R I N G A N A L Y S I S
wish to observe the effect of the algorithm on the terms in the environment as it incrementally isolates and applies variable bindings. To facilitate this we add an extra variable and auxiliary equation, the purpose of which is simply to force the algorithm to leave the entire environment in the equation set. W e consider the equation set X = /Ci~ } formed from Eo by adding an auxiliary equation of the form X = / C i , . where X is a new variable not occurring elsewhere in the system. Clearly Ei,~ is solvable iff Ea is. Let
be the solved form of Ei,~. Since Ei~ and Eo~t are equivalent, they have the same most general unifiers. Let 7 be one. Then it follows that /C = X 7 =/Ci,~ cr (modulo renaming) = ~o~t W e prove the l e m m a by induction on the number of steps applied by the Solved Form Algorithm. Notice that none of the steps will ever select the first equation9 W e show that the following induction hypotheses hold at each stage of the Solved Form Algorithm given by an equation set E and a term/C, where 9
E =
X
=
/C
tl
=
t2
Induction hypotheses:
(a) i.s,j.s E O(#C) ~ (IC/i.s E VarsV K~/j.s E Vars) =~ (i.s,j.s) E New ( h ) Vr, q
:
-
Vats
# q
q)
CShari.g
...)
(c) For each tt = t2 in E that is not of the auxiliary form X =/C and that has not yet been the subject of Step 5, there exists a path s E O(/C/i)NO(/C/j) such that either tl - IC/i.s,t2 =- IC/j.s or t2 - IC/i.s,ti - IC/j.s Clearly, Condition (b) expressed for the stage given by the solved form equation set E ~ t and the term/C gives the conclusion of the lemma. Basis: The induction hypotheses hold for the stage given by Ei~ and/Cir. Condition (a): The pairs (i.s, j.s) are in BindingPairs(/C~,,,i, j) by definition, so from the the fact that TermShift is an increasing function, they are in New. Condition (b): Because CSharingpc,~ = TransitiveCIosure(CShr~c~ U CShr~c~ ) is a sharing component for /Ci,,, the pairs (r,q) are in CSharingjc~ . So, using Lemma 4.2.2, they are in CSharingpc..,.
4.2. P R I M I T I V E O P E R A T I O N S
75
Condition (c) holds because the only equation in Ei,~ not of the auxiliary form X =/C,,, is ICi,,/i = ICin/j, and e 9 O(IC,,,/i) (30(ICi,,/j). S t e p : We consider each step of the Solved Form Algorithm and show that its application preserves the induction hypotheses. We use Ebe! .... ICb,Ior , and E~lt,r, ICMte~ to denote the two consecutive stages just before and after the application of a step of the Solved Form Algorithm. That is, we assume that the induction hypotheses hold for Eb,Jor,, ICb~lo~,. We must prove that they hold for case 1: When Step 1 is applied,/Cb~lo~e ~ /C=#er, so Conditions (a) and (b) are preserved. By Induction Assumption (c) there exists s E O(/Cb~lo~/i)0 O(ICb,lo~,/j) such that (without loss of generality) f ( t l , . . . , t , ~ ) = JCbelo,.e/i.s, and f ( r l , . . . , rn) ~ ]CbeforJj.s. S o w e have for all g satisfying l
&
(,'L
As/C~,lo~e -/Ca/te~, Condition (c) is preserved. case 2: By assumption, the unification succeeds, so Step 2 is not applied. case 3: When Step 3 is applied, K:~elor, ~-/Calt,~, so Conditions (a) and (b) are preserved. No new equations are introduced, so Condition (c) is preserved. case
case
4: Again, ICS~lo~, = tC~#~, preserving Conditions (a) and (b). Clearly, also Condition (c) is preserved. 5: By assumption, the algorithm does not fail. Let Z = t be the selected equation. Consider a path s such that i.s,j.s E O(]Calte~) & (]C~tter/i.s E VarsV ICalttr/j.s E Vats). We must prove that (i.s,j.s) C
C o n d i t i o n (a):
New. By the way /C~#er is constructed from /Cbelo~,, we know that there exist paths p and r such that s = p.r & i.p, j.p E O(IC~,/o~,) & (ICt,tlor,/i, p 9 Vats V ICt,,lo,.,/j.P 9 Vats). By the Induction Assumption (a), (i.p,j.p) 9 New. Because 36:IC,,it,r = K:o~t, it follows from the definition of substitution application that O(/Cajt,~) C_ O(/Co~t). Because New is a pre-sharing component for/Co~t (Lemma 4.2.3), it follows that (i.p.r,j.p.r)= (i.s,j.s) 9 New. Consider paths r,q such that r,q 9 O(IC~lt,, ) & r # q & /Calter/r -~ ICalttr/q & ICalter/r 9 Vars. We must prove that (r,q) 9 CShatingt:o,,. Let IC,,#,~/r =_ IC,,]tt,./q =- Y . Since /Ca#,~ is constructed from ICt,~lo,., according to Step 5, we know that each s 9 O(K:~/t,~) such that IC~lt~ds =_ Y must satisfy one of the following
C o n d i t i o n (b):
(Figures 4.8.a and 4.8.b). (s 9
a
----
(4.9)
C H A P T E R 4. S H A R I N G A N A L Y S I S
76 <>
,/
/' "', .
",,
,,"
I ..... , ~" , " ' , t .
<>
,,
'. . . . ' ,]~;~,t .
.
.
(a)
.
.
.
(b)
Figure 4.8: (a) (b) (C) (d)
<>
,,
'--'Z . . . . ;.--~ I' " ~t
,, , '- Z--. z-'~--~,l~---
.
r"
,_ y _ y _ . (c)
(d)
/Cbelo,~ when s satisfies (4.9), /Cbejo,~ when s satisfies (4.10), ]Cbelore satisfying Induction Assumption (c), ]Cbelo~ when q, r both satisfy (4.11).
or
(.= s'."
3s', s " : .
o(,) /
& I C b , l o , , / s ' -- Z & I C , ~ , ~ / s -- ~ / s " -- Y
(4.10)
]
Shortly, we shall show that (r, q) G CSharlngK:o., by a case analysis of r and q baaed on which of (4.9) or (4.10) holds when s is taken as r and, respectively, q. But first we derive a convenient consequence of (4.10). Recall that Z = t is the selected equation. By Induction Assumption (c), there exists a path p e O(]Cbe/o~e/i)f30(ICbeyote/j ) such that ICb,yo,,/i.p -- Z & ICbeto,,/j.p ---- t (without loss of generality). Fix such a p (Figure 4.8.c). Consider any s E O()Calte,) satisfying (4.10) and the corresponding values of s' and s". Since ]Cbeyore/s' -Z & )Cbelo~/j.P -- t & Z ~ t we have 8' ~ j.p, hence s'.s" ~ j.p.s". Since t / s " - Y and ]Ca/~ is derived from ]CbeIo ~ by replacing Z by t, we have ]c.~edz.p.8 " " - ~,d~.p.8 " " - ~b,/o~Jj.p.8" - Y. Using Condition Ca), proved above, since Ka#e~/i.p.s" G Vars, we have (i.p.s",j.p.s") 6 New C CSharingro.,. From (4.10) we have s' 60(]Cb,lo~) & ]Cb,lo~,/s' =-- Z which, by Induction Assumption (b), implies that (s', i.p) 9 CSharing~co., or s' = i.p, which implies that (s'.s", i.p.s") 9 CSharingjc~ since CSharingjCo.t is a pre-sharing component for ICo,,t (Lemma 4.2.4), or s'.s 't = i.p.s". Since we now have either (s'.s", i.p.s't), (i.p.s 't, j.p.s") G CSharing~:o~, & st.ottr j.p.s tt or s ' . s t t : i.p.s"r j.p.s 't & (i.p.s tt, j.p.s tt) 9 CSharinglc~ we also have (s'.s",j.p.s") 9 CSharingx:o., (Lemma 4.2.4). Thus, if s 9 o(J%.,.,) satis~es (410). then s satisfies
( ~St Stt
-- s t . s " & ( S , j . p . s " ) 9
CSharingK:~
-- S s at E O(](~.befove) a ]~.before/j.p.S t t ~__ Y
)
(4.11)
Suppose r and q each satisfy (4.9) with r and q, respectively, standing for s. Then it follows immediately from Induction Assumption (b)
4.2. P R I M I T I V E
OPERATIONS
77
that (r,q) E CSharingjco~,, as desired. Otherwise, at least one of r and q must satisfy (4.11) when substituted for s. Without loss of generality, assume that it is q. The strategy is now to show that each of r and q is related to some occurrence of Y in IC~lo~Jj.p. ( T h a t is why (4.11) uses IC~4o~Jj.p.s". ) This will allow us to apply Induction Assumption (b), in case the occurrences related to r and to q are different, and Lemma 4.2.4, to show the required result. By assumption, q is an s that satisfies (4.11), so there exist q' and q", corresponding to s' and s", satisfying (q, j.p.q") E CSharing/Co., &: ICbefore/j.p.q" =- g If r satisfies (4.9), then
so (r,j.p.q") E CSharing~:o~,,by Induction Assumption (b), and consequently (q,r) E CSharing~c.... by L e m m a 4.2.4 as required. Otherwise, r satisfies(4.11), so there must exist r' and r" (Figure 4.8.d), corresponding to s' and s', such that (r, j.p.r") E CSharing~c~
& Kbelort/j.p.r" = Y
Since ICa~lo,.~/j.p.r" =- Y =_ /C~lo~Jj.p.q" , either r " = q" or (j.p.r", j.p.q") E CSharing~co.,, by Induction Assumption (b). In the former case one application of Lemma 4.2.4 and in the latter case two applications derive the required result (r, q) E CSharingjc~ from (q, j.p.q"), (r, j.p.r") E CSharingpCo~,, which was shown above. C o n d i t i o n (c): For each equation "1 ~elor~ : ~belor~ ~ in Ebb/ore other than the selected Z = t and not yet selected in a previous application of Step 5, and not of the auxiliary form X = /Cb,lo~, there exists by Induction Assumption (c), a path s such that i.s,j.s E O(IC~/o,~) and (t~ el~ = IC~lo~/i.s ) & (t~ ~1~ -- IC~lor~/j.s ) (without loss of generality). Since replacing Z by t throughout the system has the same effect on ~~1belo~e' + ~2b~lort as it has on ICbtlo~e/ i.s, ICbelo~t/ j.s the relationship (t~ lt~ = IC,jt~r/i.s) & (t~ ft'~ =__IC,jt~/j.s) holds in the resulting system Ealt,~. I-3 The following corollary formalizes the relationship between CSharingjco., as computed by Unify and the high-level unification algorithm given by the Solved Form Algorithm.
78
C H A P T E R 4. S H A R I N G A N A L Y S I S
C o r o l l a r y 4.2.6 Let (]Ci,,, CSharingjc~) be a concrete sharing environment. Let 1 _< i < y _< =~ity(Jc,.[~]), and (~o~,, CSharing~..,)= u~ifygC~., CSh~ring,:,~, i, j), (i.e., Unify does not fail). Let the equation sets Ei,~ and E and the term IC be as defined in the proof of Lemma 4.e.5. Then, for all computations of the Solved Form Algorithm on Ei,, and for all stages in such a computation, given by E and IC, CSharlng~.., satisfies the following.
(a) i.,, j.s ~ o(pc) ~ (PCli.s ~ vats v Ic/j.s ~ Vats) = (i.s, j.,) ~ New (b) Vr, q E O(/C): (/C/r ---- ]C/q & /C/r E Vars & r ?d q ::~ (r, q) E CSharingjc.,,.) Proof.
Follows immediately from the inductive proof of Lemma 4.2.5.
[]
We are now in the position to define the abstract operation. The following definition uses auxiliary functions Convert and BindingEdges, defined below. D e f i n i t i o n 4.2.7 F o r a n abstract sharing environment (Tin t, (AShr~-, AShr~- ))
and i <__i < j <_ ~rit~(~,,[d),
AbstrUnify((Ti.', (AShr~-, AShr~-)), i, j) = if then
TGUnify(Ti,', i, j) is fail fail
else
(To,,t', (AS h r~-..,, AS h r~-..,)),
where
To~,t" = TGUnlfy(Tin', i, j), and
AShr~-.., C B TGShift(To,,t, AShrL~,)
= =
Convert(Tin, AShr~-,To,,t), TGShift(To,,t, Convert(Tin, AShr~-,To,,t)),
= =
TGShift(To~t, BindingEdges(Ti,, To,,t, i,j)), AlternatingClosure(C, B).
Recall that failure is represented by T~_ in the case of type graph environments (Section 2.3.2), and by (T~_, (0, 0)) for the abstract domain of sharing environments. We still have to define the auxiliary functions. The function Convert takes an abstract sharing component for a type graph Ti,,and reexpresses it for a type graph To,n. In particular, the function will be used for a type graph To,,tthat describes a supersct of instantiations of terms represented by the type graph Ti,. D e f i n i t i o n 4.2.8 For two abstract term environments T.e'Tin, ,o,,te such that arit~Ti,,[e]) = arit~t(To~t[e]), and an abstract sharina component PT"~ for Ti,,, define
4.2.
PRIMITIVE OPERATIONS
79
Convert(Ti., P:T~.,To.t) =
/ (7-o.,[R],To.,[s])13R,s e S(To.,):
([R, S 6 S(~,,) & (Ti,,[R], Ti,,[S]) 9 TGShlft(Ti,,, PT"~.)] v [3R',S',k,Z,/: R = R'.(k,/) ~ S=S'.(Z,:) R'.(k, v), S'.(t, v) e s ( ~ , )
/
& f is a functor or a constant &
(T..[R'.(k, V)], ~.[S'.(I, V)]) E -I-CShift(~., P~r,..)]) The function BindingEdges i~ the type-graph analogue of the BindingPairs function for concrete terms. D e f i n i t i o n 4.2.9 For art abstract t e r m environment arity(7-i,~[e]) and 7-o,,t~ = TGUnify(Ti,,~, i, j),
7~',
1
<_i<j<_
BindingEdges(T~,~, To,,,, i, j) = (7"o.,[R], 7-o,,,[S]) I 3R, S E S(To,,t), 3 f : f is a functor, a constant or V &
([R = (i, f) a S = (j, f) a ((i, V) e S(~.) v (j, V) ~ S(~.))] (either the i th or the jth component of Tin is itself a V-node) V [3g,T,l:
R=
(i,g).T.(l,f)
& S=(j,g).T.(l,f)
&
(
~. (%.', (ASh,~-, ASh,;,.)) is an ab,t~act ,har~ng environment ~. qC.,, (CSh%,., CShr~:,,.)) is a concrete sh~ring environment 3. (/Ci,=, (CShr~c~,CShr~c..)) E InstrEnvConc((Ti,~ t, (AShr~r~,AShr~-))), that is,
(a} tCi,, E TGEnvConc(~')
(b) CShr~c~,CShr~c~ are pre-sharing components for ICi,=, and TransitlveClosure(CShr~c~ U CShr~) is a concrete sharing component
for/Ci.
(c) AbsPai%.,X:,.(CSh4c,. ) c TGShift(~., AShr~,~) and AbsPair~,=,/Ci,=(CShr~:~..) C_TGShift(T~., AShr.~)
80
C H A P T E R 4. S H A R I N G A N A L Y S I S
4. 1 _< i < i _< arit~ (Z,.[d) = arit~ (/C~.[d)
5. (/Co,,,, (CShr~:.,.,, CShr~:.,.)) = Unify((/Ci,. (CShr~.., CShr~;..)), i, j), is not
fair 6. (7-o~t', (AShr~-..,, AShrL.,) ) : AbstrUnify((Ti,~', (AShr~q~, AShr~- )), i, j) T h e o r e m 4.2.11 (Safety of AbstrUnify(-,i,j)) Assuming Condition 4.~.10, it follows that
(/Co.t, (CShr~.,., CShr~:...)) e InstrEnvConc((To~,t', (AShr~-...,, AShr~-o,,,))), that is,
Part 1. ICont E TGEnvConc('To~,t'), Part 2. CShr~c..,,CShr~c.= ' are pre-sh.aring components for ICo~,t and TransitiveCIosure(CShr~c.. ` U CShr~c..,) is a concrete sharin# component for ICo~t, P a r t 3. AbsPairTo,t, iCo,t(CShr~.., ) C_ TGShift(To,t, AShr~-..,) and AbsPairTo,jt, iCo,,,( CShr~c..,) C TGShift('To,,t, AShrL,., ). Part I. is a restatement of Theorem 2.3.9. Part 2. follows from the fact that Unify is well defined ( L e m m a 4.2.5). The proof of Part 3. will follow at the end of the section, alter we have related the auxiliary functions used in the definition of AbstrUnify to those used in the definition of Unify.
L e m m a 4.2.12 (Safety of TGShift) For an abstract term environment 7"e and a term IC E TGEnvConc(7-'), let P~ be a set of unordered pairs (r, s) such that r, s E O(IC) and IC/r =_ IC/s. Then
AbsPairT,/c(TermShift(/C, PJc)) _C TGShift(T, AbsPairT,/c(Ppc)). Proof.
Expanding the definitions of AbsPairT-,K; and TerrnShift we have AbsPairT",/c(TermShift(/C, PJc)) = (4.12) {(7"[Sel(/C, r.t)], 7-[Sel(/C, ,.t)]) I (r, s) 9 P/c & r.t, s.t 9 O(/C)}.
Fix arbitrary r,s,t such that (r,s) E P~ and r.t,s.t 9 O(/C). From Property 4.1.13, it follows that SeI(/C, r.t), Sel(/C, s.t) 9 S ( T ) . Since/C/r = IC/s, for T = Sel(/C/r, t), we have, by the definition of Sel,
Therefore, we have
Sel(~:,rt)
=
Sel(~,r) T,
selC~:,8.t)
=
Sei(~,s).T.
4.2.
81
PRIMITIVE OPERATIONS
(7-[Se[(/c, r.t)], 7-[Sel(/c,s.t)]) e TGShift(7-,{(7-[SeI(/C,r)], 7-[Sel(/c,s)])}) by the definition of TGShift, = TGShift(7-,AbsPakT-,/C({(r , s)})) by the definition of AbsPairT-,/c. In summary,
(7-[SeI(/C,r.t)], 7-[SeI(/C,s.t)]) E TGSHIft(7-,AbsPairT-,/c({(r,s)})).
(4.13)
The lemma follows from (4.12), (4.13), and the monotonicity of TGShift and AbsPairT,/c. 12
7-o``," be abstract t e r m envir o n m e n t , such that arity(~.[e]) = arity(7-o``t[e]) and let ICi~, E TGEnvConc(Z.') and/co``t E TGEnvConc(To``t') such that/co``t - /ci, a for some substitution tr. For P~., a pre-sharing "component for/ci,~, "we have that L e m m a 4.2.13 ( S a f e t y o f Convert) Let T i e and
AbsPsir7-o``t,/co``t(P~,.) C_Convert(T~., AbsPair~.,/c~.(P~:,.), To``,). Proof. Consider any (r, s) E we need to show that
P~..
Applying the definition of AbsPairT,
O``'I
/C
O``t
,
(7-o``t[Se[(/co``t,r)], 7-o``t[Sel(/co``hs)]) E Convert(~T/.,AbsPair~.,/c,,,(P~c,.), To.,). From Definition 4.1.4, we have r, s 6 0 ( / C , , ) . From/Ci, 6 TGEnvConc(~,') and Property 4.1.13, we have Sel(/ci. , r), Se](/ci., s) E S(7-i.).
(4.14)
It follows from/Co``t - / C i , a and the definition of substitution application that O()C,,,) C_O(/co``,). Hence r,s E O(/Co``,). So from /Co,,, E TGEnvConc(7-o``t') and Property 4.1.13 it follows that
Sel(/co``h r), Sel(/co``hs) E $(7-o``t).
(4.15)
Since/c,./r -/ci./s by Definition 4.1.4, there are two cases. case It ( / C i . / r , / c i . / s q! Vars). From/Co`` t =/ci.a and the definition of substitution application, we have that
Sel(/co``$,r) : Se]()Cin, r) ~ Sel(/co``t,s) --- Sel(/cin, s). We then observe that
----(7-i.[Sel(/ci.,r)], n[Sel(/cin, s)]),
(4.18)
CHAPTER 4. SHARING ANALYSIS
82
by (4.16), E AbsPairT~jCi~(Plq. ), from (r, s) E PJc~., and the definition of AbsPairTi,./Ci,=,
c_ ZGShift(~., abseslrz.,iC~.(P~,.)) , by the definition of TGShift. Summarizing, we have
(~.[SeI(PCo.,, r)], ~n[SeK~Co.,, s)]) E TGShift(~., AbsPair~.,/Ci. (Pico. 11.
(4.17)
The desired result is now obtained from (4.14), (4.15), (4.16), (4.17) and an application of the first disjunct in the definition of Convert.
case 2: (/Ci,=/r, Kin/s E Vars). Here we have
SeI(X:,., ,.) = s~(~:,,,, ,-').(z, v) ~ SeI(X:,.,s) = SeI(X:,.,s').(~, V), (4.18) and
Sel(/Co~,t,r I = Sel(/Ci,=,r').(l,fl & Sel(/Co,,t,s) = Sel(/Co=,s').(k,f), (4.19) for r ~,s~ E O(/Ci,), l,k E -~ such that r = r~.l, s = s~.k and f either V, a functor or a constant, depending on whether the substitution a further instantiates ICi,,/r = ICi,/s. If the variable is not instantiated by the substitution, then Sel(/Co,,t, r) = Sel(}C/n , r / & Sel(/Cout, s) = Sel(lCin, s), and the result is obtained as in case 1 above. Otherwise we observe that
(~.[SoKJC~.,r)],~.[S~I(IC~.,s)]) E AbsPair~,=,/Ci,=(Px:,.,), from (r, s) E PK:.. and
the definition of C TGShift(~,,, AbsPairTo~,Ki,,(P~q.,)), by the definition of TGShift.
AbsPair~.,,/Ci,~,
Summarizing, we have
(~.[Sel(~:,.,r)],~.[SeI(~:,.,s)]) E
TGShift(Tin, AbsPair~,~./Ci,~(P~) ).
(4.20)
The desired result is now obtained from (4.141, (4.151, (4.18), (4.19), (4.201 and an application of the second disjunct in the definition of Convert.
[]
Lemma 4.2.14 (Safety of BindingEdges(-,-, i, j)) Assuming Condition 4.~.10, it follows that AbsPalr,To,a, iCo.t(BindingPairs( ICir. , i, j) ) C_ BindingEdges(~,~, To,a, i, j).
P R I M I T I V E OPERATIONS
4.2.
Proof.
Each element of
83
AbsPairT-o~t, Eo,,t(BindingPairs(ICi,,,i~) ) has the form
(7"o,t[Sel(~out, i.p)], "To.~t[Sel(/Cout, j.p)]), where
(i.p,j.p (JCinli.p 9
Wrs
9
o(Jcin)),
v
IC,nlj.p 9
(4.21)
Wrs).
(4.22)
Using (4.21), Condition 4.2.10.3a and P r o p e r t y 4.1.13, we have
SeI(lq., i.p), Sel(~:,., j.p) 9 S(~.). F r o m (4.22) and the definition of Sel, it follows in the case where p = c t h a t
Set(~:i,, i.p) = (i, v) v Sel(t:,,, j.p) = (j, V). I f p r c, it follows from ('4.22), Condition 4.2.10.5 and the definition of Sel t h a t there exist a functor g, a selector T, a p a t h p~ E O(lCin/i) nO(lCin/j) and 1 E 1~r satisfying g = K:in[i] =/(:in[j] & P =
p'.l & T = Sel(ICin/i,p') = Sel(ICi,Jj, p'),
and Sel(K;i,,
i.p) = (i, g).T.(l, V) V Se[(K~in , j.p)
=
(j, g).T.(l, V).
It follows f r o m the definition of substitution application and Condition 4.2.10.5 t h a t i.p, j.p E O(K;o,~t), and if p -- e then Sel(K:out, i.p) : (i, f ) & Sel(/Cout,
j.p) : (j, f),
else Sel(/Co,,t,
i.p) = (i, g).T.(l, f) & Sel(/Co,,t, j.p) = (j, g).T.(l, f),
where f is either V, a functor or a constant. From the safety of TGUnify (Theo r e m 2.3.9), we have K;o,,t E TGEnvConc(To,,t'), so f r o m P r o p e r t y 4.1.13, it follows t h a t for b o t h cases Sel(Eo,,t, i.p), Sel(/Co~,t, j.p) E $(To,,t). Finally, by the definition of BindingFdges, we have
('-l"o,~,[Sel(ICo~,t, i.p)], To~,t[Sel( ICo,,,, j.p)]) E BindingEdges(Tin, To,t, i, j), as desired.
[]
We are now in a position to prove the m a i n result of the section. We restate the proposition. Theorem
4.2.11, Part
1. AbsPair.-r
~
3:
Assuming Condition 4.~.10, it follows that
(CShr~
2. AbsPairTZ O ~ t J~(CShr~) ~O~t~ ~ o ~ t
C TGShift(To,,t, AShr~-o.,), C TGShift(To, mAShr~o.,). --
C H A P T E R 4. SHARING ANALYSIS
84
Proof. Applying Conditions 4.2.10.5 and 4.2.10.6 and expanding the definitions of Unify and AbstrUnify, we can rewrite 1. as
AbsPairT-o~,t,K:o.t(TermShift(K:o,,t, CShr~c,..)) C TGShift(To.t, Convert(~.,AShr~-,To.t)).
(4.23)
We observe that
A bsPair'/'o.t,K:o.t (Term Shift (K: o,,t, CShr~c,~)) C TGShift(7"o,,t, AbsPairT'o.t, ICo,,t(CShr~..)), by Lemma 4.2.12 (the safety of TGShift),
C TGShift('To.t, Convert(~,,, AbsPairTi,jCi,~(CShr~c,. ' ), To.,)), by Lemma 4.2.13 (the safety of Convert), and the monotonicity of TGShift, C_ T GShift( 7"o,t, Convert(7~,,, TGShift(Ti,,, AShr~-~.),To,t)), by Condition 4.2.10.3c, and the monotonicity of TGShift and Convert,
= TGShift(7"o.t, Convert(Ti,., AShr~-,.., To.t)), by the definition of Convert, and the idempotence of TGShift. Summarizing, we get (4.23), as desired. Applying Conditions 4.2.10.5 and 4.2.10.6 and expanding the definitions of Unify and AbstrUnify, we can rewrite 2. as
AbsPairTo.jCo.t(TransitiveClosure(AccNew LJ New)) C_AlternatingCIosure(C, B). First we relate AccNew and C.
AbsPaira- /C (AccNew) = AbsPairTo~t,/~o.t(TermShift(/Co.t, CShr~c,.)), by the definition of AccNew,
C TGShift(To.t, AbsPairTo.t,/Co.t(CShr~c,~)), by Lemma 4.2.12 (the safety of TGShift), c_ TGShift(7"..,, Convert( ., AbsPa
.),
by Lemma 4.2.13 (the safety of Convert), and the monotonicity of TGShift,
C TGShift(To.t, Convert(~,,, TGShift(~,~, AShr~-), To.t)), by Condition 4.2.10.3c, and the monotonicity of TGShift and Convert,
= TGShift(To.,, Convert(~., AShr~-, To.t)), by the definition of Convert, and the idempotence of TGShift, = C,
by the definition of C.
(4.24)
4.2. P R I M I T I V E O P E R A T I O N S
85
Summarizing, we have
AbsPairTo,,t, iCo,a(AccNew ) C_ C.
(4.25)
Similarly,
AbsPair To,t,IC o,,t ( New ) = AbsPairTo,,t, ICo,,t(TerrnShift(ICo,,t , BindingPairs(/Ci,~, i, j))), by the definition of New,
C_ T GShift( To,,t, AbsPair To~,t,lCo,,t ( BindingPairs( IC~,. i, j ) ) ), by Lemma 4.2.12 (the safety of TGShift),
C_ T GShift( To,t, BindingEdges(T~n, Tout, i, j ) ), by Lemma 4.2.14 (the safety of BindingEdges), and the monotonicity of TGShift, = B, by definition of B. Summarizing, we have
AbsPairTo,,t,iCo~t(New)
C B.
(4.26)
Finally, we prove (4.24).
U New)) C_ AlternatingCIosure(AbsPairTo~t,iCo~t(AccNew),
AbsPair']-o.t,ICo.t(TransitiveCIosure(AccNew
AbsPair To~t,IC o,,t ( New ) ),
by Lemma 4.1.21 (the safety of AlternatingClosure), C_ AlternatingClosure(C, B), by (4.25), (4.26), and the monotonicity of AlternatingCIosure.
[] 4.2.1.2
Xi : f ( X i l , . . . , X i j )
The main difference with the basic operation Xi : Xj is the specification of the set BindlngPairs describing places where new sharing is introduced by the concrete unification. If the left-hand side Xi is a free variable, then the operation is in fact a construction operation, otherwise we call it a selection operation. The set BindingPairs is defined as the union of two sets; however, one of these sets will be empty depending on whether the operation is a selection or a construction. D e f i n i t i o n 4.2.15 For a concrete sharing environment QCi,~,(CShr~:,~,CShr~c,,.))
and 1 < i, i l , . . . , ij < arity(lCin[r f is a functor of arity j,
such that i, i l , . . . , ij are pairwise distinct and
Unify((/Cin, (CShr~c,. , CShr~,.)), i, f( il, . . . , ij ) ) = if then else
mgu(ICinli, f(ICinlil,...,ICinlij)) fail (ICo,a, (CShr~co,,,, CShr~co,,,)),
is fail
86
C H A P T E R 4. S H A R I N G A N A L Y S I S
where ICont ~ ICing, for a = mgu(ICinli, f(K.i,~lil,..., PC,,/i,)), and
CShr~co," = TerrnShift(/Cont, CShr~c,.,), AccNew - TermShiff(/Cout, CShr~:,,), New = TerrnShift(ico~t, B i n d i n g P a i r s ( I C i , ~ , i , f ( i l , . . . , i j ) ) ) , CShr~:o,, - TransitiveCIosure(AccNew U New), BindingPairs(ici~ , i, f ( i l , . . . , ij ) ) =
{
(i.t.p, Q.p)
u
{(i.l,
(ICi,Ji.t.p e Vars v ICi,JQ.p 9 Vars) 11 <_ t <_ j 9 Vars}.
}
The next lemma, stating that the initial sharing is preserved, can be proved in the same way as Lemma 4.2.2 for the basic operation Xi = X 1. L e m m a 4'2.16 Let (icin, CSharingK:,.) be a concrete sharing environment. Let {/Co,t, CSharingjco.,) = Unify((/Cin, CSharing,:,~), i, f ( Q , . . . , ij)) (Unify does not fail), where 1 < i, i x , . . . , i S <_ arity(ici,,[c]) are pairwise distinct, and f is a functor of arity j. Then CSharingjc~., C_ CSharingjco,.. The proof that Definition 4.2.15 guarantees the concrete unification operation Unify(-,/, f ( i l , . . . , i j ) ) to be well defined on the domain of concrete sharing environments is almost identical to the proof of the well-definedness of the Unify(-, i, j) operation. First we demonstrate that New = TermShift(/Cont, BindingPairs(ici,,,i,f(il,...,ij))) is a pre-sharing component for Icont. Second, using the previous result, we prove that CShr~:~ AccNew, CShr~o., and CSharing~:.~, = TransitiveCIosure(CShr~:.., U CShr~n,) , are pre-sharing components for/Cont. Third, we demonstrate that CSharinglco., is a concrete sharing component for/Co~t. Using the fact that it is a pre-sharing component, we still need to show that it satisfies Condition (4.1), concerning multiple occurrences of a free variable in/Cont. L e m m a 4.2.17 Let (icin, CSharing~:~) be a concrete sharing environment. Let (ICon,, CSharing/c..,) = Unify((/Cin, CSharingjc,..), i, f ( i l , . . ., ij) ) (Unify does not fail), where 1 _< i, i x , . . . , i j <_ arity(ICin[e]), such that i, i z , . . . , / j are pairwise distinct, and f is a functor of arity j. Then New = TermShift(ICo,,t, BindingPairs(ICi,,, i, f ( it, . . . , ij ) ) ) is a pre-sharing component for ~ont. Proof. From the definitions of TermShift and BindingPairs, it is clear that New is a set of unordered pairs of the form (r, s) where r, s 9 O(ICo,,). We must prove that New satisfies Conditions 4.1.4.1-4. C o n d i t i o n 4.1.4.1 is satisfied b y New: Given (r, s) 9 New, we must prove that ICo~t/r - ICo~t/s. The property holds for the set BindingPairs(/Q,,,i, f ( i l , . . . , 6 ) ) as, after unifying ICi,,/i and f ( I C i , , / i l , . . . , ICi,,/ij)), we have
4.2. P R I M I T I V E O P E R A T I O N S
87
that ]Co,,t/i.s - ]Co,,t/it.p for all s satisfying 1 < s < j and for all paths p E O(]Co~,t/i.s The function TermShift preserves the property because corresponding subterms of identical terms are identical. C o n d i t i o n 4.1.4.2 is satisfied b y New: Given (r, s) E New, we must prove that r r s. The property holds for the set BindingPairs(]c~,, i, f ( i x , . . . , ij)), as i, Q , . . . , i s are pairwise distinct. The function TermShift preserves the property. C o n d i t i o n 4.1.4.3 is satisfied b y New: Given (r, s) E New, we have to prove that TermShift(]co,t, {(r, s)}) _C New. This follows from the idempotence of TermShift w.r.t. ]Cant. C o n d i t i o n 4.1.4.4 is satisfied b y New: Given (r, s), (s, t) E New where r t, we must prove that (r,t) E New. From the definitions of TermShift and BindingPairs, we know that (r,s) and (s,t) are both of the form (i.l.p.q, it.p.q), where (i.Lp, it.p) E BindingPairs(]c~,,,i, f ( i l , . . . , i 3 ) ) , so 1 < s < j and p is possibly c. Suppose that s = i.Lp.q (without loss of generality), then r = it.p.q = t, i.e. there are no two different consecutive pairs in New. [] L e m m a 4.2.18 Let (]ci,,, (CShr~c,..,CShr~c.~)) be a concrete sharin9 environment. Let (]co,,t, (CShr~c..,, CShr~..,)) = Unify((]ci,, (CShr~c,~ , CShr~c,,,)),i, f ( i ~ , . . . , is) ) (Unify does not fail}, where 1 < i, i l , . . . , i s < arit~(]c,,[c]), such that i, i l , . . . , i j are pairwise distinct, and f is a functor of arity j. Then CShr~:..,, AccNew, CShr~c.. * and CSharingx:.., are pre-sharing components for ]co~,t (where CSharingx:.., = TransitlveCIosure(CShr~:.., U CShr~c..,) }. Proof. The proof of this lemma is identical to the proof of Lemma 4.2.4, except that Lemma 4.2.17 is used instead of Lemma 4.2.3. r3 The following lemma states that the set of concrete sharing environments is closed under the Unify(-, i, f ( i l , . . . , is) ) operation. L e m m a 4.2.19 Bet (]ci,, CSharingx:~.) be a concrete sharing environment. I.et (]co,t, CSharingjc..,)= Unify((]ci,, CSharing~:,~), i, f ( il, . . ., is ) ) (Unify does not fail), where 1 < i , Q , . . . , i s < arity(]ci,[e]) such that i, i l , . . . , i s are pairwise distinct, and f is a functor of arity j. Then QCo,~t, CSharingx:..,) is a concrete sharin# environment. Proof.
By the definition of Unify, we have ]co,,t = ]ci,, a, where r = mgu(]ci,,/i,
]c .lis))
is not fail. Because
CSharlng
,.) is a concrete
har-
ing environment, it follows from the definition of substitution application that the term ]co,,t has principal functor 0, and that O(]Q=) C_ O(]co,,t). From Lemma 4.2.18 we know that the sets CShr~:o.,, CShr~.,, and CSharing,~:.,., =
C H A P T E R 4. S H A R I N G A N A L Y S I S
88
TransltiveClosure(CShr~co," U CShr~co.,) , are pre-sharing components for/Co,,t. So, we still have to prove that CSharing~o., satisfies Condition (4.1), i.e.,
(ICo,,t/r = ICo.t/s) & (/Co.Jr E Vars) & (r ~: s) =~ (r, s) E CSharing,:o.,. From the relationship between mgu's and solved form equation sets [47] we know that mg,,(JC~./i,/(JC~./il,..., ~:~./iS)) can be obtained by transforming the solvable equation set E= = { ~ . l i
= f(~.li,,...,
~.li~)}
into its equivalent solved form (a is a most general unifier of Eo). Again, we wish to observe the effect of the algorithm on the terms in the environment as it incrementally isolatesand applies variable bindings. W e consider the equation set
Ei,~=
ICi./i
=
/(ICi,Uit,...,ICi./i~)
formed from Eo by adding an auxiliary equation of the form X = K;i,~,where X is a new variable not occurring elsewhere in the system. Clearly Ei,~ is solvable iff Eo is. Let
J~o~t = be the solved form of E~,~. Since Ei,~ and Eo,,e are equivalent, they have the same most general unifiers. Let ")' be one. Then it follows that /C = X 7 =/Ci,~cr (modulo renaming) =/Co~t. From the fact that Unify(~/Ci,~,CSharingpc,.,), i, f ( i l , . . . , lows that two cases are possible.
ij)) does not fail, it fol-
case 1: lCi,Ji E Vars & ICi,~/i ~ Vars(f(ICi,JQ,...,ICi,Jij)). case 2= ICi,~[i] = S, where f is a functor of arity j. In case 1, assuming ICi,Ji = Z, step 5 of the Solved Form Algorithm is applicable, replaces Z by f(ICi,~/Q,..., ICi,Jij) in the auxiliary equation X = /Ci,~, and then halts. Given paths r,q such that r,q E O(/Co,,t) & r ~ q & ICo,,t/v = ICo,,t/q & ICo,,t/T" ~ Vars, we must prove that (r, q) G CSharing~c~ Let ICo,,t/r - ICo,,Jq = Y . Since/Co,,t is constructed from/Ci,~ according to Step 5, we know that each s E O(/Co,,t) such that/Co,,t/s = Y must satisfy one of the following.
or
~$1,~a, 8 II . I
8 = 81.~a.a II
l<_l,<_j
9' ~ o ( ~ . ) 9" e o ( ~ . / i ~ . )
~ ~.I~' = z ~ ~ ~o ~, / , = ~ . / i ~ . . , " -
~ Y
/
(4.28)
4.2.
PRIMITIVE
OPERATIONS
89
Note that for case (4.28), we have lCi,~/s' - Z = ]Ci,,/i. So, because CSharingjc, = TransitiveCIosure(CShr~c,. ' U CShr~:,,) satisfies Condition (4.1), we have t h a t either s' = i or (s I, i) E CSharing~ci.,. So, using L e m m a 4.2.18, and CSharingjci. C_ CShadng;c~ ( L e m m a 4.2.16), we have either s = i.g,.s" or (s,i.g,.s") E CSharing,c..,. From ]Ci,,/i E Vats and the definition of Unify, we know t h a t
(i.ls .s", tt..s " " ) E New C CSharing~o~,. So, using again L e m m a 4.2.18, a more convenient consequence of (4.28) is
3g,, s" : 1 < ts <_ j & ]Ci,~/it,.s" ----Y & (s, it,.s") E CSharingjco.,
(4.29)
We shall now show t h a t (r, q) E CSharingico., by a case analysis based on which of (4.27) or (4.29) holds for r and q. Suppose r and q both satisfy (4.27), i.e. r, q E O(]Ci,,) and ]C~,,/r = Y =_ ]C~,,/q. Because CSharing,:,~ satisfies Condition (4.1) and r # q, we have (r, q) E CSharing;c~,, C CSharingjc .... as desired. Suppose r and q b o t h satisfy (4.29), i.e. 3 & , r " , g q , q " : 1 < g,,gq < j &
(r, it..r") E CSharing;co.,
&
(q, it,.q") E CSharing;co., &
(ICinlit..r" =
Y
-- ]Ci,~/it, .q").
Because CSharing,c,~ = TransitiveClosure(CShr~c,~ U CShr~c,~ ) satisfies Oondition (4.1), we have l..
II
9
= ll,.q
II
V (it..
7,11 ~
II\
, l,.q ) E CSharingjq .
It follows from ~" :/: q, CSharing,c~ C CSharingjc .... and L e m m a 4.2.18, that (r, q) E CSharing~:o~,. Otherwise, one of r and q satisfies (4.27) (assume it is r, without loss of generality), and the other (thus q) satisfies (4.29). From r satisfying (4.27) we know K:~,,/r = Y, and from q satisfying (4.29) we know 3gq,q" : 1 <_ gq < j & ]Ci,Jit,.q" =- Y & (q, it,.q") E CSharing;c..,. Because CShating,c,.. satisfies Condition (4.1), we have either r = it,.q" or (r, i t , . q ' ) E CSharing,c,. C_ CSharing,:o.,. It follows from r # q , (q, it,.q") E CSharing,: .... and Lemma4.2.18, t h a t (r, q) E CSharing;co.,. In e a s e 2, step 1 of the Solved Form Algorithm is applicable, and results in the set of equations
E~,~ =
X
= K:i,, }
ICin/i.1
=.
ICi,~/il
.
Similarly as in the proof of L e m m a 4.2.5, one can show t h a t the following induction hypotheses hold at each stage of the Solved Form Algorithm given by
90
C H A P T E R 4. SHARING A N A L Y S I S
an equation set E and a term/C, where
E =
X
=
/C}
tl
=
t2
"
Induction hypotheses:
(~) i.t.s, i~.s ~ o(~c) & pc/i.l.s ~ w~s v ~:/i~.s E w~s) ~ (i.d.s, i~.s) ~ N ~ (b) Vr, q E O(/C): (IC/r =_ IC/q & /C/r E Vats & r :fi q => (r,q) E CSharlngjc..t) (c) For each tx = t2 in E that is not of the auxiliary form X =/C and that has not yet been the subject of Step 5, there exists a path s E O(Ig/i.l) n O(lC/it) such that either tt = IC/i.s t2 =- IC/it.s or t2 = IC/i.Ls, tl =- IC/it.s Clearly, Condition (b) expressed for the stage given by the solved form equation set Eo~. and the term K; gives the conclusion of the lemma. [] Next, we give the definition of the abstract operation. D e f i n i t i o n 4 . 2 . 2 0 For an abstract sharing environment (Ti,,',(AShr~r~, AShr~%)) and 1 < i, Q , . . . , ij < arity(Ti~[e]) such that i, Q , . . . , i j are pairwise distinct, and f is a functor of aritv j,
AbstrUnify((Ti,,', (AShr~-~., AShr~-)), i, f(il,..., ij)) = if then
TGUnify(Ti,~',i,f(il,...,ij)) fail
else
(To,,,', (AShr~-..,, AShr~-..,)),
is fail
lllhere
To,,," = TGUnify(T~.', i, f(ix,..., ij)), and
AShr~-.., C
= =
Convert(Ti,,,AShr~-,To.t), TGShift(To,,,, Convert(~n, AShr~%,Toat)),
B = TGShift(To,,, BindingEdges(T/,,, To,t, i, f ( i l , . . . , ij))), TGShift(To.,, gShr~..,) = AlternatingClosure(C, B). We still have to define the auxiliary procedure BindingEdges. It is the type-graph analogue of the BindingPairs function for concrete terms. Definition 4.2.21 For an abstract term environment 7"i,~e, 1 < i, i l , . . . , i j < arity(Ti,~[e]) such that i, i l , . . . , ij are pairwise distinct, f is a functor of arity j
4.2. P R I M I T I V E
OPERATIONS
91
and Tout" = TGUnify(Yi,,', i, f ( i l , . . . , ij)), BindingEdges(T/n, Tout, i, f ( il , . . . , ij ) ) =
(TO.,[R], TO.,[S]) 13R,S 9 S(TO.,), 3h, g: h is a functor, a constant or V & 1 < g < j & ([R = (i, f).(g, h) & S = (it, h) & ((i, V) 9 S(Ti,,)V (i, f).(g, V) 9 S(T/,,) V (it, V) E S('T/.))] (either the i th, the (i, f ) . l th or the il h component of Tin is itself a V-node)
]
I
[3g, Y,k : R = ( i , f ) . ( g , g ) . T . ( k , h ) & S = ( i t , g ) . T . ( k , h ) & ( (i, f).(t, g).T.(k, V) 9 S(7-in) v (it~ g).T.(k, V) 9 S(~n))] (or there is a V-node inside the (i, f).gt~' or component of Ti,,)) The following theorem gives the correctness condition for the AbstrUnify operation. Condition 4.2.22
1. (~,,', (AShrams, AShr~,~)) is an abs~.ract sharing environment ~. ( ~ . , (CShrL., CShr~,.)) is a conc~et~ sha~ing environment
3. (/Ci., (CShr~c~.,CShr~c,~)) E InstrEnvConc((~.', (AShr~- ,AShr~- ))), that is, (a) ICi,~ 9 TGEnvConc(Tin ~)
(b) CShr~c..,CShr~c~ are pre-sharin9 components for PCin, and TransitiveCIosure(CShr~c~ U CShr~c~..) is a sharing component for ICin (c) AbsPairT/,~,/Ci.(CShr~c~..)C TGShift(Ti,~,AShr~-
) and AbsPairTi,.,/Ci.(CShr~,.) C_ TGShift(~,~, AShr~- )
4. 1 ___ i, i~,..., i; _ a~ity (~-[d) = a,'it,./ (~:,-[d) s~ch that i, i , , . . . , i, a,'~ pairwise distinct and f is a functor of arity j ~ 5. (/Co.t , (CShr~c..,, CShr~c..,) ) = Unify((Ei,~, (CSh r x:,~, CShr~:,.)), i,
f(i,,...,ij))),
/s not fail
6. (To.t e, (AShr~-..,, AShr~-..,)) = AbstrUnify((Ti. e, (AShr~,,~, AShr~-)), i, f(ix,...,ij))) T h e o r e m 4.2.23 ( S a f e t y of AbstrUnify(-, i, f ( i l , . . . , i j ) ) ) tion 4 . ~ . ~ , it follows that
Assuming
Condi-
(/Co,t, (CShr~c..,, CShr~co.,) ) E InstrEnvConc((To.t', (AShr~-o.,, AShr~-..,))). Proof. The proof of this theorem is identical to the proof of Theorem 4.2.11, except that Theorem 2.3.10 is used instead of Theorem 2.3.9, and Lemma 4.2.24 instead of Lemma 4.2.14. []
92
CHAPTER
4. S H A R I N G
ANALYSIS
L e m m a 4.2.24 ( S a f e t y o f BindingEdges(-, - , i, f(i~, . . ., ii))) Assuming Condition 4 . ~ . ~ , it follows that
AbsPairTo,,t,iCo,,t(BindingPairs(ICi,~ , i, f ( i l , . . ., ij ))) C_ BindingEdges(Ti,, To,,t, i, f ( i l , . . . , ij)). Each element of AbsPair To,JC o,,t ( BindingPairs( ICi,, i, f ( il , . . . , ij ) ) ) has
Proof.
the form (~'o~,[Sel(~o.,, i.t.p)], 7-o.,[Set(~ o.t, it.p)]), where
(i.s
0(ICi,)) (ICi,~li.s 6 Vats
& v
(l
&
]Ci,Jit.p 6 Vats),
(4.30)
or
(p = ~ ~ (i <_ t < j) ~ ~.,./i ~ v~ts).
(4.31)
We first consider case (4.30). Using Condition 4.2.22.3a and Property 4.1.13, we have Sel(]Cin, i.l.p), Se](]Cin, il.p) 6 S(7-i,~). From (4.30), Condition 4.2.22.5 (the unification succeeds) and the definition of Ssl, it follows that if p = e then
SeI(~:,,, i.l.p) : . v s,i(~:,,, it.p) : , otherwise there exist a functor g, a selector T, a path p' 6 0 ( ] C ~ , / i . l ) n O ( ] C i , / Q ) and k 6 / V , satisfying
g =/c,,[i.l] : ~,.[it] u p : p'.k ~ T : S , l ( ~ , , / i . t , / )
: Sel(/C,./it,/),
and Sel(/Ci,=,i.s
= (i,f).(l,g).T.(k,V) V Sel(/Ci,,it.p): (it,g).T.(/r V).
From the definition of substitution application and Condition 4.2.22.5, we have that i.Lp,Q.p 60(]~o~,t),and if p = e then
Se[(ICon,, i.l..p) -" (i, f).(l., h) a Se[(]gou,, it.p) = (it, h), else
Sel(~:o,,, i.t.p) : (i,/).{t, g).T.{k, a) ~ S01(~o,t, it.p) : {it, g).T.{k, a), where h is either V, a functor or a constant. In case (4.31), it follows from Condition 4.2.22.3a and the definition of Se[ that Sel(~,., i) = (i, V) 6 S ( ~ . ) .
4.2. P R I M I T I V E
OPERATIONS
93
From Condition 4.2.22.5 (the unification succeeds) and the definition of substitution application, we have that i.g, it E O(ICo,,t), and Sel(/Co,,t, i.g) = (i, f}.(g, h) & Sel(/Co,,t, it) = (it, h), where h is either V, a functor or a constant. From the safety of TGUnify (Theorem 2.3.10) we have ICo,,t E TGEnvConc(To,,t'). So, from Property 4.1.13 it follows that for all cases Sel(/Co~t, i.Lp), Sel(/Co~t, it.p) E S(To,,t). Finally, by the definition of BindingEdges, we have
(7"o~,[Se~(~:.,, i.t.p)], 7-o.,[set(~o~,, i,.p)]) E BindingEdges('Ti,,,To,,t,i,f(i~,...,iy)), as desired. []
4.2.2
Procedure
Entry
The procedure-entry operation is applied by the abstract interpretation procedure when expanding the AND-OR-graph. It corresponds to resolving the next call with an applicable clause while constructing a concrete proof tree. A first step of the operation, independent of the clause(s) applicable, consists of restricting the information present in the current environment to the arguments of the call. The second step, which depends on the clause(s) that is (are) applicable, consists of an initialization of the new environment(s). We will only specify the first step of the operation as the second step is straightforward. As discussed in Section 2.3.3 for type-graph environments, the second step serves to pass on the type and sharing information existing for the actual parameters to the formal parameters and to initialize the local variables of the clause to the type 'V'. Note that the local variables initially do not take part in the sharing relation. We represent a concrete environment having domain { X a , . . . , Xn} as a term with principal functor 0 and arity n (see Section 4.1.1). We assume that all Prolog programs are in normal form, so the arguments of the next call can be given as a subset { X q , . . . , X i = } of the domain and an injection ic~u: { 1 , . . . , m } { 1 , . . . , n} : k ~-~ ik can be used to formally define the restriction of the sharing information to the arguments of the call. D e f i n i t i o n 4.2.25 For a concrete term IC = (ICl,...,IC,~}, a set L,c C_ O(IC), and PJc a set of unordered pairs (r, s) such that r, s E O(IC), and an injection i: ( 1 , . . . , m} --. { 1 , . . . , n} (for m _ n), we define Proj(/C, i) Proj(L,:, d) Proj(P~:, i)
=
(~,;(1),..., ~,:(~)),
= =
{k., I k E { 1 , . . . , m } ~ i(~)., E t~}, {(k.s,l.r) l k ,l E { 1 , . . . , m } & (i(k).s,i(l).r) E P,:}.
94
C H A P T E R 4. S H A R I N G A N A L Y S I S
P r o p e r t y 4.2.26 For a concrete t e r m IC such that arity(IC[e]) = n, an injection i : { 1 , . . . , m} -4 { 1 , . . . , n} (for m < n), a a substitution over Vars(Proj(/C, i)), and k.r 9 O(Pro5(~ , i)), we have
1. Proj(/C, i) a = Proj(/Ca, i) e. Proj(/C,i)/k.r =_ JC/i(k).r
3. Proj(O(/C), i) = O(Proj(/C, i)) Proof. These equalities follow immediately from the definition of Proj and the definition of substitution application. [] The arguments of the restriction operation (i.e. the first step of the procedureentry operation) comprise the abstract sharing environment at the call point and the icau injection. The operation always succeeds, because of the normal form of programs. D e f i n i t i o n 4.2.27 For a concrete sharing environment (]Ci,,,(CShr~c,,.,CShr~c,..)) such that arityQCi,,[e]) = n, domr C { 1 , . . . , n } , and ic~u : { 1 , . . . , m } ---* { 1 , . . . , n) an injection (for < n) such that i , , ( { 1 , . . . , m)) = domo,,, Restrict((K;in, (CShr~c,,., CShr~,~)), teal,) = (~.,tr, (CShr~c.... ,0)) lllhere
/Cr, tr CSharingjc,~ CShr~c ....
= = =
Proj(/Ci., icall), TransitiveClosure(CShr~c,~ U CShr~,..), Proj(CSharingjc~, icazO.
The following lemma states that the operation Restrict is well-defined on the set of concrete sharing environments. L e m m a 4.2.28 Let (K;i,~, (CShr~c,..,CShr~,.)) be a concrete sharing environm e n t such that aritu(]Ci,,[e]) = n, domcazz _C { 1 , . . . , n } , and icon: { 1 , . . . , m} ---* { 1 , . . . , n ) an injection (for m <_ n ) such that i,att({1,...,m)) = domcaw Let (/C.,t~, (CShr~c.... , CShr~c..,.)) = Restrict((/Ci,~, (CShr~c.. , CShr~c~..)), it.u). T h e n (IC,,t,, (CShr~:.,,., CShr~: .... )) is a concrete sharin 9 environment. Proof. By the definition of Restrict, we have K:,,t, ---- Proj(K:i,~, i~,,n), CShr~..,. = 0 and CShr~c..,. = Proj(TransitiveCIosure(CShr~c~ U CShr~.),ic.u). The empty set is a pre-sharing component for any term. Because (/Ci,,, (CShr~c,.,, CShr~,~)) is a concrete sharing environment, and because the function Proj preserves Conditions 4.1.4 and (4.1), it follows that CShr~c.... is a concrete sharing component for K:~ot~. Hence (]~r,t~, (CShr~: .... , CShr~.,.)) is a concrete sharing environment. [] To define the abstract restriction operation, we first introduce a type-graph analogue of the function proj for concrete terms that translates sharing edges for a type graph T into sharing edges for a type graph T~,t~ constructed from T by selecting subgraphs according to an injection i.
95
4.2. P R I M I T I V E O P E R A T I O N S
D e f i n i t i o n 4.2.29 For an abstract term environment T ~ with arity(T[e]) = n, a set PT of unordered pairs (p,q) such that p,q 9 NodesT & Label(p) ' o r ' a Label(q) r 'Or', a~d an injection i : { 1 , . . . , - q -4 { ~ , . . . , ~ } (for m < n) such thai Tr,tr e = TGRestrict(T e, i), we define
AbstrProj(T, P:r, T.,t. i) = (k, f)..5', (1, g).R e ,.q(Tr,t,)
{ (%..[(k,
I).S],
%.,.[(~, g).R])
k, z e ( 1 , . . . , , ~ )
(i(k), f).s, (i(Z),g).R e S ( Z ) ~ (T[(i(k), f).S], T[(i(1), g).R]) 9 P~-
]
!
9
D e f l n i t l o n 4 . 2 . 3 0 For a~. abstract sharing environment (~n~,(AShr~-~,., ASh~,~)) such that arit~(~[~]) = ~, dora,, C (1, . . . , ,q, and i,,, : {1, . . . , m} -~ {1,..., ~) an injeaion (/or ~ _< ,q such that io~,({1,..., ,q) = domo~,, AbstrRestrict((Ti,", (AShr~-~, AShram,,)), ir
= (T~,t,', (AShr~..,., g))
where
"T~,t,' = TGRestrict(~.", i~u), ASharinga~ = AlternatingCIosure(TGShift(T, ASh foa~), TGShift(T, AShr~- )), TGShift(~,t~, AShr~- ) = AbstrProj(~,~, ASharingT~, "]~,tr, ic~u). The following theorem gives the correctness condition for the AbstrRestrict operation. T h e o r e m 4.2.31 ( S a f e t y of AbstrRestrict) Assuming the following conditions 1. ( ~ , ' , (AShr~-, AShr~-,~)) is an abstract sharing environment
2. (~,., (CSh%., CShr~,.)) is a concrete sharing environment 3. (]Ci•, (CShr~Q~, CShr~:,~)) E InstrEnvConc((Ti,', (AShr~r,~, AShr~%.))), that is, (a) ~,~ e TGE,vCo.c(%Z) (b) CShr~c,~,CShr~, ~ are pre-sharing components for tgi,, and TransitiveClosure(CShr~c,~ u CShr~,..) is a concrete sharing component for If.in (c)
AbsPair~ /C (CShr~c)C TGShift(%,~,AShr~. ) AbsPair~,jCi,,(CShr~c~ ) C "l-GShift(~., AShr.~)
5. ic~zz : { 1 , . . . , m }
--* { 1 , . . . , n } i~,u({1,..., re}) = dom~,u
6. (]Cr, t. , (CShr~c...,
and
is an injection (for m <_ n) such that
CShr~c.,,.)) = Restrict((/Cin, (CShr~c.. , CShr~c.~)), ir
96
C H A P T E R 4. S H A R I N G A N A L Y S I S
7. (Tr,t.',(AShr~-, AShr~- )) = AbstrRestrict((~.e,(AShr~q,,,AShr~q.)),
it=.) it follows that
(K:..r, (CShr~c.. ., CShr~..,.)) e InstrEnvConc((7"~0tr', (AShr~-, AShr~-
))).
Before proving the theorem itself, we first give a lemma that contributes to the proof. L e m m a 4.2.32 ( S a f e t y of AbstrProj) Let Ti~ e be an abstract term environment such that n = arity(~[e]), and/Ci, E TGEnvConc(Ti,~'), and P~z,~ a set of unoraered 'pairs of the form (r, s) where r, s E O(ICi,). Let i,an : {1,..., m} {1,..., n} (for m < n) be an injection such that T,,t," = TGRestrict(Ti,,', i,,zz) & K:r, tr --~ Proj(Kin, i~=n). Then
AbsPairT.,t,~g:.,,.(Proj(P~z,~, it=n)) C_ AbstrProj(T/,,,AbsPairTi,~,/Ci.(P~c,.),~~ Proof.
After expanding the definitions of AbsPair-r /C 2 rOt'P~ r
AbsPair7-..K:.~
{(7-
i,=n)) =
l
(4.32) ic=u). and Proj, we have
(i~=,(k).,,io~,(l).r) e P~,.
For arbitrary k, l, s, r such that k, l E {1,..., m} and (ir we must prove that
}
"
ic=n(l).r) E Ppc,.,
(~r,,,,[Sei(~:,,,,, k.s)], ~,,,[Sel(~:,,,,, l.,)]) E AbstrProj(T~.,AbsPair~n,K:i.(Pjc,.),T~,t.,ic=n). By the definition of AbstrProj, it is sufficient to prove that
SeI(JC,,,,. ~.s), sel(~,,,,,l.r) Sel(K:i,~,i~=u(k).s),Sel(/Ci,,,i~tr(/).r)
e s(~~ e S(T/.),
(7~.[Sel(~., io..(k).s)], ~.[Sel(~,., i..(0.r)]) E AbsPairTi,~,/Ci.(Ppc~, ,), such that if SeI(/C,,,, k.,) = (k, f).S, then Sel(/Ci,,ir and similarly if Sel(/(:,,~, l.r) = (1, g).R, then Sel(/Ci,, ir
(4.33) (4.34) (4.35) = (i,=n(k), f).S, = (it=n(/), g).R.
From the definition of AbsPair.Ti.,/Ci. and (ic=zz(k).s, ic.zz(l).r) E PJc,.., we derive (4.35). Because/C,, E TGEnvConc(Ti,~'), we get (4.34) from ican(k).s, icazt(l).r E O(ICi~) and Property 4.1.13. From/C,,,, = Proj(/Ci,,, i,=u), k, l E {1,..., rn}, ir i,,n(l).r e O(K:,.) and Property 4.2.26.3, it is clear that k.s, l.r E O(/C,,t,), IC,,t,[k] =- /Ci,,[i,=u(k)],
4.2.
PRIMITIVE
OPERATIONS
97
/C,,t,[/] - /C,,[i~u(l)], IC,,t,./k =- /Cin/i~u(k) and IC,,t,./l =__ /Ci,/i~u(l). Now, suppose that Sel(/C,,t,, k.s) = {k., f ) . S . If s -- e, then so is S, and f is V, a functor or a constant, depending on the value oflC,,t~/k. But then SeI(/C~, i~u(k)) = (icau(k), f), because K , s t r / k = IC,,,/icau(k). If s # e, then f = IC,str[k] : /C~jir and S = Sel(IC,ot,/k, s) = Sel(ICi,,/i~u(k), s), by the definition of SeL Hence SeI(K:i,t, icau(k).s) -- (lean(k),f).S. From the assumptions of the lemma and Theorem 2.3.12, we have /C~,t~ E TGEnvConc(T~,t~'). So, from Property 4.1.13 and k.s,l.r ~ O(/C~,t~), we derive (4.33). []
Proof. [ T h e o r e m 4.2.31 ( S a f e t y o f AbstrRestrict)] We must prove that
(~,,t,, (CShr~c.,.,CShr~c.o,.)) E InstrEnvConc((Tr,tr~, (AShr~..,.,AShr~- ))), that is,
Part I. ICrst, E TGEnvConc(~are), Part 2. CShr~..,.,CShr~..,. are pre-sharing components for IC,,t,. and TransitiveClosure(CShr~c.o,. U CShr~c.... ) is a concrete sharing component for/C~stt ,
P a r t 3. AbsPairqAbsPairT r
~- (CShr~ I C TGShift(T, st,, AShr~(CShr~ I C TGShift(T,,t,, nShr~- .,.).
r s t r , ~ r s t r
x
~ r . t r
i
) and
--
P a r t 1. is a restatement of Theorem 2.3.12. P a r t 2. follows from the fact that Restrict is well defined (Lemma 4.2.28). Expanding the definitions of Restrict and AbstrRestrict, we can rewrite P a r t 3. as
AbsPair.T
~-
9 ~$t!P~l~lPst!p~
(CShr~
P~st~l
~ C AbstrProj(~,~, ASharingT-i~, T~,t~, lean), (4.36) --
AbsPairT~,t,jC,,t,(0 ) C TGShift('/-,,t,,r
(4.37)
Obviously, (4.37) is satisfied. To prove (4.36) we observe that
AbsPair.T v- (CShr ~~ x . r s t r / I yStrl]~'t,#tT~ = AbsPair.T ~- (Proj(TransitiveCIosure(CShr ~
U CShr~c~),icau)),
by the definition of Restrict,
C_ AbstrProj(Ti,~, AbsPairTi,jCi,~(TransitiveCIosure(CShr~c,,, U CShr~c,~)), T~,t~, ican), by Lemma 4.2.32 (the safety of AbstrProj),
C AbstrProj(~,,, AlternatingCIosure(AbsPair~,~,Kin(CShr~c,.), AbsPairTin,K:i,~(CShr~c~)), 7"rstr, icall), by Lemma 4.1.21 (the safety of AlternatingCIosure), and the monotonicity of AbstrProj,
98
C H A P T E R 4. S H A R I N G A N A L Y S I S
C AbstrProj(Ti,,, AlternatingCIosure( TGShift(Ti,t, AShr~,..), TGShift(Ti,,, AShr~-~..)), T,,t,, i~,u), by Condition 4.2.31.3c, and the monotonicity of AbstrProj and AlternatingClosure, = AbstrProj(~,=, ASharingT~, T~,t~, ic=/z), by the definition of ASharing~r~ . Summarizing, we get (4.36), as desired.
El
For the procedure-exit operation (Section 4.2.3), a special version of the restriction operation is needed that restricts the abstract sharing environment at the last program point of some clause to the variables of the head. It differs from the version needed on procedure entry, in that the new sharing edges created in the current environment, are not joined with the old sharing edges to become the set of old edges for a lower level of the proof tree (resp. AND-OR-graph in the abstract case). Procedure exit is needed to move one level upwards instead. The new edges of the current environment will also be considered as new edges at that higher level. We omit the proofs of the well-definedness of the concrete operation and the safety of the abstract operation as they are rather straightforward. D e f i n i t i o n 4.2.33 For a concrete sharing environment (/Ci,=,(CShr~c,~,CShr~,.))
such tkat grity(/Ci~=[e]) = r~, domhead C_ { 1 , . . . , n } , and ihead : {1,..., m} --~ (1 .... , . } an injection (for m <_ . ) such that i,,ad((1,... , rn}) = domhead,
Restrict2((/Ci., (CShr~c,. , CShr~c,.)) , ih,ad) = (/C.str, (CShr~c..., CShr~..,.)) where ]Crstr
=
Proj(/Ci~, ihead),
CShr~c.... CShr~..,.
=
Proj(CShr~,..,ihead), Proj(CShr~c,,,,ih,ad ).
=
D e f i n i t i o n 4.2.34 For an ab,tract ,hating environment (Ti,,', (AShr~.,AShr~-;.))
,.ch thct =/qty('.T/n[.]) = ~, domhead C ( 1 , . . . , n } , grid ihead : ( 1 , . . . , r n } --~ ( 1 , . . . , . } =n i.jection (for m <_ . ) such th=t ihead((1,..., rn}) ----domhead, AbstrRestrlct~((Ti.', (AShr~.,,, AShr~.)), ihead) = (Trst.', (AShr~-,., AShr~- ,.)) 1//here ~r=tr r
---- TGRestrict(T~,=', ihe=d),
TGShift(T~,t,, AShr~r ,.) TGShift(T~,t,, AShr~- )
=
AbstrProj(T~,,, TGShift(T~,,, AShr~r,.), 7/-,,t,, ihe=d),
=
AbstrProj(T~,~, TGShift(T~,,, AShr~-), T~,t~, ih,=d).
4.2.3
Procedure
Exit
The procedure-exit operation is applied as soon as the a b s t r a c t interpretation of the different clauses matching a call is completed. In a first step, the abstract
4.2.
PRIMITIVE
99
OPERATIONS
sharing environment at the last program point of each applicable clause is restricted (see Section 4.2.2) to the arguments of the head of the clause. Then these restricted abstract sharing environments (all having the same arity) are combined by the upperbound operation (i.e. an over approximation is computed, see Section 4.1.4). In a second step, the extension operation regains the information about the calling environment that was lost by the restriction operation on procedure entry (it computes the effect of the call on the variables of the environment that are not passed as arguments to the call). In this section, we present only the extension operation that is needed in the second step of the procedure-exit operation. Proving the safety of the first step is straightforward, as was discussed in Section 2.3.3. Again, we start with the instrumentation of the concrete extension operation for the domain of concrete sharing environments. We need the following auxiliary function. D e f i n i t i o n 4.2.35 For a concrete t e r m IC such that arity(IC[e]) = m & ra < n, an injection i : { 1 , . . . , m } --, { 1 , . . . , n } and a set Px: of unordered pairs (r,s) such that r, s E O(IC), we define
RevProj(Pjc, i) = {(i(k).r, i(l).s) I k, t E {i,...,
m} ~ (k.r, l.s) E Px:}.
D e f i n i t i o n 4.2.36 For concrete sharing e n v i r o n m e n t s (K;i,, (CShr~c,. , CShr~c,.)) and (IC,,t,, (CShr~c..,. , CShr~c.... )), such that arity( ICin[c]) ~- n & arity( ICr,t,[e]) = m & ra < n, and an injection i,=n: { 1 , . . . , m } ~ { 1 , . . . , n } ,
Extend((/(:/.,, (CShr~c,. , CShr~,.)), (/Cr,t., (CShr~c.... , CShr~c.... )), icau) --
if then else
(3a over Vars(Proj(ICi,,ican)) : Proj(/C/,,i,=u)a z-/C,,,,) & Vars(/Cr, tr) fq Vars(/Cin) C_ Vars(Proj(/Cin,/cart)) & CShr~c .... : TermShift(/Cr,tr, Proj(TransitiveClosure(CShr~c.. U CShr~q.),/carl)) ()Co,,t,(CShr~c.,,,, CShr~c.,,,)), fail
where ~out -~ ]('in 0", f o r the substitution a over Vars(Proj(/C~,,i,=z,)) such that Proj(/C/n,ir ]~rstrl and
CShr~c.. ' AccNew
= =
New CShr~:.~,
= =
=_
TermShift(/Cout, CShr~c,.), TermShift(/Co,~t, CShr~c~,,), RevProj(CShr~c .... , ic=u),
TransitiveCIosure(AccNew U New).
Note that for a given pair/C/,~, /Cr, t~, there is at most one substitution a over
Vars(Proj(K;in, /call)) such that Proj(/C/n, ic=ll)a = /C~,t~. The conditions on the relationship between /Ci,~ and ICr,tr reflect properties of the computed answer substitution of an SLD-derivation [3, 50]. The domain and range of a substitution, accumulated in a subderivation, consist of the variables occurring either
C H A P T E R 4. SHARING A N A L Y S I S
100
I (CShr~:,..,
TermShift TermShift
/
' CShr~,.)
Call
~..,)
TransitiveCIosure L
~
.
Call I
Head
"N~Proj
(CShr~c.... , CShr~c..,.) q .
. Call/
.
..
Restrict2 I Call0
(CShr~c, CShr~c)
,,,J
Figure 4.9: Extend: Construction of the sharing component. in the subgoal that is executed, or in the renamed clauses used during the subderivation, but no other variables. The condition on the relationship between the sharing components of ]Ci,, and ]C,.,t, further characterizes the implementations for which our analysis is intended, namely, we address implementations in which sharing, once introduced between term representations, persists until final success or failure of the computation. Notes: 9 The specification of the Extend operation does not use the value of CShr~c.." to construct the output sharing environment (see Figure 4.9). However, the properties of CShr~:.... are crucial to the proof of Lemma 4.2.37. 9 The extend operation differs from the unification operation only in the definition of New. The following lemma states that the sharing derived by the extension operation comprises both the initial sharing and the sharing that resulted from the call. L e m m a 4.2.37
Let (/Ci,~,CSharingjc,,~) and (/Cr,t.,CSharingjc...)
be concrete
and i~n : { 1 , . . . , m } --~ { 1 , . . . , n } an injection. Let (ICo~t, CSharingjc.~,) = Extend((/Ci,~, CSharing~), (IC~,t~, CSharingjc.,.), i~n) (Extend does not fail). Then
I. CSharingjc.. C CSharingjc.~,. 2. RevProj(CSharingx::...,icon) C CSharingjc.~..
4.2. P R I M I T I V E
OPERATIONS
101
Proof. We first prove 1. From the properties of TermShift and the definition of Extend, we have
CShr~c,,` CShr~c.
C TermShift(/Co.t, CShr~c,,~) ---- CShr~co.,, C TermShift(/Co~,t, CShr~c ) : AccNew.
(4.38)
(4.39)
From (4.39), the properties of TransitiveClosure and the definition of Extend, we have
CShr~q.,
_C TransitiveCIosure(AccNew U New) = CShr~co.,.
(4.40)
From (4.38), (4.40) and the monotonicity of TransitiveCIosure, it follows that
Transit veCIosure(CShr~c,~ U CShr~c,,,) C TransitiveCIosure(CShr~co. ' U CShr~:o.,) or shortly, CSharing~c~ C CSharingjc=.~. To prove 2., consider (r, s) E RevProj(CSharingpc..... i~u). From the definition of RevProj, we know that (r, s) = ( i ~ u ( k ) . r ' , i ~ u ( l ) . s ' ) where 1 < k, l < m & (k..r', l.s') E CSharing~c_, . Let (k~.rx, l~.Sl),..., (kp.rp, lp.sp) be the finite sequence over CShr~:.... U CShr~c,, ~ that leads to the inclusion of (k.r',l.s') in CSharingpc_,. = TransitiveCIosure(CShr~ .... U CShr~c.,,) according to the definition of TransitiveCIosure. We have
(
& )
Cl < i < p ~ k i , l i E { 1 , . . . , m } ) Vi E / N : (1 <_ i < p :=~ li
---- ~ g i + l
& Si
:
Vi+I)
(4.41)
a (k.rl, l.s/) = (~l.rl, lp.sp). From the definition of Extend we know that the extension operation succeeds only if the input sharing components satisfy the relation
CShr~c.... - TermShift(/Crstr, Proj(TransitiveC osure(CShr~q~ U CShr~,.), i~=u)). Hence it follows from the definition of TermShift that for (/r
li.si) E CShr~c.,,.,
we have
3t~i
: . (k~.ri,I l~.s;) E Proj(Transit veClosure(CShr~c,. U CSh r n~c,~), ic=~z)
By the definition of Proj, (i~u(ki).r~,ic=u(li).s~) E TransitiveCIosure(CShr~c~ u C S h r ~ , , ) = CSharing/c~,,. Let (uz, v l ) , . . . , (uq, vq) be the f i n i t e sequence over o 9 r i,~ icazl(li).s;) in CSh r Jc~,, U CShr~,~ that leads to the inclusion of (lcall(ki).
CSharingpc,., (ux = ic=u(ki).r~, vq = i~=u(/i).s~). Because CSharing~c,., is a sharing component for/Ci,, we have that for all j satisfying 1 < j < q, ~,../~.i - ~,./,,,
-- ~,Ji~o,Ck,).r;
- ~,dic.,,Cz~).~;
(4.42)
C H A P T E R 4. SHARING ANALYSIS
102
Using Property 4.2.26.1 and the definition of Extend we have that for a substitution over Vars(Proj(:,.,it..)),
Proj(/Co,,=, ir
- Proj(/Ci,= a, it=u) = Proj(K;i., ir
a -/Cr,t~.
(4.43)
From (4.43) and Property 4.2.26.3, we have O(/Cr,tr) = O(Proj(/Co.=, it=u)) = Proj(O(/Co.,), it=u).
From ki.r;.ti, li.s;.ti E O(Kr,tr) and the definition of Proj, it then follows that ir i~,,u(li).s~.ti E O(/Co,,t), and because of (4.42) also uj.~i,vj.ti E O(/Coft) for all j satisfying 1 < j < q. Using the definition of TermShift, we obtain a finite sequence (ut.ti, vx.ti),. 9 (% .ti, %.ti) over TermShift(/go=t, CShr~c,~) UTermShift(/go,,t, CShr~,,,) such that ul.ti = ir &%.~ = ic=u(li).s~. From the definition of Extend, CShr~c.. ' -- TermShift(/Co,,t, CShr~c,.,) and AccNew = TermShift(/Co~,t, CShr~c,, ). Summarizing we have
(l~i.ri, li.si) E CShr~c..,. :::>
(4.44)
(ic=/l(ki).ri, ic=zl(h).si) E TransitiveCIosure(CShr~co~ , U AccNew). From New = RevProj(CShr~c..,. , ir
and the definition of RevProj, we have
(ki.ri, li.si) E CShr2.,,. :e. (i~=tz(ki).ri, i~=zz(li).si) E New.
(4.45)
Given the finite sequence (kx.rl, ll.Sl),..., (kp.rp, lp.sp) over CShr~c.... WCShr~c.,,. satisfying 4.41, it follows from (4.44) and (4.45) that (i,=u(k~).rx, i,=u(h).st), 9 9 (i~u(kp).rp, i~=u(lp).sp) is a finite sequence over TransitiveCIosure(CShr~c.~' U AccNew) W New, and s) =
E
TransitiveCIosure(TransitiveCIosure(CShr~co. ' U AccNew) U New).
W e observe that
TransitiveClosure(TransitiveCIosure(CShr~c~ ' U AccNew) U New) = TransitiveCIosure(CShr~co. ' U TransltiveCIosure(AccNew U New)), by the associativity of TransltiveCIosure, =
=
TransitiveCIosure(CShr~co. ' U CShr~=.,), by the definition of CShr~o.,, CSharing~co.,, by the definition of CSharingjco~.
Therefore, (r, s) = (ic~u(k).r', ic=u(/).s') E CSharingjc..t, as desired.
D
The proof that Definition 4.2.36 guarantees the concrete operation Extend to be well defined on the domain of concrete sharing environments is analogous to
4.2. P R I M I T I V E O P E R A T I O N S
103
the proof of the well-definedness of the Unify operations. First we demonstrate that New = RevProj(CShr~:..,., i~an) is a pre-sharing component for ICo,,t. Second, using the previous result, we prove that CShr~c.~,, AccNew, CShr~c.. ' and CSharingjc.., = TransitiveCIosure(CShr~c.~ ' U CShr~c.~,), are pre-sharing components for ICo,,t. Third, we demonstrate that CSharingK:..~ is a concrete sharing component for ICo,,t. Using the fact that it is a pre-sharing component, we still need to show that it satisfies Condition (4.1), concerning multiple occurrences of a free variable in/Co,,t. L e m m a 4.2.38 Let (/C~,,,CSharingjc,~) and (IC~,t~, CSharingpc.,,.) be concrete sharing environments such that arity(ICin[e]) = n & arity(ICrstr[e]) = m & rn < n, and i,~n : { 1 , . . . , m ) --, { 1 , . . . , n ) an injection. Let (ICo,,t, CSharingx:..,) = Extend((/Cin, CSharingjc,~), QCr,t,, CSharingjc.,,.), icatt) (Extend does not fail}. Then New = RevProj(CShr~c.,., its/I) is a pre-sharin# component for ICo,,t. Proof. From the definition of RevProj, it is clear that New is a set of unordered pairs of the form (r, s) where r, s E O(/C~ We must prove that New satisfies Conditions 4.1.4.1-4. Condition 4.1.4.1 is satisfied by New: Given (r, s) E New, we must prove that Ko~,t/r =- /Co,a/s. For (r, s) E New = RevProj(CShr~c ....,icazZ), it follows from the definition of RevProj that (r, s) = (ican(k).r', ican(1).s') for k,l e {1,...,m} & (k.r',l.s') e CShr~ ..... Because CShr~..,. is a pre-sharing component for/C~,t~, satisfying Condition 4.1.4.1, we have
/C~,t~lk.r' -~ lC,,t,/l.s'.
(4.46)
Using Property 4.2.26.1 and the definition of Extend we have Proj(/Co,~t, ic=zl) - Proj(/Ci,~cr, i~=n) _-__Proj(/Ci,,ic~l;) a --/C~,t~.
(4.47)
Using Property 4.2.26.2 and (4.47), we have
ICo~tlr -/Co~,/i<=.(k).r'
-
Oroj(Ko~t,i<=,,)/k.r' - / C . , . / k . r ' &
ICo,,tls = ICo,,tlic=ll(l).s' -~ Proj(ICo,,t, i,=,,)ll.s' =__IC,,t,ll.s'.
(4.48)
Summarizing (4.46) and (4.48), we have ICo,,t/r =- ICo,,t/s, as desired. C o n d i t i o n 4.1.4.2 is satisfied b y New: Given (r, s) G New, we must prove that r ~ s. By the assumptions of the lemma, CShr~c.,,. satisfes Condition 4.1.4.2. The function RevProj preserves the property, thus passing it on to New. C o n d i t i o n 4.1.4.3 is satisfied b y New: Given (r, s) E New, we have to prove that TermShift(/Co~t, {(r, s))) C New. Expanding the definition of TermShift, we must prove that for each t such that r.t,s.t E O(Ko,t), (r.t,s.t)E New. Fix arbitrary such r, s, t.
104
C H A P T E R 4. S H A R I N G A N A L Y S I S
As (r, s) E New = RevProj(CShr~c..,. , i:,n), it follows from the definition of RevProj that (r, s) = (i,,z~(k).r', i,=zt(/).s') for k, l E { 1 , . . . , m}&(k.r', l.s') E CShr~.,, . From r.t = i=:zl(k).r'.t, s.t = i,:ll(1).s'.t E O(/co,t), Property 4.2.26.3 and (4.43) above, we have k.r'.t, l.s'.t E Proj(O(/Co,,t), lea/l) = O(Proj(/Con,, icat,)) = O(IC,,t,).
Because CShr~..,. satisfies Condition 4.1.4.3 and (k.r',l.s') E CShr~ .... , we have that (k.r'.t,l.s'.t) E CShr~c.... . So, for (r,s) E New, we have (r.t, s.t) = (icazZ(k).r'.t, icat,(l).s'.t) E New = RevProj(CShr~..,., icau). C o n d i t i o n 4.1.4.4 is s a t i s f i e d b y New: Given (r, s), (s,t) E New where r t, we must prove that (r,t) E New. By the assumptions of the lemma, CShr~:... satisfies Condition 4.1.4.4. The function RevProj preserves the property, thus passing it on to New. []
L e m m a 4.2.39 Let (/cin, (CShr~c,,~, CShr~c,.~)) and (/cr,tr, (CShr~c..,., CShr~.o,.)) be concrete sharing environments such that arity(/cin[e]) = n & arity(K~,t~[e]) = rn&ra < n, and i , , n : { 1 , . . . , m]. --* { 1 , . . . , n} an injection. Let (ICo,,t, (CShr~c.. ,, CShr~c..,) ) = Extend((/Cin, (CShr~c,,,,CShr~c,~)) , (/c,,t,,(CShr~c..,., CShr~c..,.)), icalz) (Extend does not fail). Then CShr~c..,, AccNew, CShr~c.~ ' and CSharlng~c.., = XransitiveCIosure(CShr~c.. ' UCShr~..,), are pre-sharing components for/co~t.
Proof. The proof of this lemma is identical to the proof of L e m m a 4.2.4, except that L e m m a 4.2.38 is used instead of L e m m a 4.2.3. [] The following l e m m a states that the operation Extend is well-defined on the set of concrete sharing environments.
L e m m a 4.2.40 Let (/cin, CSharingjc~) and (/C,,t~, CSharingjc.,.) be concrete sharing environments s ch that arit (/cin[d) = = ,_< n , and i ~ , n : { 1 , . . . , m } --* { 1 , . . . , n ) an injection. Let (/co~t, CSharingx:..,) = Extend((/cin, CSharinglc,,.),(/C,,t,, CSharingjc.,.), icatt) (Extend does not fail). Then (/coat, CSharing~c..,) is a concrete sharing environment. Proof. By the definition of Extend, we have ICo,,t - Kina for a a substitution over Vars(Proj(/ci,,,icazz)) such that Proj(/Cin,icau) a - /Cr,tr. Because (/cin, CSharing~c~.) is a concrete sharing environment, it follows from the definition of substitution application that the term/Co~t has principal functor () and that O(Kin) C_ O(Ko,,t). From L e m m a 4.2.39 we know that CShr~co.,, CShr~:o.,
and CSharing,:~ = TransitiveCIosure(CShr~c.. ' U CShr~c..,) are pre-sharing components for /co~t. So, we still have to prove that CSharing~.., satisfies Condition (4.1), i.e.,
C/co.dr --/co.,Is)
~
(/co.dr
~ Vars) & (r ~- s) =~ (r, s) E CSharingjc~
4.2. P R I M I T I V E O P E R A T I O N S
105
O~t
ICratr
/ /
/ 9/
m
_L.,I'\ I
\
Y
x
Y
Figure 4.10: ICo~,t = ICi,~a and q = q'.q" E O(ICo~t) Let Ko~t/r - ICo~t/s =- Y . Since Eo~t =- K i , a for a substitution ~r over Vars(Proj(/C~,,,i~u)) such that Proj(/C,,,,i,~u)a - /C~0t~, we know that each q E O(K:o,,t) such that ICo,,t/q =- Y must satisfy one of the following. (q E O(ICi,~)
&
ICin/q :- ICo,~t/q -- Y )
(4.49)
or
3q', q" :
( q = ql.q,, & q, E O(ICi,~) & lCi,,/q' - X, E Vars(Proj(/Cin, i~,m)) '~ (4.50)
\
& Xq cr =_ t.q & ql, E O(tq) & ~out//q ~-- tq/q" = Y
/
Note that X, E Vars(Proj(K:in, icau))
g 3k,.q* G O(Proj(/Cin, ir
k, E ( 1 , . . . , m} & IC.i,~/ic~u(kq).q* =- X , .
Because CSharingjc,.. = TransitiveClosure(CShr~c,~ U CShr~c,~ ) satisfies Condition (4.1), we have in case (4.50) that either q' = ir or (q',ic~u(kq).q ~ E CSharingx:,~ = TransitiveCIosure(CShr~c,. U CShr~c,,,) (see Figure 4.10). So, using Condition 4.1.4.3 proved above, and CSharinglc.,. C_ CSharingx:.., (see L e m m a 4.2.37), we have either q = i,au(kq).q*.q" or (q, ic~u(kq).q*.q") E CSharingjc..,. So, a more convenient consequence of (4.50) is
3k,,q*,q': ( (q&=icall(k')'q*'q'v(q'icall(k' - Y)'q*'q' )lC,,tr/k,.q,.q. E CSharingx:.., ) (4.51) We shall now show that (r, s) E CSharingx:.., by a case analysis based on which of (4.49) or (4.51) holds for r and s. Suppose r and s both satisfy (4.49), i.e., r , s E O(K:in) and ICi,,/r ==- Y =_ Kin/s. Because CSharingjc,~ satisfies Condition (4.1) and r ~- s, we have (r,s) E CSharing~c,~ C_ CSharing~c..,, as desired.
106
C H A P T E R 4. S H A R I N G A N A L Y S I S
Suppose r and s both satisfy (4.51), i.e., 3k,, r*, r", k,, s*, s": (r = iea/l(k,).r*.r"
V
(s
V
(r, icall(kr ).r* .r") E CSharingjco.,) & ' " ' k ,). ' s * 9s " ~) E CSharingx:..,) & ks, z~n~,
Y
-
=
i,an(k,).s*.s"
(/c,.,,,./k,.r
9
.r
II
-
~,.,,,/~,,.s
*
.s
It
).
Because CSharingjc.,,. = TransitiveClosure(CShr~c., * U CShr~c.,,. ) satisfies Condition (4.1), we have kr.r* .r" = k,.s* .s" V (kr .r*.r", k,.s* .s") E CSharingR:.., . It follows from RevProj(CSharing~c..,., i,~n) C_ CSharing~:.~, (Lemma 4.2.37), r s, and Condition 4.1.4.4, proved above, that (r, s) E CSharingjc..,. Otherwise, one of r and s satisfies (4.49) (assume it is r, without loss of generality) and the other (thus s) satisfies (4.51). From r satisfying (4.49) we know /Ci,~/r -- Y E Vars(/C,,~), and from s satisfying (4.51) we know )C,.,t~/k,.s*.s" =
r e Wrs(~:.,,). Because W r s ( ~ : . , , ) n W r s ( ~ )
C_ Wrs(Proi(~:~, i . , ) ) , we have
3 k,, r ' : k, E { 1 , . . . , m} &/Ci,~/i,,n(k,).r' - IC,ot~/k,.r' - Y . From CSharing~c,~ and CSharingx: .... satisfying Condition (4.1) and s satisfying (4.51), we know 3kr, r t, ks, s*, 8" : (r = i,an(kr).r' (k~ .r I = k, .s* .s"
(s = i,~,(k,).s*.s"
(r, ic~n(kr).r') E CSharingx:,,.) & V (k~.r I, k , . s * . s ' ) E CSharingjc.,,.) &
V
V (s,i,~n(k,).s*.s")E CSharing~c..,).
It follows from r :fi s, CSharingJc,,. C CSharlngjc.~,, RevProj(CSharlngx:..,., lean) C CSharingjc.., (Lemma 4.2.37), and Condition 4.1.4.4, proved above, that (r, s) E CSharingx:.~,.
D
D e f i n i t i o n 4.2.41 For abstract term environments 7 TM and "T~,t~e, such that arity(7-[e]) = n & arity(T~,t,[c]) = m & m < n, an injection i : {1,..., ra} {1,..., n}, and PT".,,. a set of unordered pairs (p, q) such that p, q E Nodescr.o,., we define
AbstrRevProj(T~,t,, Pr..,., T, i) = k, l e { 1 , . . . , m } &
(7-[(i(k),f).s], 7-[
f).S], Tr,t,[(l, g).R]) e PT"..,.
D e f i n i t i o n 4.2.42 For abstract sharing environments (7-i,~',(AShr~r~,,,AShr~- )) and ( .,~ (AShr~-.,., AShr~..,.)), such that a~/t~(Z,~[~]) = ,~ ~ arit~(7;,,.[~]) = m ~ r . < n, and an injection i . . : { l , . . . , m } --, {1,...,n}, AbstrExtend((~,, ~,(Ashr~- , A S h r ~ - ) ) , ( ~ , t , ' , (AShr~-, AShr~-)),i~,,) if TGExtend(Ti,~~,T~,t~~,icon) is fail then f a i l
else (]-o,,,e,(AShr~-:,.,,AShr~.,,)),
107
4.2. P R I M I T I V E O P E R A T I O N S whePe
T.o,~t" = TGExtend (~,,~, ~,t~ ', i~.u), and
AShr~ro., = C =
Convert(~,~,AShr~-,To~t), TGShift(2-o~t,Convert(~,~, AShr~-,]-o,,t)),
B
AbstrRevProj(Trstr, TGShift(Tr,t~, AShr~..,.),
=
To,,t, icau),
TGShift(To,,t, AShr~-o.,) = AlternatingClosure(C, B). Theorem 4.2.44, below, gives the correctness condition for the AbstrExtend operation. It assumes the following condition. C o n d i t i o n 4.2.43
1. ( i,~,
is an abstract sharing
en~ironmen/.
~. (Jq., ) i, a concrete sharing environment
3.
(to...,
(CSh4c. . . . , CSh,~
....
))
is a
eo,~erete sha~i,~g environment
6. ()C,st,, (CShr~c.... , CShr~c.... )) E InstrEnvConc((T~,t~', (AShr~r..,., AShr~- ))) 7. ~ r i t ~ ( T , . [ d )
= ,~ ~
arit~(7-..[d)
= m
s~ ,-~ < ,~
8. icatt: { 1 , . . . , m } ~ { 1 , . . . , n } is an injection O. (ICo,t, (CShr~:o.,, CShr~o.,)) ----Extend(QCi,~,(CShr~;., CShr~,.)), (/C,.,tr ,
(CSh,~.... , CSh,~..,.)), i~o.), is ~ot Mt
AShr~-o,.)) = AbstrExtend((Ti. t, (AShr~-, AShr~.~)), (7:,,,tr ' , (AShr~r,., AShr.~.,.)), ic~u)
10. (To,~te, (AShr~ro.,,
Theorem 4.2.44 (Safety of AbstrExtend) Assuming Condition J.~.J3, it ]ollows that
(/Co~t, (CShr~co.,, CShr~co.,)) E InstrEnvConc((7o,,t', (AShr~-o.,, AShr~-o.,))), that is,
P a r t 1. /Co,,t E TGEnvConc(To,,t'), P a r t 2. CShr~co~,,CShr~o~, are pre-sharing components for Ko,,t and TransitiveCIosure(CShr~co. ' U CShr~c~ is a concrete sharing component for ~ O~g s
Part 3. Ab~Pai%.dCo./CShr~:o.,) C_TGShift(=ro~.AShr~o.,) and
108
CHAPTER
4. S H A R I N G A N A L Y S I S
Part I. is a restatement of Theorem 2.3.14. Part 2. follows from the fact that Extend is well-defined ( L e m m a 4.2.40). The proof of Part 3. will follow at the end of the section, after we have related the auxiliary function AbstrRevProj used in the definition of AbstrExtend to its counterpart RevProj in the definition of Extend.
L e m m a 4.2.45 ( S a f e t y o f AbstrRevProj) Let 7" e and r,tr be abstract t e r m environments, such that arity(T[e]) : n & arity(T,,t~[e]) = m & rn <_ n, and/C 9 TGEnvConc(T') & /Cr,t, E rGEnvConc(~,t,'). Det ican: { 1 , . . . , m) --> { 1 , . . . , n } be an injection such that Proj(/C, ican) --/Cr,t,. Let P~c.... be a set of =nordered pairs of the form (r,s) where r,s 9 O(/C,,t,). Then
AI0sPairT,/r (RevProj(P~:..,., i,=u)) C_ AbstrRevProj(7-,,t,,AbsPairT-r,t,,/C,,tr(P~:..,.),7",ieau). Proof.
After expanding the definitions of AbsPairT,/C and RevProj, we have
AbsPairT,/c(RevProj(Ppc.,., i<=,)) = {(7-[sel(#c, i<=.(k).s)], 7-[Sei(SC, i<~ I k, I 9 {1,..., m} & (k.s,l.r) e Px:..,.}. For arbitrary k, s, 1, r such that k, l 9 {1,..., m} and (k.s, l.r) E P~:..,., we must prove that
(m[sel(~, i<.,~k).s)], "T[Sei (K:, ir 9 AbstrRevProj(T,,tr, AbsPairm,,vjC,,t,(P~; .... ),7-,lean). By the definition of AbstrRevProj, it is sufficient to prove that
Sel(K;,ic=n(k).s),Sel(E,i<~/i(1).r) Sel(/Cr, t,, k.s),Sel(K:r, tr, l.r)
9 S(T), 9 S(7-,,/r),
(~,t,[Sel(~r,t,, k.s)], ~,tr[Sel(~r,t,, l.r)]) E Abs Patr7- t~.,/C,.,tr(Pjc_,.),
(4.52)
(4.53)
(4.54)
such that if SelUC, i<=,(k).,) -- (i<=,(k), f).S, then SeI(IC,,,,, k.,) = (k, .f).S, and si~il~ly if SelUC, i,,zK0.r) -- (i,,(0, g).R, then SelgC,,., Z.r) -- (t, g).R. From the definition of AbsPairT,,t~,lC,,tr and (k.s, l.r) E P~.,,., we derive (4.54).
Because /C,,t, e TGEnvConc(T~,t,,), we get (4.53) from k.s, l.r E O(/C,,t,) and Property 4.1.13. From Proj(/C, icau) -- /C~0t~ and k . s , l . r 9 O(IC~,t~), it is clear that ic=n(k).s,
icaii(l).r 9 O(K;), #Cli<=n(k) =. lC,,,~lk and x;/i<=.(l) = g,,,Jl.
Now, suppose
that SeI(/C,i,=n(k).s) = (i=,n(k), f ) . S . I f s = e, then so is S, and f is V, a functor or a constant, depending on the value of/C/i~=n(k). But then Sel(K:~,t~,k) =
4.2. PRIMITIVE OPERATIONS
109
(k, f), because IC/ic~n(k) ~ IC~,t~/k. If s :~ e, then f = /C[icat,(k)] = 1C~ot~[k] and S = Sel(/C/ir = Sel(/C~,t~//c,s), by the definition of Sel. Hence Sel(~:~,,~, k.s) = (k, f ) . s . Because/C E TGEnvConc(T'), we derive (4.52) from Property 4.1.13 and i,~,,(k).s,
ic~,l(1).r E O( IC).
t:]
T h e o r e m 4.2.44, P a r t 3: Assuming Condition 4.2.43, it follows that 1. AbsPairor g O~tl~O~t
( C S h r ~o ~cl ) C TGShift(To~,t, AShr~-.~,) --
2. AbsPairTo~,t, iCo,,t(CShr'~c..,) C_ TGShift(Tout, AShr~-..,) Proof. Applying Conditions 4.2.43.9 and 4.2.43.10 and expanding the definitions of Extend and AbstrExtend, we can rewrite 1. as
AbsPairTout, iCout(TermShift( ICou,, CShr~:,,,)) C_ TGShift(Tout, Convert(Ti,,, AShr~-i~ , To,,,)).
(4.55)
We observe that
AbsPairTout,ICo,,t(TermShift( ICo,,,, CShr~:i~)) C_ T GShift( To,,t, AbsPairTout, ICout( CShr~i..) ), by Lemma 4.2.12 (the safety of TGShift), C_ TGShift(Tout, Convert(Ti,~, AbsPairTin,K;i,,(CShr~c,~), To,,t)), by Lemma 4.2.13 (the safety of Convert), and the monotonicity of TGShift, C TGShift(Tout, Convert(Ti,~, TGSh ft(Ti,~, AShr~%.), Tout)), by Condition 4.2.43.3, and the monotonicity of TGShift and Convert, o ~ , Tout)), = TGShift(Tout, Convert(Ti,~, A ShrTi by the definition of Convert, and the idempotence of TGShift. Summarizing, we get (4.55), as desired. Applying Conditions 4.2.43.9 and 4.2.43.10 and expanding the definitions of Extend and AbstrExtend, we can rewrite 2. as
AbsPair~,
/C (TransitiveClosure(AccNew U New)) C AlternatingCIosure(C, B).
ou~, out
First we relate AccNew and C.
AbsPairTout,ICout ( AccNew ) = AbsPairTo,,t,iCout(TermShift(ICout, CShr~i~)), by the definition of AccNew, e- (CShr~: i n ~, o,a, t l r ~ . o u t ~ iz by Lemma 4.2.12 (the safety of TGShift),
C TGShift(Tout, AbsPairT. --
C_XGShift (Touh Convert(~in, AbsPa r~,,,/C,n(CSh r~,~.
), To,t)),
(4.56)
110
C H A P T E R 4. S H A R I N G A N A L Y S I S
by Lemma 4.2.13 (the safety of Convert), and the monotonicity of TGShift,
C_TGShift(To,`t, Convert(T~., TGShift(T~,~, AShr~-), To,`t)), by Condition 4.2.43.3, and the monotonicity of TGShift and Convert,
= TGShift(To,`t, Convert(~,,, AShr~-,.., ~o,`t)), by the definition of Convert, and the idempotence of TGShift, ---- C,
by the definition of C. Summarizing, we have (4.57)
AbsPairTo,`t,/Co,`t(AccNew ) C_ C. On the other hand,
AbsPalrTo,`t,/C o,`t(New) = AbsPair.-r ~- (RevProj(CShr~: i~Iz)), by the definition of New, C AbstrRevProj(T~,,,, AbsPairT,,,,jC,,t,(CShr~..,.), To,,,, icazz), by Lemma 4.2.45 (the safety of AbstrRevProj),
C AbstrRevProj(T,,,,, TGShift(T,0t,, AShr~-.,.), To,`,, it,u), by Condition 4.2.43.6, and the monotonicity of AbstrRevProj, -- B, by definition of B. Summarizing, we have
AbsPairTo,`t,iCo,`t(New ) C B.
(4.58)
Finally, we prove (4.56).
AbsPair.]'o~,,.iCo,,(TransitiveCIosure(AccNew U New)) C AlternatingCl~
/C
(AccNew), AbsPair~
/C (New)),
by Lemma 4.1.21 (the safety of AlternatingClosure),
C_AlternatingClosure(C, B), by (4.57), (4.58), and the monotonicity of AlternatingCIosure. []
4.3
Evaluation
In this section, we show some of the results obtained by a prototype implementation based on the specifications for primitive operations given in Section 4.2.
4.3. E V A L U A T I O N
111
Because most Prolog implementations do not create any sharing when a variable gets bound to an atom or an integer, a small optimization with respect to the formal specifications is incorporated, avoiding the introduction of such sharing edges. Detailed tables representing the results produced by the prototype are given in Appendix A. So here we discuss only one example in detail, after which we comment on a possible refinement of the sharing analysis, based on a new concept that characterizes meaningful sharing edges for a given type graph (cfr. Section 4.1.2). Next, based on our experience with the prototype implementation, we discuss the strength of the sharing analysis (e.g. the gains that can be expected from using the alternating closure) and we point to a few shortcomings (e.g. the remaining causes of imprecision and the main sources of inefficiency).
4.3.1
Example:
insert/3
Consider Program 4.1 (page 59) for inserting an element into a sorted binary tree. Assume that the program is called with the abstract sharing environment ~i,~ = (T~,~e, ASharingT-,,), where Tim Tree ASharing~r~
::= ::= =
(Int, Tree, V), empty [ t(Tree, Int, Tree), (@, @).
For the analysis, we use depth bound two for the tree nodes labeled 1. The success substitution ~o,,t = (To,,t', ASharingT-o~,), computed by the abstract interpretation procedure, is given by
~out TreeOne ASharingTo.,
AShr~-o.,
::= ::= =
(Int,Tree, TreeOne), t(Tree,Int, Tree), (0, AShr~..,),
=
{
t).(3, t)], ( To,,t[(3,t).(1,t)],
I)1), t)]) ).
Only the principal sharing edges are represented. The full set of sharing edges is obtained by applying the TGShift operation. Figure 4.11 represents both ~i,~ and flo~,t. We see that the output argument Air gets bound to a tree containing at least one element and that after the call there is potential sharing between the input and output trees. This corresponds exactly to what one intuitively expects from the source code of the program. Note that the sharing relation is not transitive and that no internal sharing is derived for the output tree. The top tree cell is not shared either. We obtain such a precise result thanks to using depth bound two for the t labeled nodes. If depth bound one were used instead, the output type graph would have been folded and the top tree cell would be indistinguishable from the other tree cells. There is no sharing between the integer _E and the integer elements of the output tree Air because of the optimization mentioned above. Next, let us consider one of the recursive clauses.
C H A P T E R 4. S H A R I N G A N A L Y S I S
112 Tin
<>
Int
Or
Tout V
<> Or
lnt
Int
t
Int "~empty ". t Int
empty ,, t ..,"
Int
Figure 4.11: Abstract sharing environments ~in and flout for the predicate insert/3.
inserg(_E, _0T, _lIT) :_OT = t(_L, _F, _..K),
Y. =< _F, _}IT = t ( J L , insert(_E, _L, _NL).
2(~ _F, _It),
|
In Section 4.1.2, we already discussed the binding states derived for the program points ( ~ and (~) . Figure 4.5 (page 59) represents the restricted sharing environments before and after the selection operation _0T = t (_L, _F, _R). Here, we restrict ourselves to a discussion of the results obtained for the program point ~ , just before the recursive call. If we order the variables of the clause according to the tuple (_E, _0T, _Jr, i l L , _L, _F, _~), then the abstract sharing environment ;3 = (T *, ASharingsr) computed by the abstract interpretation procedure, is given by T
ASharinglAShr,~-
::=
(Int, TreeOne, t(V,Int,Tree), V, Tree, Int, Tree), (@, AShr~-), { ( T[(3,~).(3, g)], 7"[(2, t).(3, t)] )'
( T[], ~r[<~, t>] ), ( ~-[(3, t).] ), ( T[(2,t).(a,t)], 7[<~, t>] ), ( T[(2, t).(1,/)], 7[<5,~)]) }.
It is represented in Figure 4.12. The output argument .~T is only partially constructed. The left subtree is a free variable that has yet to be filled in. It will receive its value through the sharing with the program variable ilL, which is the third argument of the recursive call. After restricting the environment to the variables of the recursive call, we obtain an abstract substitution that is equivalent to ;3in. Starting from the substitution ~in, all program points have an empty first sharing component. Therefore, assume as a second example that the program is
4.3. EVALUATION
Int
113
t
t
V
Or
Int
//',,",, Or
Int
Or
V "
Int
Or
empty I
t
Or
/;',,",, ~
crop,y[
I
ernpty I
t
~ empty[
Int
".~
t ..~ Int
empty[ ~ t .'~
.".,. . . . % 4 4Int- - - ; z
t Int 9
t @9
,."1
Int
. . . . . . .-;,. ,,"
Figure 4.12: Abstract sharing environment/3 for point
(~) in the predicate
insert/3. called with the abstract sharing environment T1 DTree ASharingT-1 AShr~- 1
::= ::= = =
~1 :
(T~,
ASharingT-~) where
(d(Int), DTree, V), empty ] t(DTree, d(Int), DTree), ], Tl[<2,t>.<2, d)]) ).
The substitution /31 represents that the element to be inserted, a structured term of type d(Int), possibly shares with (at most) one element already present in the input tree (at most one, because there is no internal sharing in the input tree of the second argument). The success substitution f12 = (T~, ASharingr2>, computed by the abstract interpretation procedure, is given by 7- 2
DTreeOne ASharing-T2 AShr~- 2 AShr~- 2
::= ::= =
(d(Int), DTree, DTreeOne), t(DTree, d(Int), DTree), (AShr~r 2, AShr~-2),
=
{ (~r~[], ~2[<2,t>.<2,a>]) },
T: [<2,t>]), { (T2[<3,t>.(3,t)], T~ [<2,t>]), ( T2 [<3,/).(1, t)], ( 7-2[<3, t>.<2,d>], T~[<2,t).<2,d>]), ( T2 [(3,/>.(3, t).(2, d)], T~[]), ( T2 [(3,/).(1, t).(2, d)], T2 [<1, d>] ), ( 7-2 [<3, t).<2, d)], 7-~[(i,d>])}.
Remember that the full sharing relation is obtained by computing the alternating closure of AShr~- 2 and AShr~2. This set contains edges between all the nodes of the subtree for the third argument that are labeled d (there are six implicit sharing edges of which three are self-edges). So, it is correctly derived that there may be internal sharing in the output tree for the third argument. For example,
CHAPTER 4. SHARING ANALYSIS
114
the following edges are implicit in the representation above.
( T2[(3,t).(3,t).(2, d)], 7"2[(3, t).(2,d)])
(
t).(1, t>.(2, d)],
(T2[(3,t).(2,d)],
t).(2, d)]) 7-2[(3, t).(2, d)] )
Note that the latter edge is in fact an irrelevant edge, as will be defined in the next section.
4.3.2
Relevance of Sharing Edges
For an abstract sharing environment of the form (T t, (AShr~-, AShr~-)>, the full abstract sharing component is given by AlternatingClosure(TGShift(T, AShr~-), TGShift(T, AShr~-)). The meaning (and relevance) of the elements of ASharing~r is determined by the concretization function InstrEnvConc and depends on the eornezt, namely the type graph T. The conditions on an element (m, n) E ASharing~-, imposed by the definition of an abstract sharing component (Definition 4.1.14), namely m, n E NodesT and Label(m) = Label(n) and Label(n) :/: 'Or', do not guarantee that the element is meartingful or relevartt in its context T (meaningful in the sense that it corresponds to sharing in some of the concrete terms represented). Intuitively, we can distinguish several kinds of irrelevant edges (for an illustration, see Figure 4.13): (a) Edges that carry no (interesting) information. In the corresponding concrete terms, they only express sharing of a subterm with itself. (b) Edges between disjoi~tt nodes. There are no corresponding concrete terms in which such sharing can exist, because only identical concrete terms can share. (c) Edges between mutual ezclusive nodes. There are no corresponding concrete terms in which such sharing can exist, because only one of the alternatives allowed by an Or-node of a type graph occurs in a concrete term. (a)
<>
I
a
<>
(b)
a
(c)
I
b
<>
(d)
!',.-_r C
Figure 4.13: Irrelevant sharing arcs - - a concrete term exhibiting such sharing cannot be constructed. The semantics of the type graph, and hence of the sharing edges, is formally defined by a concretization function that may be adjusted to a particular application. Figure 4.13.d shows a self-edge that is irrelevartt if the concretization
4.3. EVALUATION
Tin
115 AbsUUnify(A,[B]) ~ Tout
<>
Or
<>
Or
Or
nil r l""
V
8.,~ ml r
:.) ~
""
6
Figure 4.14: Ignoring the creation of irrelevant edges causes imprecision. function generates non-circular terms only (which is the case in our application). This is appropriate if the abstract domain is intended to be used for the analysis of systems that perform the occur check. If the intended application is e.g. loop detection for systems not performing the occur check, the concretization function has to consider circular terms too, and the self-edge in Figure 4.13.d will be relevant, indicating possibly circular terms. We will come back to this in Section 4.3.3 below. The definition of an abstract sharing component and the specifications of the abstract primitive operations ignore the possible presence or creation of irrelevant edges. An irrelevant edge does not affect the outcome of the concretization function for some abstract sharing environment, because it does not correspond to sharing that is possible in any of the concrete terms represented by the underlying abstract term environment. Removing the irrelevant edges from an abstract sharing environment 131, leads to an equivalent abstract sharing environment/32, in the sense that InstrEnvConc(/31) = InstrEnvConc(/32). Note that the order relation, defined in Section 4.1.4, does not recognize such equivalent sharing environments either. We have
/32 ~Sh /31 ~ /31 ~Sh /32" Consequently, a few more iteration steps may be needed in the abstract interpretation procedure before a fixpoint is recognized. Moreover, Figure 4.14 illustrates that, if irrelevant edges are not detected by the operations that create them, they can cause imprecision in subsequent steps of the abstract interpretation, because shifting down irrelevant edges can result in relevant ones. Suppose we have two program variables, A and _B, that are both bound to lists of free variables, and that the elements of list _~ possibly share with one another (type graph T~,, of Figure 4.14). The abstract interpretation of the unification A = [_B], consists of the following steps (in the figure, initial sharing edges are drawn as short dashed lines, accumulated new edges as long dashed lines, and edges in the alternating closure as dotted lines), 9 Expressing the sharing component for type graph Ti,~ (input sharing edge (1)) in terms of type graph To~t by the operation Convert (which leads
116
CHAPTER
4. S H A R I N G A N A L Y S I S
to edges (2, 8)), immediately followed by an application of the TGShift operation w.r.t. To,,t (which gives edge (5)). Constructing the edges that correspond to places where unification possibly introduces new sharing by the operation BindingEdges (which leads to edges (3, 9)), also immediately followed by an application of the TGShift operation w.r.t. To,,t (which gives edge (6)). Computing the alternating closure of the sets obtained in the previous two steps (which gives the edges (4, 7, 10) by the paths (3-2-3), (6-5-6) and (9-8-9) respectively). However, because the program variable _a is known to be a single element list after a successful unification, the input sharing edge (1) is in fact converted into the irrelevant edges (2) and (8) (corresponding to class (c) and (d) in Figure 4.13, respectively). If these edges are not removed at this intermediary stage, the subsequent TGShift operation introduces the relevant edge (5). So, if irrelevant edges are not removed by the Convert operation itself, but only after abstract interpretation of the unification has been fully completed, then we can only remove the edges (2, 4, 8, 10) as irrelevant edges (assuming that the concretization function excludes circular terms), and we end up with an imprecise output sharing component, containing the redundant edges (5) and (7). In general, when as a result of unification, some 'Or'-alternatives or back-arcs of some input type graph T/,, do not have corresponding arcs in the output type graph To,,t, the converted version of relevant sharing edges for T/,, may be irrelevant for To,,t. Relevant edges can be formally defined as follows. Definition 4.3.1 For an abstract t e r m environment T t and rn, n E Nodes:r such that Label(m) = Label(n) and Label(m) # 'Or', define
Relevant(T', (m, n))
i.ff
I
=I/C E TGEnvConc(Tt), CSharing/c a sharing component for IC, (r, s) E CSharinglc : rrt = T[SeI(/C, r)] & n = T[SeI(/C, s)].
Using this concept, we can now give a more restrictive definition for abstract sharing components. Definition 4.3.2 For ar~ abstract t e r m environment 7- t containing the type graph T = (NodesT, ForwardArcs~r, BackArcs~r), we call a set of unordered pairs (re, n) such that ra, n E Nodes~-, Label(m) = Label(n), Label(m) • 'Or', and Relevant(T ~, (m, n)), an abstract sharing component for T . The specifications of the primitive operations (Section 4.2) must be adapted accordingly, and the safety proofs will become more involved. The algorithms for detecting the irrelevant edges shown in Figure 4.13 are rather straightforward. Edges as in Figure 4.13.b can be characterized as edges that cannot be shifted down all the way to leaf nodes in at least one way (or
4.3. EVALUATION
117
as edges connecting two nodes representing type graphs with an empty intersection [38, 39]). The irrelevant edges shown in Figure 4.13.a, 4.13.c and 4.13.d, in fact connect the roots of two subgraphs that represent sets of terms of which no elements can occur as (distinct and independent) subterms within a term represented by the enclosing type graph. These edges can be characterized as the edges connecting two nodes nl and n~ for which the sets of ancestor nodes w.r.t, the type graph do not have a functor node n! in common, such that nl and n2 are descendants of (or equal to) two differer~t children of n I. We expect that, based on properties of normalized type graphs (such as the absence of empty nodes), and the precise definition of the concretization function of type graphs, it can be proved that these are the only possible irrelevant edges, and that the characterization given above is correct. Further investigations should address the following issues: 1. Which operations possibly create irrelevant edges. (For example, the irrelevant edge in the success substitution ~2 for the example in the previous section is created by the AlternatingClosure operation.) 2. What is the impact of detecting irrelevant edges at intermediary stages on the precision and the efficiency of the analysis. 4.3.3
Imprecision
in the Sharing
Analysis
Although for the test programs that we used (see Appendix A) the analysis