Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Jones N.D.Partial evaluation and automatic program generation.1999

.pdf
Скачиваний:
9
Добавлен:
23.08.2013
Размер:
1.75 Mб
Скачать

Supercompilation and deforestation 361

Main program

Out := fg;

Pending := fLinitial -> Tinitialg;

while 9 an unmarked rule g0 v x1. . . xm -> e[g v t1. . . tn] 2 Pending do Mark it;

forall g P v1. . . vn -> R 2 Program do

Add g0 P x1. . . xm -> T [[(e[g P t1. . . tn])]] to Out;

De nition of T

T [[x]]

= x

T [[c t1. . . tn ]]

= c T [[t1]]. . . T [tn]]

T [[e[h t1. . . tn]]]

=

let rule L -> R 2 Pgm with L = h t1. . . tn in T [[e[ R]]]

T [[e[g(c t01. . .t0m)t1 . . .tn]]] =

let rule L -> R 2 Pgm with L = e[g(c t01. . . t0m)t1. . . tn] in T [[e[ R]]]

T [[e[g v t1. . .tn]]] =

let L0->R0 = make new(e[g v t1. . . tn]) in

if L0->R0 2 Pending then L0 else

Add L0->R0 to Pending;

result is L0

De nition of Make new

Make new(e[g v t1. . . tn]) = g0 v x1. . . xm -> e[g v t1 . . .tn] where g0 is a new function name, depending only on e[g v t1. . . tn], and fx1,. . . ,xmg = FreeVariables(e[g v t1 . . .tn])nfvg

Figure 17.5: Deforestation by fold/unfold.

cover computations on all possible inputs. By 1, one instantiates variable v in a lazy context e[g v . . .] for all possibly matching g rules. Components of a construction c t1. . . tn so obtained may be processed further since the values of t1. . . tn can possibly be demanded.

Assumption 2 implies that unfolding and instantiation may only be done if forced by lazy evaluation. By 1, a call not inside another call may be unfolded, even if inside a constructor term. For an outermost g call (g (h. . .). . .), the value of h will be needed in order to determine which g rule can be applied. Thus the h call may be unfolded at transformation time.

Summing up, when outermost call unfolding is impossible the algorithm instantiates or unfolds calls in a rst g argument as needed to be able to continue. The result is a program with call con gurations e[g v t1. . . tn], perhaps nested. New

362 Program Transformation

function de nitions are made for each for appropriate instantiations of v, and the program is folded to refer to the new functions. Termination requires the number of these to be nite | which often occurs in practice.

17.4.2Achieving niteness

The algorithm above often improves programs signi cantly. Careful attention has been paid to preserving the language's lazy semantics, but there are problems: it loops in nitely on some source programs, and can slow programs down because of duplicated computations. We brie y discuss the problems and some approaches to solving them.

Finiteness by syntactic restrictions

Wadler's `deforestation' algorithm resembles the one above, and has been proven both to terminate and not decrease execution speed when applied to treeless programs [274,86], de ned by some restrictive syntactic conditions. Non-degradation of run time further requires rules to be right linear, meaning that no pattern variable is used more than once on a rule's right side.

Wadler's transformation rules, and the algorithm above as well, succeed on some non-treeless programs, and improve some non-linear programs. A result recently proven (not yet published) is that the algorithm above terminates and yields the same result whenever applied to a treeless program.

In the literature, relaxation of the syntactic conditions has been done in two ways. Wadler de nes `blazing', a way to recognize parts of a program dealing with non-treelike data, for instance integers, and only requires that the non-blazed parts be treeless and linear. Chin has a `variables only' requirement somewhat more liberal than Wadler's conditions [45]. Further, he devises a type system to extend `blazing' to arbitrary programs, so that every term in a program is annotated as treeless or non-treeless. The result is that any program may be handled; but the annotation scheme is rather conservative, leaving untransformed many terms that the algorithm above can optimize.

Finiteness by generalization

The algorithm of Figure 17.5 `can go wrong' by adding rules g0 v x1. . . xm -> t to Pending or Out for in nitely many t. In general the right side has the form

