specialisation of prolog and fcp programs using abstract interpretation

28
New Generation Computing, 6 (1988) 159-186 OHMSHA, LTD. and Springer-Verlag OHMSHA, LTD. 1988 Specialisation of Prolog and FCP Programs Using Abstract Interpretation John GALLAGHER, Michael CODISH and Ehud SHAPIRO Department of Computer Science, Weizmann Institute of Science, 76100 Rehovot, Israel. Received 2 May 1988 Abstract This paper presents an approach to specialising logic programs which is based on abstract interpretation. Program specialisation involves two stages, the construction of an abstract computation tree and a program construction stage. For the tree construction stage, abstract OLDT resolution is defined and used to construct a complete and finite tree corresponding to a given logic program and a goal. In th e program construc- tion stage, a specialised program is extracted from this tree. We focus on two logic programming languages : sequential Prolog and Flat Concurrent Prolog. Although the procedural reading of concurrent logic programs is very different from that of sequential programs, the techniques presented provide a uniform approach to the specialisation of both languages. We present the results for Prolog rigorously, and extend them less formally to Flat Concurrent Prolog. There are two main advantages of basing program specialisation on abstract interpretation. Firstly, termination can be ensured by using abstract interpretations over finite domains, while performing a complete flow analysis of the program. Secondly, correctness of the specialised program is closely related to well-defined consistency conditions on the concrete and abstract interpretations. Keywords: Abstract Interpretation, Program Specialisation, Logic Programming, Concurrent Logic Programming, Prolog, Flat Concurrent Prolog, OLDT Resolution. w Introduction This section introduces briefly the concept of abstract interpretation, the connection between abstract interpretation and program specialisation and the differences between specialising sequential and concurrent programs. The

Upload: john-gallagher

Post on 25-Aug-2016

213 views

Category:

Documents


0 download

TRANSCRIPT

New Generation Computing, 6 (1988) 159-186 OHMSHA, LTD. and Springer-Verlag

�9 OHMSHA, LTD. 1988

Specialisation of Prolog and FCP Programs Using Abstract Interpretation

John G A L L A G H E R , Michael C O D I S H and Ehud S H A P I R O Department of Computer Science, Weizmann Institute of Science, 76100 Rehovot, Israel.

Received 2 May 1988

A b s t r a c t This paper presents an approach to specialising logic programs which is based on abstract interpretation. Program specialisation involves two stages, the construction of an abstract computation tree and a program construction stage. For the tree construction stage, abstract OLDT resolution is defined and used to construct a complete and finite tree corresponding to a given logic program and a goal. In th e program construc- tion stage, a specialised program is extracted from this tree.

We focus on two logic programming languages : sequential Prolog and Flat Concurrent Prolog. Although the procedural reading of concurrent logic programs is very different from that of sequential programs, the techniques presented provide a uniform approach to the specialisation of both languages. We present the results for Prolog rigorously, and extend them less formally to Flat Concurrent Prolog.

There are two main advantages of basing program specialisation on abstract interpretation. Firstly, termination can be ensured by using abstract interpretations over finite domains, while performing a complete flow analysis of the program. Secondly, correctness of the specialised program is closely related to well-defined consistency conditions on the concrete and abstract interpretations.

Keywords: Abstract Interpretation, Program Specialisation, Logic Programming, Concurrent Logic Programming, Prolog, Flat Concurrent Prolog, OLDT Resolution.

w Introduction This section in t roduces briefly the concep t o f abstract interpretat ion, the

connec t ion between abstract interpretat ion and program specialisation and the

differences between specialising sequential and concurrent programs. The

160 J. Gallagher, M. Codish and E. Shapiro

remainder of the paper is presented as follows : In Section 2 we define the notion of an abstract computat ion tree for a Prolog program, and the relation between abstract and concrete trees. We then show in Section 3 how a specialised program can be derived from the abstract tree. Section 4 gives a brief introduc- t ion to Flat Concurrent Prolog and outlines an abstract computation tree and a specialisation method for Flat Concurrent Prolog. Section 5 concludes with related work and some final remarks.

1 . 1 Abstract Interpretation Abstract interpretation is a general and formal framework for the static

analysis of programs and in particular for logic programs. It provides a uniform method for collecting information about specific aspects of a program's behav- iour. This information provides the basis for a wide range of program analysis techniques such as global data flow analysis in optimising compilers, program testing, symbolic evaluation of programs, partial evaluation and others. The first formal definition of abstract interpretation was given by Cousot and Cousot 1) for imperative languages. In 1981 Mycroft z) pioneered the application of these techniques to the analysis of functional languages. Recently the same general approach has been applied to the analysis of logic programming languages as well. a-8)

An abstract interpretation is defined by an abstract domain and abstract semantic functions. The potentially infinite computat ional domain (the concrete domain) is approximated by mapping sets of elements of the concrete domain into single elements of the abstract domain. This mapping is called the abstrac- tDn functDn, a. The abstract domain will usually be finite and represent some specific information of interest and abstract away other information. The abstract semantic functions define an abstract denotation of the program, which should be a safe approximation to the concrete denotation. That is, the inter- pretation is safe if a(Mce)~MA P, where MA ~" and Mc ~ are the abstract and concrete meanings of program P as defined by the respective semantic functions and ~ is the ordering on the abstract domain. An unsafe abstract interpretation would not capture part o f the concrete meaning and could give unsound results.

A clear account of the safety conditions, in the framework of logic programming is given by Jones and Sondergaard3 ) There is also further discus- sion of safety (or consistency) in later sections.

The interpretation of a program over a finite abstract domain is terminat- ing, and if safe may reveal information about specific aspects of the program's concrete behaviour. The practical usefulness of performing an abstract interpre- tation depends on the particular abstract domain and semantic functions defined. In the extreme case, abstractions which "throw out" everything will be useless.

Jones and Sondergaard S) suggest a general approach to abstract inter- pretation of logic programs in which the semantics of the language is parti t ioned

Specialisation of Prolog and FCP Programs Using Abstract Interpretation 161

into two parts, the core part common to all interpretations, and the domain part which is particular to a specific interpretation. The core semantics is based on a collecting interpretation of the program. That is, the meaning of the program contains information not only about the answers computed but also about the course of the computations and the intermediate states reachable. Thus for example, an abstract interpretation for the mode analysis of a Prolog program may be applied to provide information about the modes in which predicates are called. The modes of the final answers computed by the program are of little interest in this case.

1 .2 Program Specialisation In operational terms, abstract interpretation using a collecting semantics

performs a flow analysis ; it collects the computat ion states and thereby also the computation paths which are followed at runtime. In logic programming computation paths will be represented as sequences of clauses. Program specialisation amounts to the construction o f a program which follows certain computation paths but not others. Thus our approach to specialisation has two stages, an abstract interpretation which yields computation paths, followed by the construction of a specialised program which generates those paths. Further- more, the framework of abstract interpretation provides an approach for proving the soundness of the specialisation process.

This paper presents an approach to specialising logic programming languages which is based on abstract interpretation. We focus on two languages which although similar at first glance are very different in nature; namely sequential Prolog and Flat Concurrent Prolog (FCP). 9~ For Prolog, much work has already been done both on specialisation and on abstract interpretation. Our contribution is in the application of abstract interpretation to program specialisation. For FCP, we must take into account that concurrent programs should be viewed as reactive systems. This is because, typically, a component in a concurrent system maintains a reactive interaction with the other components in the system. 1~ This implies that specialisations of concurrent programs in principle have to preserve intermediate states of computations, since reactive behaviour may depend on intermediate states, as will be seen in the following section. In contrast, the specialisation of sequential logic programs needs to preserve only final answers. It is interesting to note that the techniques presented in this paper provide a uniform approach to the specialisation of both lan- guages, in spite of the differences between the two.

1 .3 Specialising Reactive Systems Program components in reactive systems cannot be described solely by