t = g1(. . . (gn[g v t1. . . tn]. . . ). . . )

so the problem is unboundedly deep call nesting. A concrete example to illustrate the problem is the ` atten' program (a is `append' from before):

 

 

 

 

 

 

Supercompilation and deforestation 363

g (Leaf

x)

 

->

Cons

x Nil

g (Branch

t1 t2)

->

a (g

t1) (g t2)

a

Nil ys

 

->

ys

 

a

(Cons

x

xs)

->

Cons

x (a xs ys)

which returns a list of all leaves of a binary tree. The algorithm of Figure 17.5 begins by constructing

Pending

=

fg0 x ->

g

xg

 

 

 

Out

=

fg0(Leaf

x)

 

 

-> Cons

x Nilg

Pending

=

fg1 t1 t2

 

 

 

-> a (g

t1) (g t2),. . . g

Out

=

fg0

(Branch

t1

t2)

-> g1 t1 t2,. . . g

Out

=

fg1

(Leaf

x)t2

 

-> Cons

x (g0 t2),. . . g

Out

=

fg1

(Branch

t1

t2)t3

-> g2 t1

t2 t3,. . . g

Pending

=

fg2

t1 t2

t3

 

-> a(a(g t1)(g t2)) (g t3),. . . g

Function g0 has one argument, g1 has two, g2 has three, etc., so transformation fails to terminate.

A natural solution to this is to generalize, i.e. to add fewer and more general new de nitions. One way is to extend syntax to include `Term ::= gen(t)'. (gen is not a constructor, and should be ignored in the standard semantics.) In the example above, t1 would not be instantiated to Leaf x and Branch t1 t2.

To illustrate suppose we annotate the program as

g (Leaf

x)

 

->

Cons x Nil

g (Branch

t1 t2)

->

a gen(g t1) (g t2)

a

Nil ys

 

 

->

ys

a

(Cons

x

xs)

->

Cons x (a xs ys)

Applying Figure 17.5 with generalization proceeds as follows:

Pending

=

fg0 x ->

g

xg

 

 

Out

=

fg0(Leaf

x)

 

-> Cons x Nilg

Pending

=

fg1

t1 t2

 

 

-> a gen(g t1) (g t2),. . . g

Out

=

fg0

(Branch

t1 t2)

-> g1

(g0 t1) t2,. . . g

Out

=

fg1

Nil t2

 

-> g0

t2,. . . g

Out

=

fg1

(Cons

x

xs) t2

-> Cons x (g1 xs t2),. . . g

The transformed program is thus:

g0

(Leaf

x)

 

->

Cons

x Nil

g0

(Branch

t1 t2)

->

g1

(g0 t1) t2

g1

Nil t2

 

->

g0

t2

g1

(Cons

x

xs) t2

->

Cons

x (g1 xs t2)

364 Program Transformation

How can one generalize?

There is no de nitive answer to this question as yet. Turchin has a sophisticated online approach which is rather complex, partly due to the language Refal being transformed [269].

Current research involves an o ine approach, preprocessing a program to nd out where to place generalization annotations. The idea is to construct a grammar able to generate all con gurations that could ever arise in transformation by the algorithm above. The grammar will be nite even if transformation would continue in nitely. Once the grammar is constructed it may be analysed to recognize the sources of in nity, to see where to place gen( ) annotations, and how to interpret them so transformation will create only nitely many con gurations.

17.5Exercises

Exercise 17.1 Use the algorithm described in Figure 17.4 to partially evaluate the power-program with respect to n = 5 and unknown x. The initial rule is power5 x -> power 5 x, and the original program is

1.

power

0 x

->

1

 

2.

power

n+1

x ->

x

* power n x

2

Exercise 17.2 Use the same algorithm to partially evaluate the following program. This time with respect to xs = [a,b], zs = [c,d], and unknown ys. The initial

rule is aaabcd ys -> append (append [a,b] ys) [c,d], and the original program is

1.

append

Nil ys

->

ys

2.

append

(Cons x xs) ys ->

Cons x (append xs ys)

2

Exercise 17.3 Apply Burstall and Darlington's method (as in Section 17.2.1) to improve the following program:

1. f xs -> rev (db xs) Nil

2.

db Nil

->

Nil

3.

db (Cons y ys) ->

Cons (2*y) (db ys)

4.

rev Nil ws

 

-> ws

5.

rev (Cons v vs) ws -> rev vs (Cons v ws)

2

Exercise 17.4 Apply the deforestation algorithm described in Figure 17.5 to the following expressions: flip (flip tree) and (sum (map square (upto 1 n))), with the following de nitions:

Exercises 365

1. flip (Leaf a)

-> (Leaf a)

2.flip (Branch x y) -> Branch (flip x) (flip y)

3.upto m n -> if (m > n) then Nil else (Cons m (upto m+1 n))

4.

sum Nil

->

0

5.

sum (Cons x xs) -> x + (sum xs)

6.

square Nil

 

-> Nil

7.

square (Cons x xs) -> Cons (x*x) (square xs)

8.

map f Nil

-> Nil

9.

map f (Cons x xs) -> Cons (f x) (map f xs)

2

Chapter 18

Guide to the Literature

This chapter gives an overview of the current literature on partial evaluation. Werst sketch the history from 1952 to 1984. Then we give an overview of the literature grouped by subject language (imperative, functional, logical), by the techniques used in partial evaluation (including binding-time analysis), and by applications. Finally, we mention some related topics.

The bibliography le is available for anonymous ftp from ftp.diku.dk as le pub/diku/dists/jones-book/partial-eval.bib.Z. See page 123 of this book.

18.1A brief historical overview

18.1.1The classics

Kleene's s-m-n theorem (1952) essentially asserts the feasibility of partial evaluation [149]. Kleene proved that for any given program (Turing machine) for a general m + n-argument function f, and given values a1; . . . ; am of the rst m arguments, there exists a program (a Turing machine) for the specialized function g = fa1;...;am which satis es g(b1; . . . ; bn ) = f(a1; . . . ; am; b1; . . . ; bn) for all b1; . . . ; bn. Moreover, there is a program (a Turing machine) which e ectively constructs the specialized program from the general one and the inputs. Thus Kleene's constructive proof provides the design for a partial evaluator.

However, his design did not, and was not intended to, provide any improvement of the specialized program. Such improvement, by symbolic reductions or similar, has been the goal in all subsequent work in partial evaluation.

Lombardi (Italy and the USA, 1964) is probably the rst use to the term `partial evaluation', when discussing the use of Lisp for incremental computation, or computation with incomplete information [175,176]. Landin (UK) also mentions partial evaluation, but does not de ne it, in a discussion on evaluation of lambda calculus expressions [161, p. 318].

366

A brief historical overview 367

Futamura (Japan, 1971) is the rst researcher to consider a partial evaluator as a program as well as a transformer, and thus to consider the application of the partial evaluator to itself [92]. Futamura's paper gives the equations for compilation and compiler generation (single self-application) using partial evaluation, but not that for compiler generator generation (double self-application). The three equations were called Futamura projections by Andrei Ershov [80]. Futamura's early ideas were not implemented.

Around 1975, Beckman, Haraldsson, Oskarsson, and Sandewall (Sweden) developed a partial evaluator called Redfun for a substantial subset of Lisp, and described the possibilities for compilation, compiler generation, and compiler generator generation by single and double self-application [19]. This is probably therst published description of the possibility of compiler generator generation by double self-application.

Turchin and his group in Moscow (USSR) also discovered the idea of partial evaluation in the early 1970s, while working with symbolic computation in the functional language Refal. A description of self-application and double self-application is found in [263] (in Russian). The history of that work is brie y summarized in English in [264].

Andrei Ershov in Novosibirsk (USSR) worked with imperative languages also, and used the term mixed computation to mean roughly the same as partial evaluation [76,77]. Ershov gave two comprehensive surveys of the activities in the eld of partial evaluation and mixed computation, including overviews of the literature up until that time [78,79]. Futamura gave another overview of the literature [93].