their input-output behaviour. In specialising concurrent programs which are components of reactive systems it is therefore not enough to preserve only input-output relations. Intermediate states and synchronisation may be observ-

162 J. Gallagher, M. Codish and E. Shapiro

able. One reason is that there may be programs which handle infinite data structures such as streams, and yield no final output. Secondly, given two concurrent logic programs P1 and Pz which yield the same final output, but go through different intermediate states, a third program Pa (called a context) can be constructed which yields different results when run in parallel with P1 than when run in parallel with P2. A simple example in FCP (see Section 4) is the following.

Example Let P~ be

p(X, Y)<--X = a [ Y-- b.

and let / '2 be

p(X, Y ) . - Y : b l X = a .

That is, both P1 and P2 have {X/a , Y /b} as a solution, but P1 instantiates X before Y, while P2 instantiates Y before X.

However running the goal p (X, Y) concurrently with the goal q (X, Y) defined by q (X, Y?),--X? = a [ true.

yields different observable results, since q (X, Y) succeeds only when Y is uninstantiated and X is instantiated to a. This occurs during the computat ion o f p (X, Y) in/'1, but not with p (X, Y) in Pz. p (X, Y), q (X, Y) can succeed in P1, but fails in P2.

Although this example uses a feature (read-only variables) which is not part of other concurrent logic programming languages, Brock and Ackerman give a more complex example, u) which was expressed in FCP by A. Takeuchi, ~z) and which can be expressed similarly in other concurrent logic programming languages.

w Abstract Computation Trees for Prolog This section presents an operational semantics for Prolog which is then

used as a basis for abstract interpretation. The meaning of a program is described as a mapping from goals to computation trees.

Given a language containing functions and predicates, and an infinite set of variables V, we assume the usual definitions of terms, atoms and goals. The atoms true (the empty goal) and fail are also goals. An expression is a term, an atom, a goal, a tuple of expressions or a set of expressions.

Notation A substitution is an idempotent, almost everywhere identity function 0: V--->/) where /) is a set of terms, and the set d o m ( O ) = { X l X O # : X } is finite. The identity substitution is denoted e. The definition of substitutions extends naturally to mappings from expressions to terms. A substitution 0 can be

Specialisation of Prolog and FCP Programs Using Abstract Interpretation 163

represented by a finite set of pairs {Xi /X iOlXi~dom(O)} . The restriction of a substitution 0 to a set of variables Var is denoted 0l Var. The set of variables in an expression E is denoted vats (E).

A renaming is a substitution o': V---, V which is injective. For any expression E and renaming 0, EO is a called a variant of E. A renaming o" of a substitution O={X]/T1 . . . . . Xn/Tn}, is the substitution 0 .a defined by (X~) O. ~ = XOa for all X E dom(O), which can be represented by { X~ a / / ' 1 a . . . . . Xna / Tnff }.

We use the standard notion of the canonical form of an expression in order to avoid the frequent mention of variants. (See Ref. 8), for example.) Given a sequence of canonical variables Z~, Z2, Z3 . . . . . not occurring except in canonical forms, the canonical form of an expression E (denoted HE H) is obtained by renaming the first variable occurring in E by Z1, the second by Z2, and so on. Thus lip(U, V, U)]I is p(Za, Zz, Z1). Variants of an expression have the same canonical form.