18.1.2Renewed interest

However, until 1984 neither single nor double self-application had been carried out in practice. At that time Jones, Sestoft, and S ndergaard (Denmark) constructed the rst self-applicable partial evaluator. It was written in a language of rstorder recursion equations (or rst-order statically scoped pure Lisp), and was used to generate toy compilers and compiler generators [135,136,245].

At the same time the interest in partial evaluation in logic programming and other areas was increasing. This was the background for the rst Workshop on Partial Evaluation and Mixed Computation (PEMC) held in October 1987 in Denmark. The workshop was organized by Dines Bj rner (Denmark), Andrei P. Ershov (USSR), and Neil D. Jones (Denmark), and was the rst to bring together a substantial number of partial evaluation researchers from all over the world.

The papers from the workshop have been published in a book [24] and in a special issue of the journal New Generation Computing [84]. Both contain Andrei Ershov's personal account of the history of mixed computation and partial evaluation [82, 83], and a bibliography of all known papers on partial evaluation [248,249]. The bibliography includes a substantial number of papers published in Russian and largely unknown to western researchers.

368 Guide to the Literature

An ACM Sigplan Symposium on Partial Evaluation and Semantics-Based Program Manipulation (PEPM) was held June 1991 in the USA and was organized by Charles Consel (USA) and Olivier Danvy (USA) [1]. An ACM Sigplan Workshop on the same theme was held June 1992 in the USA [2], and another ACM Sigplan PEPM Symposium was held June 1993 in Denmark.

18.2Partial evaluation literature by subject language

18.2.1Imperative languages

Ershov and his group worked primarily on imperative languages [76,78,79]. In 1985 Bulyonkov and Ershov constructed their rst self-applicable partial evaluator (for a ow chart language), reported in [42].

Gomard and Jones reported a self-applicable partial evaluator for a ow chart language in [103].

Jacobsen constructed a partial evaluator for a small subset of C [126]. Meyer studies procedure specialization in an imperative language [182,183]. Nirkhe and Pugh report a similar partial evaluator, but it cannot produce recursive residual procedures [205]. Andersen's partial evaluator for a C subset handles procedures as well as pointers and arrays, and is self-applicable [7,8,9]. See Chapter 11 of this book.

18.2.2Functional languages

Beckman, Haraldsson, Oskarsson, and Sandewall constructed the rst major partial evaluator, called Redfun for a substantial subset of Lisp [19,112,113].

Later partial evaluators for Lisp and Scheme have been reported by Kahn [143], Schooler [243], and Guzowski [108]. Weise et al. constructed a fully automatic online partial evaluator for a subset of Scheme [281].

Jones, Sestoft, and S ndergaard constructed the rst self-applicable partial evaluator for rst-order recursion equations. It was called mix, following Ershov's terminology. The rst version required user supplied annotations [135,245], but a later version was fully automatic [136,246]. Sergei Romanenko improved on that work in various respects [227]. Chapter 5 of this book presents a self-applicable partial evaluator for a rst-order functional language.

Consel constructed a self-applicable partial evaluator called Schism for a userextensible rst-order Scheme subset, handling partially static structures and polyvariant binding times [51,52,60]. Later he extended it to handle higher-order functions also [53].

Bondorf and Danvy constructed a self-applicable partial evaluator Similix for a user-extensible rst-order subset of Scheme [32]. Subsequently Bondorf constructed

Partial evaluation literature by subject language 369

a series of extensions, which handle a higher-order subset of Scheme, including restricted side e ects [28,27,29]. See Chapter 10 of this book.

Recently, Sergei Romanenko has developed a Similix-like system, called Semilux, for use on personal computers.

Gomard and Jones described a self-applicable partial evaluator for an applied untyped call-by-value lambda calculus [102,106]. See Chapter 8 of this book.