Definition A computation state is a pair (A, Subst) where A is a goal and Subst is fai l or a substitution 0: V ~ 19 and /) is a set of terms called the domain o f interpreta- tion. Given a set D, the set o f computat ion states is denoted State D �9 The states (true, Subst), and (fail, Subst> are sometimes referred to as true and fail respectively.

Definition Let D be a domain of interpretation, P be a set of logic program clauses. Let

p: State D • D

be a map called a computation rule over D, and q0 = (A, 0> be a state in Stateo.

Definition The p-computa t ion tree of P and q0 is a tree Tp(P, qo) such that:

(1) Each node in the tree is labelled by a state q ~ S t a t e p . The root is labelled by q0. Each leaf node is labelled by true or fail.

(2) Arcs are directed away from the root and are labelled by clauses from P. There is an arc labelled by a clause c from a node labelled by state rl to a node labelled by a state r2 iff p(rl, c ) = r2.

Given a computa t ion rule p over some domain, we define the operat ional semantics [ �9 ]p as a mapping from logic programs and goals to computat ion

trees in the following way:

[ P, g ~ = Tp(P, (g, e>)

164 J. Gallagher, M. Codish and E. Shapiro

Definition The concrete operational semantics is the operational semantics defined by the resolution computation rule resolve. The concrete domain of interpretation is the set of terms in the language.

[ P, g l resoZve = Tresotve(e, <g, e>)

where the computation rule resolve is resolution on the leftmost atom in the goal and the head of a clause in P.

resolve( <( G1, ..., Gn), 0>, H*--Body)

[ <(Body St, Gz . . . . . G,), (OOmgu(GlO, HSt))> L <fail, fail>

if mgu(GlO, HSt)=r fail; otherwise

where St is a renaming substitution such that

vars((H+--Body) St)f) vars(((G~ . . . . . Gn), 0 ) ) = r

Note that resolve is not defined when 0 is fail , or when the goal is true or fail.

Definition An abstract interpretation is defined by the following:

(1) A domain of abstract substitutions Dsub-- V---> 0 (2) Abstraction and concretisation functions ot and 3, respectively,

or: 2sub---~ Dsub ~,: Dsub---> 2 su~

where Sub is the set of concrete substitutions. If ~ 2 sub and p ~ D s u b , the following hold:

a(~,(p)) =p _c y(a(~) ) .

a and 3/are also monotonic. In addition, we will always have ~(fai l )= {fail} and ot({fail})=fail .

(3) An abstract computat ion rule p, which is consistent with the concretisa- tion function and standard resolution in the following way: If t rEDsub is an abstract substitution, c ~ P a program clause,

resolve( <A, 0>, c)=<A' , O'>( O" #: fail), and O~),(tr),

then ~ tr' such that p(<A, tr>, c ) = <A', tr'> and 0"~7(~') . This condit ion is similar to the consistency condition stated by Bruynooghe et al. 3) For a consistent abstract computation rule p, we shall assume that it can be expressed in the following way, using some composition opera tor 0~ II & on abstract substitutions:

Specialisation of Prolog and FCP Programs Using Abstract Interpretation 165

p ( < ( a , . . . . . an),a>,( n .-- Body ) ) : <( Body/z, G2 ..... a . ) ,~ II 6>

where 8 = a({mgu(G~, H/z)}). The concrete rule resolve can be expressed

in this way, where and & 110z=supremum(6~, Oz), using the supremum of substi tut ions as defined by Eder. TM

2 . 1 Clause Sequences Given a c om pu t a t i on tree, conta in ing a node labelled by state G, a clause

sequence for G is a finite sequence o f clauses Cl . . . . . cn which labels an initial part o f a branch leading from G. A n answer sequence c~ .. . . , cn is a clause sequence labell ing a b ranch terminat ing wi th a state true. Given a clause

sequence c~, ... , c~ the associated answers are all answers in the tree given by

answer sequences Cl . . . . . c~ . . . . . cn, (j < n).

2 . 2 Abstract and Concrete Computations We consider the relat ion between a concrete compu ta t i on tree and an

abstract tree which represents it.

Definition I f S A = ( G , ~)(G~=.fail, ~=#.fail) is an abs t rac t state and 6 E y ( ~ ) , and S c= (G, 8) is a concrete state, then we say tha t S A represents S c.

I f P is a program, G a goal, o" is an abstract substi tution over d o m a i n D,

p is an abstract c om pu t a t i on rule over State D, and 0 E y(~) , then the abstract

tree Tp( P, ( G, ~) ) represents the concrete c ompu ta t i on tree Treso~ve(P, ~ G, 0)).

Lemma I f p is consistent, the abstract state S A represents concrete state S c, and S C ~ .fail, then either p( S A, c) represents resolve( S c, c), or resolve( S c, c) equals fail.

Proof F r o m the definit ion o f consistency of p. [ ]

Proposition (Abstract computa t ions preserve clause sequences.)

Given a concrete tree T c and an abstract tree T ~ which represents it, if So a= (G, (r)( G d: fai l , (rd:fail) is an abstract state in T A, 0~y( (~ ) , So c = (G, 07

is a concrete state in T c and c~, ... , cn is a sequence o f clauses label l ing some branch f rom the concrete state S0 c to some state S c, then there is a b ranch

labelled by the same sequence o f clauses leaving the abstract state S0 a to a state S a. Furthermore, either S c is fail , or S A represents S, c.

Proof (by induct ion on the length o f the sequence)

Basis I f the sequence equals a single clause c, then the propos i t ion holds by the consistency of the abstract computa t ion rule, and the Lemma above.

166 J. Gallagher, M. Codish and E. Shapiro

Induction

Suppose that q . . . . . e~ is a clause sequence leaving the concrete state So c, and that the same sequence leaves the abstract state So A. We show that if the sequence can

be extended by en+a in the concrete tree, then we can also extend the abstract tree sequence by en+l.

S c is not fail since we suppose that we can resolve it with e~+~. Therefore

$2 represents S c by the inductive hypothesis. Hence by the consistency of p we can also obtain p(S2, e~+l), and either S,,+~ c is fail , or S,,+a A represents S~+1 c.

[ ]

Note that a sequence may be in an abstract tree but not in the concrete tree which it represents. Not every abstract computat ion need have a concrete counterpart.

2 . 3 Specialisation by Partial Evaluation and Its Refinement We now turn to program specialisation, the main topic of this paper. A

clause sequence represents a piece of computation. In the following procedure which is the basis of specialisation we show how to replace the clauses in a

clause sequence with one (or possibly more than one) new clauses which achieve the same effect in fewer steps when applied to a goal.

Definition Given a clause sequence cl . . . . . c n ( n > l ) from a computation tree, and H ~ - B which is the clause q, we define a clause unfold(c~ . . . . . cn) as Htk~-Bodyl~ where

resolve(resolve(...(resolve( <B, e), c2), Ca), ..., cj)= <Body, ~ )

and either Body = true and j _~ n, or Body -- B1, ... ,Bm and j = n. If j < n then Cj+l . . . . . Cn is the residual clause sequence.

The residual clause sequence gives rise to a clause unfold(c~+x . . . . . c~) in

the same way. Clauses may thus be derived until the residual sequence is empty. The set of clauses derived from a clause sequence q, ..., cn is referred to as Cl(q, . . . . cp. [ ]

Definition Given a concrete substitution 6 = {X1/TI . . . . . X~/T~}, we denote by X = T the

goal IX1 . . . . . X ~ - - [ T1 . . . . . Tn~. This goal is called a substitution goal. The predicate X = Y is defined by the single clause ( Z - - Z ~ - t r u e ) . In practice we

will not represent explicitly resolution on substitution goals. Given a state <(X = T, G~ . . . . . G~), ~> where ,Y= T is the substitution goal for a substitution

O, then the succeeding state is <(G1 . . . . . Gn), t ro 0>. []

Clearly the clauses H~k~---B~ and H*--X = T, B are logically equivalent if X = T corresponds to ~k. However, in order that the unfolded clause

unfold(c1 . . . . . c~) above yields exactly the same state as the clauses q . . . . . cj when

Specialisation of Prolog and FCP Programs Using Abstract Interpretation 167

applied to a goal, we should consider that the second form is actually used, in building computat ion trees. We will ignore this technical point from here on.

Proposition Given a set S of clause sequences, from a program P, let P ' = U { CI(e) I e ~ S} .

Then Treso~ve( P', < G, 0 ) ) includes all computa t ion states of Tresozve( P, < G, 0 ) ) which are reachable on branches labelled by concatenations of sequences from

S.

Proof (by induction) Let <A1 . . . . . A,~, if} be a state reachable by a branch labelled by q . . . . . Cr, where q , . . . , cr is the concatenation of sequences from S. Suppose first that q , . . . , er is itself in S. Then by the definition of unfo ld the clauses C l ( q .... , e r ) ~ P ' can be used to reach the same state.

Next suppose that the proposi t ion holds for all q . . . . . cr obta ined by concatenating no more than n sequences from S. Then if <A~ .. . . , Am, ~ ) can be further unfolded by a sequence of clauses er+~ ... . , er+~ from S, then it can also be unfolded to the same state by clauses Cl(cr+~ . . . . . Cr+~)c--P ". Hence any concatenation of n + 1 sequences from S can be simulated by applying clauses from P' . [ ]

Given the following definition, we state a corollary to the above proposi- t ion relating to partial computations.

Definition A partial computation tree is a computat ion tree with an arbitrary number of subtrees removed. (When a subtree rooted at R is removed, all arcs and nodes below R (but not R itself) are removed.) Thus the leaves of a partial computa- t ion tree may be arbitrary computat ion states. [ ]

Corollary Let T be a partial concrete computat ion tree derived from Tresotve(P, <G, O)).

Suppose T contains k clause sequences c/ .... , cn, i, (1 < i < k ) (n i>0 ) , and let P ' be the union of sets of clauses Cl(cf, ... , cn,~), for all i. Then if S is a computat ion state at a leaf in T, then S is in the tree Tre~oiw(P', <G, 0)). []

We thus use the derived clauses to reach the states unfolded in the partial computat ion. Using the clauses of P to unfold the computat ion further, we can clearly derive all answers in the full computa t ion tree. This suggests a method of program specialisation, which most closely corresponds to specialisation by "partial evaluation": ~4-16)

(1) Given a program P and a goal G, unfold part of the computa t ion tree.

This gives a set of clause sequences. (2) For each clause sequence ct . . . . . cn, derive C l ( q , ... , c,). Add to this set

of clauses all the clauses from P for goals occurring in the bodies of

168 J. Gallagher, M. Codish and E. Shapiro

clauses from Cl (cl .... , cn). This is the specialised program for G.

This method could be refined. Firstly, the predicates in the bodies of the derived clauses can be renamed, to prevent possible duplication of answers. For instance, if H(t)~-BI . . . . . H(y), ... , Bn is a derived clause, the occurrence of H(y) in the body should be renamed H'(y) , and the clauses for H in P should also be renamed as clauses for H' . The set of answers is still preserved.

Secondly, the bodies of the derived clauses may in turn be specialised, in the same way that G was specialised. This process could be applied successively; however it might not terminate. Predicates occurring to the right of recursive predicates might never be specialised. A simple example which shows the limitation of partial evaluation is the following:

Example Suppose a program contains the following segment:

(1) p(X)~--r(X, Y\[]), q(r). (2) r([ ], L\L)~-- (3) r(EXIXs], L\L1),~--r(Xs, L\[XIL1]). (4) q ([ ])~--... (5) q ( IX I Xs])*--...

The procedure p(X) reverses X giving Y and then calls q(Y). Given the goal p(a, [blXs]) a partial computation tree could be unfolded which yielded a specialised clause for p, given by the clause sequence [1, 3, 3]:

p([a, blXs])~--r(Xs, Y\[b, a]), q(Y).

This is really the limit of what partial evaluation can achieve in this example. The clauses (2), (3), (4) and (5) have to be added to the above clause in the specialised program. Since Y is instantiated only when r(Xs, Y\[b, a]) termi- nates, and this goal has an infinite number of solutions, we can never detect by unfolding that Y is always a non-empty list and thus we cannot eliminate clause (4).

Abstract interpretation on the other hand may yield an approximate result Y = [ Y1 I Ys], which represents all non-empty lists. In this case we could determine that clause (4) could not succeed at this point in the program, and the call q(Y) could be further specialised.

2 . 4 Using Abstract Interpretation for Specialisation

Definition A set of finite clause sequences S from a computation tree for program P is adequate if any answer sequence in the tree can be obtained by concatenating sequences from S. (Clearly the set of singleton sequences from P itself is adequate.)

Specialisation of Prolog and FCP Programs Using Abstract Interpretation 169

Proposition Given a program P and goal <G, 0>, then if S is an adequate set of clause

sequences for the tree Tre~olve(P, < G, 0>), and P ' isU { CI(c) I c ~ S }, then Tre~owe (P' , <G, 0>) contains exactly the same answers as Tresolve(P, <G, 0>).

Proof Since each answer is obtained by a clause sequence which is a concatenat ion of sequences in S, the result follows from the previous Proposition. [ ]

The use of abstract interpretation provides the power to compute such an adequate set of clause sequences. A call is an instance G~ of a literal which occurs in a computa t ion state <(G1 .... ), 0> in the computat ion tree. We observe that if we can unfold a computat ion to a partial tree which contains all possible calls, then the full tree contains only concatenations of clause sequences which have arisen in the partial tree. This is because a call determines all its subcalls.

The number of calls in a computat ion tree is in general infinite. By using a finite abstract interpretation we arrive at a tree containing a complete set of abstract calls, which by the proposit ion proved above contains all the clause sequences from the full concrete tree. As the previous example showed, it may be impossible using simple unfolding to eliminate a clause which in fact is never called. Specialisation using abstract interpretation is a method which guarantees termination, yet which allows specialisation of all the predicates in the program. Abstract interpretation may, on the other hand, be more costly than a simple unfolding, and its advantages may become apparent only when the recursive structure of the program is more complex.

Even when the abstract domain P is finite, an abstract computa t ion tree may have infinite branches, and there is a problem in knowing when all calls have been reached. We will therefore present a modified computat ion, with which we can compute a finite computat ion tree (for finite /) ) which is complete in that it contains all calls (and all answers) which the full computa t ion tree