Mogensen developed a self-applicable partial evaluator for the pure untyped lambda calculus, not restricted to call-by-value reduction [191]. Bondorf, Danvy, Gomard, Jones, and Mogensen gave a joint discussion on self-applicable partial evaluation of higher-order languages [133].

18.2.3Refal, supercompilation, and term-rewriting systems

Turchin's Refal language is designed for symbolic manipulation of programs, and a Refal program is a kind of term-rewriting system. By the operation of driving, a Refal program with partial inputs can be unfolded to a graph of con gurations (terms) and transitions, where the transitions are marked with assignments (substitutions) and contractions (matchings). The purpose of supercompilation is to control the driving process so that the resulting graph is nite. This is done by selecting a nite set of basic con gurations, and generalizing all con gurations to match on the basic con gurations [264,267,268,269,270]. Thus supercompilation and generalization in Refal are strongly related to partial evaluation, and supercompilation has been used to specialize and transform algorithms by e.g. Gl•uck and Turchin [100]. See Section 17.4 of this book.

Bondorf constructed a partial evaluator for term-rewriting systems using methods more similar to those previously used for functional languages [26].

18.2.4Prolog and logic programming languages

Komorowski pioneered partial evaluation of Prolog [151,152]. Venken showed that a partial evaluator can be developed from a Prolog meta-interpreter [272,273],

This approach has been taken in much of the subsequent work on partial evaluation of various subsets of Prolog. This includes Takeuchi and Furukawa [260], Fujita [88], Fuller [89,90,91], Gallagher [95,96], Kursawe [157], Chan and Wallace [44], Lakhotia and Sterling [159], Bugliesi, Rossi, Lamma, and Mello [38,39], Bossi, Cocco, and Dulli [35], and Benkerimi and Lloyd [20].

Sahlin constructed a practical (but not self-applicable) partial evaluator for full Prolog [235,236].

A report by Lam and Kusalik compares ve partial evaluators for pure Prolog, constructed by Fujita, Kursawe, Lakhotia, Levi and Sardu, and Takeuchi [160].

Komorowski suggested the term partial deduction for partial evaluation of pure logic programming languages [153]. Lloyd and Shepherdson gave a formal de nition

370 Guide to the Literature

for the declarative as well as procedural semantics [174]. Sahlin suggests the term partial evaluation for the processing of Prolog programs, which may have nonlogical features and side e ects, and gives a de nition [236].

Bondorf, Frauendorf, and Richter described the rst automatic self-applicable partial evaluator, including a binding-time analysis, for a Prolog subset [33]. Bondorf and Mogensen subsequently constructed a stronger self-applicable partial evaluator for a more substantial subset of Prolog, but without an automatic bindingtime analysis [34]. See Chapter 9 of this book.

18.2.5Object oriented languages

Steensgaard and Marquard constructed a partial evaluator for an object oriented imperative language [178].

Khoo and Sundaresh used Consel's partial evaluator Schism to compile inheritance in an object oriented language, thus eliminating all method lookups [148].

18.3Principles and techniques

18.3.1Polyvariant specialization

A specialization technique in which several program points in the specialized program may correspond to one program point in the original program is called polyvariant specialization. The term is due to Itkin [125] according to Bulyonkov [40,41]. Polyvariant specialization is discussed also by Ershov [81] and Jones [130]. It is usually implemented sequentially by computing the set of specialized program points reachable from the initial one. Consel and Danvy show how this can be done on a parallel architecture with shared memory [55].

18.3.2Binding-time analysis

The rst binding-time analysis in partial evaluation (for a rst-order language with atomic binding-time values) was developed in 1984 by Jones, Sestoft, and S ndergaard [135] and is described by Sestoft in [245]. Section 5.2 of this book presents essentially that binding-time analysis.

Mogensen devised methods for binding-time analysis of partially static data structures [187] and for polymorphically typed higher-order languages [188,189].

Launchbury studied binding-time analysis (for a rst-order language) for data structures using domain projections, which gives a very natural formalism for partially static structures [164,165,166,167,168]. See Section 15.4 of this book.

Hunt and Sands formalize binding-time analysis for partially static data struc-