contains.

2 .5 A Fixpoint Approach Using Calls and Answers We construct a computat ion tree by computing the fixed point of a

function on a set of calls and call-answer pairs. The O L D T computat ion method for Prolog, originally defined by Tamak i and Sato, 17) is one implemen- tation of such a function, and hence we call the computat ion tree the " O L D T tree". The terms "cal l" and "answer" are now defined.

Definition Let S = <(G1 . . . . . Gn), 0> be a state. The call corresponding to S is defined as

call(S): IN < 61, I vats( G1)> IS.

For instance,

170 J. Gallagher, M. Codish and E. Shapiro

eall( <p(a, X), { X / b } ) )=call(p(a, Y), { Y /b , Z / e } ) )= <p (a, Z1), {Z,/b}).

I f the goal of S is true or fail, we define call(S) to be true or fail respectively.

An answer for call(S), with program P, is some substitution ~k [ vars( G1), such that

3 C~ .... , Cm~P (p(...p(S, C1) . . . . . Cm) = (G2 . . . . . G,, ~k),

A call-answer pair for state S is a pair :

II((G1, o'1 vars(G1)), ~1 vars(6~)>[[. []

Intuitively, a call-answer pair <<G, 0>, ~ ) , may be regarded somewhat like the unit clause G ~ t r u e . This clause could then be used to solve calls <G, 0>. This is not quite accurate, since this clause would contain terms f rom the abstract domain of interpretation, while clauses f rom the program do not. To be more precise, we define an extension to the computa t ion rule p, which uses call-answers pairs, making use of the composi t ion operator [[ introduced earlier.

Definition Given a state S = ( ( G 1 . . . . , Gn), 0) and a call-answer pair q = (<A, 0% ~>, where q ~ X ,

answerX(S, q)--<(G2 . . . . . Gn), 0 H ~ )

where < < G1, 01 vars(G1)>, ~b~) is a variant (containing no canonical variables) of q. I f such a variant does not exist, then answerX(S, q) is not defined. In other words, we rename (if we can) the call-answer pair so that it exactly "fits" the state and then compose the answer substitution with the substitution in the state.

[]

Claim The following property holds. I f $1-- <(G1 . . . . . G~), 0) and q- - ((A, o'>, # ) is a call-answer pair, then

answerX( S1, q) --- $2 (3 CI . . . . . C ~ P (#(...p(St, G ) . . . . . Cm)-- $3) and call(Sz)--call(S3))

Definition Given a program P, a computa t ion rule p, and a set X of call-answer pairs for p and P, then we define an extended computation rule px.

pX: S ta teX(P U X)---~State

px ( s , c ) = f # (S , c) if c ~ P ; answer x (S, c) if c ~ X

The above claim implies that px generates the same set o f calls and answers as /9 does.

Specialisation of Prolog and FCP Programs Using Abstract Interpretation 171

Definition Given a set X of call-answer pairs, a state S = <Gt, ... , Gn, O> is answered in X with substitution tr if

Xx . . . . . X~ ~ X answerX ( S , Xx ) = $2 . . . . . answerX ( Sn_ l, X~ ) = ( true, a > .

We now give a monotonic function on sets of calls and call-answer pairs.

The fixed point of this function contains all calls and their corresponding

answers which may arise in a computation starting from an initial state with call <G, 0>. The intuition behind the function is that if A is a call which occurs in a computation, and H*--B~ . . . . . B~ . . . . . B~ is a clause such that m g u ( H , A ) =

0 then some instance of Bj, l < j < n is a call if the goal (B~, ... , B~-t)O can be solved. If the whole body (B~ .... , B~)8 can be solved, then an answer for A has been found. These are the only ways in which calls and answers can arise. Since

calls and answers are mutually dependent, the function below maps a set of calls and answers into another set of calls and answers. Mannila and Ukkonen 7>

define essentially the same function, in an algorithmic way, as a basis for

abstract interpretations. Given a program P, an initial call C = < G, tr> and a set of calls and call-answer pairs X, the function Fp.c is defined as follows:

I[<Bi, r l vars(Bi)>[[

I[<H, 0>, ~,lvars(H)>ll

F~,~(X) =

~c~ U

c E P , I]<H, ~>ll ~ x , o(<H, 6~>, e)=<(Bt , ..., Bj . . . . . B,), 6z>, <(B1 . . . . . Bs-1), 8z> is answered in X

with substitution r

U c~P, II<n, O>l] ~ X p(<H, 8>, c ) = <(nl . . . . . Bn), 01> <(B1 . . . . . Bn), ~> is answered in X

with substitution r

Continuity of Fe, c

Given a set of atoms over some signature, a set of variables, and a set of substitutions over some domain of interpretation, let C be the set of canonical

terms of the form <A, 0> and <<A, 0), ~k>. The set 2 C forms a complete lattice, with ordering c , bot tom element ff and top element C. A directed subset of

2 C is a set X such that every finite subset of X has an upper bound in X. We

quote a lemma from lattice theory.

Lemma If X is a directed subset of a complete lattice, { Y~ . . . . . Yn} - lub(X) iff{ Y1 . . . . . Yn}C_I, for some I E X .

172 J. Gallagher, M. Codish and E. Shapiro

Corollaries

(1) A ~ l u b ( X ) iff A ~ I , for some I G X . (2) If S = ( ( G 1 . . . . . Gn), 8) is a state, and S is answered in lub(X), then S

is answered in I, for some I G X.

Proposition Let a computation rule p and the answer rule be continuous mappings. Then the function FG,c is continuous, that is,

lub(FG,c(X)) = Fc,c(lub(X))

for every directed subset X of 2 C , where FG,c(X)= {FG,c( Y)] Y ~ X } .

Proof

A ~ lub( FG,c( X ) ) iff 3 I ~ X : A~FG,c(I), since lub is set union.

We may then substitute I by lub(X) in the definition of Fc,c(I), by the corollaries of the lemma, and hence

A~Fc,c( I ) iff A~Fc,c(lub(X)). []

2 . 6 The Abstraet OLDT Tree OLD resolution with tabulation (or OLDT) was used already for abstract

interpretation by Kanamori and Kawamura. 6) Our presentation differs from theirs in two respects: ours relates more closely to the fixpoint approach presented in the previous section, and it is defined for abstract computat ion rules, not resolution specifically.

An OLDT tree contains all derivable answers for a goal, and all calls which may arise in the computation. In the O L D T method the calls are recorded in the computation tree and call-answer pairs are recorded in a table. Each stage of the OLDT method derives a new tree and table from the previous stage until some stage yields no new information in the tree or table. This corresponds to finding a fixed point of a function similar to the function Fp,c given above.

In fact the OLDT tree construction algorithm described below computes the fixed point of the following similar function, which is monotonic, but not continuous since each iteration may produce an infinite number of calls and answers, unlike the function FG,c. However, for finite abstractions, there is a bound on the set of calls and call-answer pairs, and the function iterates to a fixed point in a finite number of steps. We do not use the function Fp,c directly since it corresponds to a breadth-first computation, and for efficiency reasons it is desirable to expand the computation depth-first as far as possible.

Specialisation of Prolog and FCP Programs Using Abstract Interpretation /73

OLDTp,c(X) =

( II<B~, r

II<<H, 0>, ~[vars(H)>[[

Definition (OLDT tree)

{c} U

II<H, 01>11 ~X, pX(pX(...(<H, 0~> .... )=<(B~ . . . . . Bn), ap>,

U cEP, II<n, 0>11EX, p(<H, 0>, c ) = <(Ba . . . . . Bn), 0~>, <(B~ . . . . . Bn), 0~> is answered in X

with substitution 1],

Given a program P, an initial state G, a set X of call-answer pairs, and a computat ion rule p, the O L D T tree is defined as follows:

(1) Each node in the tree is labelled by a state. The root is labelled by G. Each node is classified as a solution node or a loop node. A node L is a loop node if and only if it has an ancestor solution node with the same call. Each solution leaf node is labelled by true or fail.

(2) Arcs are directed away from the root. There is an arc labelled by c E P from a solution node labelled by state r~ to a node labelled by a state rz iff p(rl, c)=rz . There is an arc labelled by x ~ X from a loop node labelled by state rl to a node labelled by a state r2 iff answer x (rl, x ) - - r2.

Notice that p is applied only to solution nodes, and answer is applied to

loop nodes.

Algorithm: Construction of the OLDT Tree Given a program P and initial state <G, 0>, the abstract O L D T tree is construct- ed by applying the following operations:

(1) Initialisation Stage: Initialise the root node of the tree to <G, 0>, initialise the set X of call-answer pairs to 4'-

(2) Tree Extension Stage: Apply the computat ion rule/a x to each leaf of the tree (depth-first), extending each branch until:

(a) it reaches the goal true or fail , or (b) it reaches the state S where cal l (S) is identical to call(R) where

R is an ancestor state of S in the tree. When such a node is reached, it is classified as a loop node (called lookup nodes by Tamaki and Sato). All other nodes are classified as solution nodes.

(3) Answer Collection Stage: Compute the set of call-answer pairs contained in the current tree. These are canonical pairs

I7d J. Gallagher, M. Codish and E. Shapiro

where ((G1, ..., G,), 0) labels some node of the tree, and ((Gz ..... G,), ~k) labels a descendant node. These pairs may be efficiently found using answer literals, call-exit markers or such a device (see Ref. 6)). Update the set X with the new pairs. I f no new pairs are found, halt. Otherwise, go to the Tree Extension Stage. Note that when pX is applied to loop nodes, it is necessary only to apply

answer x, and not p, since nothing new can be added by reapplying clauses to states whose calls have already occurred.

Note that if the interpretation is over a finite domain, then the number of canonical calls and call-answer pairs is finite, and the O L D T tree is therefore finite,

Example Let P be the program

{ length( [ ~,0)~ true, length ( [ X ] U J , s( N ) )*-- length( U, U)}

Let the initial state be (length(X, Y), {X/[a, b] ZJ }).

For the purpose of illustration in this example we use an abstract computa t ion rule based on depth abstraction, similar to that discussed by Kanamor i and Kawamura. 6) We do not propose that depth abstractions will be generally useful for program specialisation; in our current work we are inves- tigating abstractions based on the type structure of the program.

A depth-d substitution is a substitution of the form {X1/T1 . . . . . X,/Tn} where each T~ is a term of depth at most d. The depth-d abstraction of a concrete substitution {X1/T1, ..., X , /T ,} is obtained by replacing each term Tj by a depth-d abstracted term Sj. St is obtained from Tj by replacing each subterm (other than variables and constants) in T~ at d e p t h > d by a fresh variable not occurring elsewhere in the substitution. For example, abstracting (to depth 2) the substitution { X / f ( Y, g(b, h ( Y)))} we may get { X / f ( Y, g(b, z))}.

The concretisation function 7 applied to a depth-d abstracted substitu- t ion o" is defined as the set o f all concrete substitutions of which o" is a depth-d abstraction. The depth-d abstraction of a set ~ of concrete substitutions, ota(xIt), is the minimal (according to an ordering on substitutions) depth-d abstracted substitution 6", for which y ( 6 " ) _ ~ . This technique of defining the abstraction function from the concretisation function is due to Bruynooghe. TM

The computat ion rule corresponding to a depth-d abstraction is then

p(<G, o->)= <Gl,(O-o 0~)>

where resolve( < G, o'>) = < GI, (o" o 01)> and ak ({ 0 } ) = 01, for some k <- d such that k is the largest possible value making ~ o 81 a depth-d abstraction. For instance,

Specialisation of Prolog and FCP Programs Using Abstract Interpretation 175

if d = 2 , (y={X/ f (Y)}, and 0 : { Y/g(g(a))}, then k = 1, 01={ Y/g(Z)} and a�9 01= {X/f(g(Z)), Y/g(Z)}.

Constructing the OLDT tree, with program P and goal G, P the set of terms of depth at most 2, and a depth-2 abstracted computation rule, we go through the following steps. X is the set of call-answer pairs, and T is the set of calls in the tree. (For brevity a call ( H , 0) is represented as HO, and a call-answer pair ( ( H , 0), lk) as (HO, H~)).

Stage 1

Stage 2

Stage 3

X=q~, T-- {length([a, b[ Z~, Zz)}

X={(length([a, bl ZlJ, z~), length([a, b], s (s (0)))), (length([b[ Z~J, Zz), length(Ibm, s(O))), (length( Zx, Zz), length([ 1, 0))}

r = {length([a, b[ Z~, Zz), length([b[ Z~J, Z2), length( Z~, Z2)}

X= {(length([a, b[ Z1], z2), length([a, b~, s(s(0)))), (length(~b[ Z~, Zz), length([b], s(0))>, (length(Z1, Zz), length([ ], 0)), (length(Ea, b[ Z~J, Zz), length([a, b[ Z~], s(s(Zz)))), (length(E b [ Z~J, Zz), length([b[ ZIJ, s( Zz))>, (length(Z1, Zz), length(Z1, Zz))}

T = { length([ a, b [ Z~3, Zz), length([b[ Z1], Z2), length( Z~, Zz)}

Stage 4 No changes to X or T.

w Deriving a Specialised Program from the OLDT Tree We now discuss how to obtain a specialised program from an abstract

tree. In the case of Prolog, a program specialised for a given goal should compute the same answers for that goal which the general program computes.

We use the structure of the OLDT abstract tree to split the computat ion tree into finite sequences, and obtain clauses for those sequences. Note that a branch in the OLDT tree is labelled by a sequence of clause sequences and call-answer pairs. The call-answer pairs label only arcs following loop nodes, that is, nodes for which a node with the same call occurs as an ancestor (called

17 6 J. Gallagher, M. Codish and E. Shapiro

its loopcall node). Given an O L D T tree, let the non-looping sequences be the set of clause

sequences containing only the largest subsequences from branches of the O L D T tree which do not pass through a loop or a loopcal l node.

Proposition Any clause sequence in the full abstract computa t ion tree may be obtained by concatenating non-looping sequences from the O L D T tree (the non- looping sequences are adequate) .

Proof The O L D T tree contains every call which is in the full tree. For any branch in the full computat ion tree, identify the nodes, if any, which have a call which appears more than once on the branch. These correspond to loop nodes or loopcal l nodes in the O L D T tree. The clause sequences between such nodes are the non-looping sequences in the O L D T tree, since the O L D T tree differs from the full tree only in its treatment of loop nodes. [ ]

3 . 1 Splitting the OLDT Tree Recall that some nodes in an O L D T tree are classified as loop nodes, and

some as loopcall nodes. Each loop node has an ancestor loopcall node with the identical call.

An O L D T tree is split into subtrees by,

(1) removing the arcs leading from loop nodes and considering each of its successor nodes to be the root of its subtree.

(2) cutting the tree at the loopcall nodes and considering the loopcalls to be both the leaves of the tree above and the roots of the subtrees.

I f we enumerate all paths from the root to the leaf nodes in each subtree, there are four cases, depending on the type of node at the start or end of the pa th (cl, .... c~ represents the sequence of clauses on a path).

(1) G eL, e2 . . . . . e~ true

(2) 6 c~, c2 . . . . . e, f a i l (3) G el, c2 . . . . . en loopcall

(4) loopeall el , e2 . . . . . cn loop

Clauses Cl(c~ . . . . . c~) are constructed corresponding to the clause sequences in the subtrees.

Failing paths (those ending with fa i l ) , can be ignored in the specialised program.

Paths ending at true result in unit clauses. Paths ending a loop or loopcal l nodes produce clauses with non-empty bodies. A path from a loopcall node to a loop results in a recursive clause.

In fact it may happen that some sequence in the abstract tree fails to

Specialisation of Prolog and FCP Programs Using Abstract Interpretation 177

produce a clause (some resolution during the clause construction process fails). In this case the sequence is ignored. It simply means that the abstraction was too imprecise to distinguish failure from non-failure in this case.

Some renaming of predicates may be desirable. Suppose there are two

calls to the same predicate at different parts of the tree, one of which is more specialised than the other. Then the two should be distinguished so that the more specialised call does not use the clauses for the more general call.

Example Consider the previous example, namely,

length([ ], O)~-true. length([Xl U], s(N))~--length(U, N).

with the same initial call. The clauses are labelled 11 and 12 respectively. The

O L D T tree which we constructed earlier is split into the following sequences.

(1) 12, 12

(2) 11

(3) 12

Note that there are two different nodes which match the predicate length, so we distinguish them. From the first sequence we obtain the clause

length([X~, X21X3], s(s(N)))<---lengthl(X3, N).

The second sequence generates

lengthl([ ], 0).

and the third,

lengthl([X~ IX2], s(N))'~lengthl (Xz, N).

[1] Correctness of the method

Proposition Given a program P, an abstract state <G, ~5, and 0E3r(cr), then <true, ~5 is an

answer in the computation tree T~esoz~e(P, (q, 0)) iff <true, ~5 is an answer in

the tree T~eso~w(P', (g, 05), where P ' is the program obtained from the non- looping sequences of the OLDT tree for P and (G, ~5, with consistent computa-

tion rule p.

Proof <true, ~) is an answer in the concrete tree Tresowe(P, (g, 0)).

Let cl, ... , cn be the clause sequence leading to this answer, cl . . . . . Cn is also a sequence in the abstract tree Tp(P, (G, o')), (since the abstract tree

represents the concrete tree). cl . . . . . c~ can be obtained as a concatenation of non-looping sequences in

178 J. Gallagher, M. Codish and E. Shapiro

the abstract OLDT tree for P and (G, 0). The program P ' is derived from the non-looping clause sequences

which is an adequate set of sequences; therefore the concrete computat ion tree T~e~owe(P ', ( g , O )) includes exactly those computation states of Tresowe(P, (g, 0)) reachable by applying concatenations of the non-looping clause sequences.

(true, ~) is an answer in the concrete tree Tresowe(P', (g, 8)).

(2) Comparison to other methods In comparison to other methods of specialising Prolog programs, this one

has the advantage of guaranteeing termination when the abstraction chosen is finite. Secondly, since we perform a complete flow analysis of the program, we obtain some finite approximation to the answers for recursive predicates, and so we are also able to specialise all calls following a recursive call in a program. The advantage of this is more likely to be seen in programs with more complex recursive structure, since the complexity of abstract interpretation is greater than simpler unfolding methods of partial evaluation. Thirdly, the method gives a clear guide as to where new predicates should be introduced, to distinguish different calls to the same procedure. This aspect has been largely ignored in existing specialisation work. Abstract interpretation also provides a framework for showing that the specialisation is sound and preserves the required properties of the general computation.

w Specialisation of Flat Concurrent Prolog Programs

4 .1 Flat Concurrent Prolog Concurrent Prolog extends logic programming with the notion of read-

only variables as a synchronisation primitive and the commit operator which distinguishes between guard atoms and the proper body atoms of a clause. Concurrent Prolog distinguishes between the writable occurrence of a variable, X, and its read-only occurrence, X?. The read-only variables impose a restric- t ion on the unification of terms and provides a synchronisation mechanism. A read-only variable is "protected" from being instantiated in unification. The intension is that a computat ion that needs X? to be instantiated will suspend until its environment will instantiate a writable occurrence of X.

We restrict ourselves to the so-called flat subset of Concurrent Prolog in which the guard atoms are constructed from a fixed set of test predicates. We include here a brief description of the language Flat Concurrent Prolog (FCP). A more detailed definition may be found in Ref. 9).

The standard definitions of terms, atoms and goals are extended to include read-only occurrences of variables and substitutions are extended to be almost identity mappings from occurrences of read-only and writable variables to the set of terms. A substitution that maps no read-only variable X? to a term

Specialisation of Prolog and FCP Programs Using Abstract Interpretation 179

T ( T # : X ? ) is said to be admissible; a substitution which is not admissible is said to be inadmissible.

A f la t guarded clause is a guarded clause of the form

H ~ G 1 , Gz . . . . . Gm[B1, Bz . . . . . B , (n, m~_O)

in which each of the Gi are test predicates and the B; are goals. H is called the clause head; G1, Gz, ... ,Gm the clause guard and B1, Bz . . . . . B , the clause body. A Flat Concurrent Prolog program is a finite set of fiat guarded clauses.

Goals may be solved in any order and may be viewed as a system of parallel processes. A program may be viewed as a set o f rewrite rules to be applied non-deterministically on the processes. A process may commit and be rewritten to an instance of a clause body if (i) it unifies with the clause head with an admissible substitution; and (ii) the clause guard evaluates to true.

4 . 2 The Abstract Computation Tree for FCP An FCP computa t ion contains two kinds of non-determinism. At each

computat ion step, there is a choice of literal and a choice of clause. A computa- t ion tree for an FCP program and a goal, which included all the non-deter- ministic choices, would be very large. We construct a tree in which the choice of literal is fixed as in Prolog. Not only does this reduce the size of the tree, but we can also deal with termination by comput ing a fixed point on calls and answers as we did for Prolog. However, since the leftmost literal in a goal may not unify with any program clause with an admissible substitution, synchronisa- t ion must be interpreted in a different light.

The meaning of a conjunction p, q will be obtained as a composi t ion of the meanings of p and of q. The meanings of p and of q are in turn defined as sets of sequences of (possibly inadmissible) substitutions. The presence of inadmissible substitutions in the meanings provides additional information about the synchronisation required to reach an answer substitution. Basically, if X ? = a is a binding in the meaning o f p then this is interpreted as a requirement that some writable occurrence of X be externally bound to a. I f this requirement is satisfied by some substitution in the meaning of q then the composi t ion will include the binding X - - a ; otherwise the composi t ion will include a binding X ? = a indicating that this is an external condit ion on the environment. However we need not enter here into a deeper discussion of this synchronisation information since we will ignore it for the abstract interpretation. The synchronisation restricts the set of answers. By ignoring it we are performing a safe abstraction. Instead of sequences of substitutions, we consider single com- posed substitutions. The answers obtained are then a superset of the answers actually obtainable (some answers which are unreachable because o f deadlocks

may be included).

18o J. Gallagher, M. Codish and E. Shapiro

Definition: FCP computation rule Let A1 . . . . . An be a goal and C - - ( H * - G I B ) be a clause.

We assume the definition of a read-only most general unifier, mgu ? (A, C) which unifies the literals A and H, evaluates the guard G and returns a (possibly inadmissible) substitution 0 or fail. We assume that all guards are evaluable by mgu ?.

Let mgu?(A1, (H*--GI B)tz)= ~. Then the computation rule FCP, is defined by:

FCP(((A, . . . . . An), O>, (H~-GI B)) f ((B/z, A2 .. . . , An), (01] ~k)) if ~#:fail L <fail, fai l) otherwise

where /z is a clause renaming as before. The composit ion 0 H ~k is not defined rigorously here; it is analogous to the supremum of 0 and ~, with extensions to handle the composition of read-only and writable occurrences of variables.

Note that FCP makes a distinction between the cases when failure occurs due to failure of mgu 7, and when mgu ? succeeds but the result is incompatible (i. e. because 011 ip fails). This is because the computation should be able to commit to the clause in the latter case.

We claim that the above rule is an abstraction of the standard FCP computat ion rule. The FCP abstract OLDT tree is constructed in exactly the same way as for Prolog, except that the abstract computation rule p is an abstraction of FCP rather than of resolve.

4 . 3 Construction of Specialised FCP Programs Given an abstract O L D T tree, it is now shown how to construct an FCP

specialised program. The specialised program preserves the desired properties of the original program. These are:

(1) The specialised program can compute the same set of answers for goals in the concretisation of the abstract goal with which the tree was built.

(2) The computation of a goal with the specialised program suspends if and only if it suspends with the original program. The conditions for releas- ing suspensions are also the same for the specialised program as for the original.

(3) The intermediate bindings for variables in the goal produced during a successful computat ion with the original program are preserved. This is a strong requirement, but our aim here is to present a solution which preserves the full semantics of a program. A case may be made for performing specialisations which preserve less of the meaning (such as only answers).

(4) Deadlocks are not introduced into the specialised program.

Some general points should be noted before presenting the method for constructing a program:

Specialisation of Prolog and FCP Programs Using Abstract Interpretation 181

(1) Preservation of synehronisation Consider the tree, containing paths labelled by clauses in the program.

The sequence of reductions r~presented by a path need not have been performed in the order in which they appear in the path. The specialised program should allow for this, so that and-parallelism and synchronisation may be present in the computations using the specialised program.

(2) Preservation of commitment Suppose there is a branch node in the tree, and more than one path leads

from the branch node. If these two paths are represented by two clauses, each representing a computat ion starting at the root node (as was the case in the specialised Prolog program), then the computat ion may commit too early to either one or the other of these paths. The computation should not commit until it reaches the branch node. The specialised program should contain explicit calls for nodes where the computation branches. Nodes in the tree from which more than one path leave will be called branch nodes. Note that we have to consider failing paths here also, where the failure does not occur immediately following the branch. This is because commitment to failure may avoid deadlock, as is further discussed below, and we do not wish to introduce deadlocks into the program.

(3) Preservation of protection There may be clauses in which a read-only variable appears in the head

of the clause. This case is special because the argument in any call which matches this clause has to be a variable in order for the reduction to succeed. Any node with an arc labelled by such a clause leaving it should also be represented by an explicit call in the specialised program, since otherwise the read-only variable may be instantiated too early by a reduction in the body of that clause, before the clause has been committed to. Such nodes in the tree will be called protected nodes. This point, though important in FCP, is more specific to the language than the other considerations, and has no analogue in other concurrent logic programming languages.

The tree is therefore split into subtrees as follows:

(1) The arcs leading from loop nodes are removed, (2) The tree is split at loopcall nodes, branch nodes, and protected nodes.

Having done this, we obtain the set of paths from root to leaf in each resulting subtree.

There is a difference between Prolog and FCP concerning fa i l ing branches. In Prolog they could be ignored from the specialised program. In FCP, if a state (fail, fail) occurs immediately after a branch, loopcall, or protected node in the tree, or immediately after the top goal, then it may be ignored, since no computat ion can commit to this branch. Otherwise, we need

182 J. Gallagher, M. Codish and E. Shapiro

in general to retain failing paths since at runtime the computat ion may avoid deadlock by committ ing to a clause which eventually fails. Since this is the only reason we are interested in failing branches, a solution is to derive, for each clause sequence ca . . . . . cn f a i l a clause

H a ~ G l l f a i l

where Ha is the head of cl and Ga is its guard. This allows the computa t ion to commit to this clause, but then fails immediately. (Note that we do not preserve the intermediate bindings on failing branches.)

Given a clause sequence not ending with f a i l we obtain a set of FCP clauses in a similar way as for Prolog. However, there are important differences due to the need to allow for parallelism and synchronisation in the specialised program.

Given any substitution 0-- (3(1/Ta . . . . . X n / T n }, we may construct an FCP goal [X~ . . . . . X,;] = [ T1, ... , T,] . In the following constructions, the expression X-,-= Ti represents the goal EArl . . . . . X~i] = E T~ . . . . . T~i]. I f 0 binds a read-only variable, the goal suspends. Otherwise, the goal succeeds with substitution 0.

Given a clause sequence ca . . . . . c~, we define f cp -un foM(e l . . . . . en) as the F C P clause

H1 "~--- GI IX2 : T2 . . . . . S j : Tj , Ba . . . . . Bk

where

FCP' (FCP' ( . . . (FCP' (Ha , ca), c2) . . . . . c~)--((Ba . . . . . Bk), 010 ... o 0~)

and Ha and Ga are the head and guard respectively of Cl. The function FCP" is similar to F C P but does not combine substitutions.

FCP' ( ( (Aa . . . . . A,,), e ) , ( H ~ G ] B o d y ) )

= ( ( B o d y tz, Az . . . . , An), e>.

The terms X ; = Ti represent the substitutions fcpcall(A1, ci) obtained from the f cp-unfo ld operation, where FCP' ( ((Aa . . . . , A,.), e ) , ce) occurs in the unfolding process. As before, i f j < n then there is a residual clause sequence and further clauses can be derived until the residual sequence is empty. By retaining the terms )7~= T~ in the derived clauses, the synchronisation and and-parallel- ism rules may take effect at runtime. That is, the reductions performed in left-to-right sequence during abstract interpretation may be performed in any order (consistent with the read-only annotations) when executing the specialised program.

We do not examine here further optimisations, but it is clear that the sequence of substitutions .~ j= ~ may sometimes be simplified.

Specialisation of Prolog and FCP Programs Using Abstract Interpretation

4 . 4 Comments on the Speeialised FCP Programs

183

(l~ Correetness The justification of correctness of the answers derivable by the specialised

FCP program is similar to the justification given for Prolog. As regards the preservation of reactive and nondeterministic behaviour, our reasoning has been much less formal. We have preserved all the computations to which the program may commit, and have retained in the specialised program the ability to perform reductions in any order consistent with the read-only synchronisation. In fact, even more orderings are possible, since substitutions in subgoals in the original program may be performed before their parents. This cannot affect the reactive properties of the program, however, since only the variables in the top goal are visible to other goals running in parallel with it. Variables from clauses only become visible when the clause has been "called" by performing the reduction corresponding to the unification of the head of the clause with a call.

What may such a specialisation achieve? The specialisation process replaces a deterministic reduction by a goal of the form [X~ . . . . . Am] = E T~ .... , Tn] which may be efficiently compiled. The specialisation may also remove some clauses to which the computation can never commit.

Commitment to failure was preserved in order to avoid introducing deadlocks. If we view this as a marginal case which hardly ever occurs, then we may ignore all branches in the computation tree which lead to fail. Thus more determinate computations may be detected, with corresponding improvements to the specialised programs.

Example Consider the following FCP program.

pl: p(X) ~---true[q(X?), r (X). ql: q(a) ~true[ true. rl: r(a) ~true[ true. r2: r(b) ~true[ true.

If the initial goal is p(Z?), and the abstract computation is over the standard domain, the computation tree contains the following sequences.

(1) pl, ql (2) rl (3) r2, fail

The specialised program derived from these sequences is,

pl ': p(X) ,--truelX?=a, r(X). rl': r(a) o--true[true. r2': r(b) ~true [fail.

184 J. Gallagher, M. Codish and E. Shapiro

Suppose the initial goal is p(a). Since there is now no branch node, the explicit call r(X) can be removed. The only clause sequence is

pl, ql, rl

The specialised program is then the single clause:

pl*: p(X) ~truelX?=a, X=a.

Clearly in this case the clause can be simplified to

pl*: p(X) *-truelX=a.

w Related Work The techniques of abstract interpretation used in this paper are directly

related to other work done on abstract interpretation of Prolog for purposes of global analysis i.e. Refs. 8), 5), 4), 3), 7) and 6). The approach presented here combines features from several of these papers, in a way which we find to be elegant and yet practical. The idea of an abtract computation tree is contained in the work of Bruynooghe, 3~ and Kanamori et al., 6~ but we link this to a fixpoint approach to the collecting sematics, such as is developed by Mannila and Ukkonen, 7~ Winsborough, 19~ and independently by ourselves.

Although abstract interpretation has played a role in partial evaluation, Refs. 5), and 20), it has previously been used only as a preprocessing stage to detect mode information and other information before applying transforma- tions. In our approach abstract interpretation plays a much more direct role. The ensuring of termination of the specialisation while allowing a complete flow analysis of the program is the major advantage of our approach.

The specialisation of concurrent logic programs has been tackled by Safra, TM and work on transforming GHC programs, with intended application to partial evaluation, has also been done. zz~ Our emphasis on ensuring termina- tion while performing a complete flow analysis, and our aim of preserving the complete semantics of parallel programs, are the distinguishing features of our work.

Conclusion We have presented a framework for specialising logic programs, based on

abstract interpretation. Although the procedural reading of concurrent programs is very different from that of sequential programs, the techniques presented provide a uniform approach both for Prolog and for FCP.

Ongoing research focusses on identifying practical and useful abstrac- tions for specialisation. In the case of FCP, our aim is to develop specialisation techniques which relate formally to the full semantics of FCP. TM

Specialisation of Prolog and FCP Programs Using Abstract Interpretation 185

Acknowledgements We acknowledge useful comments f rom part icipants at the W o r k s h o p on

Partial Evalua t ion and Mixed Com pu t a t i on in Denmark , October 1987, where a prel iminary version o f this paper was presented. We also thank the referees for

helpful suggestions.

R e f e r e n c e s 1) Cousot, P. and Cousot, R., "Abstract Interpretation: A Unified Lattice Model for Static

Analysis of Programs by Construction or Approximation of Fixpoints," POPL 77. 2) Mycroft, A., "Abstract Interpretation and Optimising Transformations for Applicative

Programs, "Ph. D Thesis, University of Edinburgh, Dept. of Comp. Sci., Dec., 1981. 3) Bruynooghe, M., Janssens, G., Callebaut, A. and Demoen, B., "Abstract Interpretation:

Towards the Global Optimisation of Prolog Programs," Proceedings of IEEE Sympo- sium on Logic Programming, San Francisco, 1987.

4) Debray, S. K., "Global Optimizations of Logic Programs," Ph. D Thesis, State University of New York at Stony Brook, December, 1986.

5) Jones, N. D. and Scndergaard, H., "A Semantics-Based Framework for the Abstract Interpretation of Prolog," in Abstract Interpretation of Declarative Languages (S. Abramsky and C. Hankin eds.), Ellis Horwood, 1987.

6) Kanamori, T. and Kawamura, T., "Analyzing Success Patterns of Logic Programs by Abstract Hybrid Interpretation," ICOT Technical Report, 1987.

7) Mannila, H. and Ukkonen, E., "Flow Analysis of Prolog Programs", Proceedings of IEEE Symposium on Logic Programming, San Francisco, 1987.

8) Mellish, C. S., "Abstract Interpretation of Prolog Programs," Proc. 3rd ICLP LNCS 225, Springer-Verlag, 1986.

9) Shapiro E., "Concurrent Prolog : A Progress Report," IEEE Computer, August, 1986. 10) Harel D. and Pnueli A., "On The Development of Reactive Systems," in Logics and

Models of Concurrent Systems (K. R. Apt ed)., Springer-Verlag, 1985. 11) Brock, J. D. and Ackerman, W. B., "Scenarios: A Model of Non-determinate Computa-

tions," in Dias and Ramos (eds.), Formalization of programming Concepts, LNCS 107, pp. 251-259, Springer-Verlag, 1981.

12) Takeuchi, A., "Affinity Between Meta Interpreters and Partial Evaluation," ICOT Technial Report, TR-166 (April 1986)

13) Eder, E., "Properties of Substitutions and Unifications," J. Symbolic Computation (1985) 1, pp. 31-46.

14) Futamura, Y., "Partial Evaluation of Computation Process: An Approach to a Compiler Compiler," Systems, Computers, Controls, 2(5), pp. 45-50, 1971.

15) Ershov, A. P., "On the Partial Computation Principle," Information Processing Letters; 6(2): 38-41, April 1977.

16) Jones, N., Sestoft, P. and Sr H. "An experiment in Partial Evaluation: The Generation of a Compiler Generator," in J. P. Jouannaud (ed.), Rewriting Techniques and Applications, Dijon, France, 1985, LNCS 202, pp. 124-140, Springer-Verlag.

17) Tamaki, H. and Sato, T., "OLD Resolution with Tabulation", Proc. 3rd ICLP, LNCS, 225, Springer-Verlag, 1986.

18) Bruynooghe, M., "A Framework for the Abstract Interpretation of Prolog", Report CW 62, Dept. of Computer Science, Katholieke Universiteit Leuven, Belgium, October 1987.

186

19)

2O)

21)

22)

23)

J. Gallagher, M. Codish and E. Shapiro

Winsborough, W., "A Minimal Graph Semantics for Logic Programs," University of Wisconsin--Madison Technical Report, Dept. of Computer Science, August 1987. Fujita, H., "Abstract Interpretation and Partial Evalution of Prolog Programs," ICOT Technical Report, September 1986. Safra, S., "Partial Evaluation of Concurrent Prolog and its Implications," Masters Thesis, Technical Report, CS 86-24, Dept. of Computer Science Weizmann Institute (1986). Furukawa, K., Okumura, A. and Murakami, M., "Unfolding Rules for GHC Pro- grams," ICOT Technical Report, 1987. Gerth, R., Codish, M., Lichtenstein, Y. and Shapiro, E., "Fully Abstract Denotational Semantics for Concurrent Prolog," Proc. LICS'88, Edinburgh (1988)