![](/user_photo/_userpic.png)
Jones N.D.Partial evaluation and automatic program generation.1999
.pdfPrinciples and techniques 371
tures and higher-order functions, using partial equivalence relations (`pers') instead of projections [122].
Bondorf used a closure analysis to construct a binding-time analysis for (higherorder dynamically typed) Scheme in the Similix partial evaluator [28]. Consel describes a binding-time analysis for partially static structures in (higher-order dynamically typed pure) Scheme, which does not require a separate closure analysis [53]. Rytz and Gengler describe a polyvariant binding-time analysis for Similix [233].
Type systems for binding-time analysis of variants of the lambda calculus are described by Nielson and Nielson [199,202] and by Schmidt [242]. Gomard gives a simpler type system and an inference algorithm for binding-time analysis of the lambda calculus [102,104]. Andersen and Mossin investigated the (somewhat complicated) extension of Gomard's type system needed for Bondorf's Similix [12]. Henglein gave an e cient inference algorithm for Gomard's type system [114].
18.3.3Automatic arity raising
Arity raising is the replacement of one variable by several variables, each holding a certain component of the original variable. Handmade annotations for arity raising were suggested in [245] where this process was called variable splitting.
Automatic arity raising was achieved in Mogensen's work on partially static structures [187].
Sergei Romanenko coined the term `arity raising' [227], and described analyses and a transformation for arity raising in a rst-order functional language in [228]. Steensgaard and Marquard extended Romanenko's method to a higher-order functional language using a closure analysis [253].
18.3.4Call unfolding
Most papers on automatic partial evaluators discuss how to control unfolding of calls during partial evaluation. Sestoft gave a discussion of call unfolding in a functional setting [246], and Bondorf and Danvy improved on this [32, Section 5]. Fuller [89], Lakhotia [158], and Bruynooghe, de Schreye and Martens [37] discuss how to avoid in nite unfolding during partial evaluation of logic programs.
18.3.5Binding-time improvements
Various techniques for binding-time improvement are discussed by Nielson and Nielson [203,204], Holst and Hughes [119], Holst and Gomard [118], and J rgensen [139].
Consel and Danvy show that transforming the subject program to continuation
372 Guide to the Literature
passing style (before partial evaluation) gives a better separation between static and dynamic data (during partial evaluation) [56]. Subsequent work by Bondorf on the Similix partial evaluator achieves some of the same e ect without a preceding transformation of the subject program [31]. See Section 10.5 of this book.
18.3.6E ectiveness of partial evaluation
Nielson suggests ranking partial evaluators by the `reducedness' of their residual programs [199]. Hansen gives another framework for reasoning about e ciency improvements [111], and Malmkj r shows how to predict the form of residual programs [177]. Andersen and Gomard give a simple way to estimate the speed-up achieved by partial evaluation [11]. See Chapter 6 of this book.
18.3.7Online and o ine partial evaluation
Most of the self-applicable partial evaluators mentioned above use o ine techniques, but Gl•uck shows that it is possible to construct a self-applicable partial evaluator which does not need binding-time analysis [98, Section 3].
In contrast, online techniques have been favoured when e ciency, simplicity, or maximal use of static data were considered more important than self-application.
Several reports and papers by Ruf and Weise [230,231,232], and Ruf's thesis [229], discuss the merits of online and o ine partial evaluation. See also Chapter 7 of this book.
18.4Applications
18.4.1Parsers and pattern matching
Dybkj r used the partial evaluator `mix' to specialize Earley's general context-free parser [72] to given context-free grammars [70].
Ershov and Ostrovski use partial evaluation to specialize (semi-automatically) a general parser to speci c parsers for real programming languages [85,207]. Pagan studies the e ect of (hand-) specializing parsers in [212].
Emanuelson specializes a general pattern-matching algorithm to an e cient one for a given pattern [73,74]. Danvy [66] and J rgensen [138] independently obtained very e cient matching of alternative patterns, by partial evaluation of a general pattern matcher.
Consel and Danvy derive the e cient Knuth{Morris{Pratt pattern matching algorithm from a naive one, using partial evaluation [54]. Smith extends that work to matching in other domains [252].
374 Guide to the Literature
18.4.6Program specialization in scienti c computation
Early examples of work in automatic program specialization are provided by Gustavson et al. [107] and Goad [101]. These authors do not associate themselves with the partial evaluation paradigm, however.
Specializing general scienti c computation algorithms by partial evaluation may give substantial savings. This has been used by Mogensen for ray tracing [186], by Berlin and Weise for calculation of planet trajectories [21,22], and by Jacobsen for neural network training [126].
18.5Other topics related to partial evaluation
18.5.1Program transformation
Program transformation by hand, or symbolic evaluation using rewrite systems, has been used in program optimization or improvement for many years. In contrast to these methods, partial evaluation involves an automatic strategy for applying the transformations.
McCarthy is probably the rst to give program transformation rules, in the form of provable equivalences between program phrases [180].
Boyer and Moore work the other way round: they prove program equivalences essentially by using valid transformations or `partial evaluations' (such as unfolding and simpli cation) [36].
Burstall and Darlington classify transformation rules as de nition, instantiation, unfolding, folding, abstraction, and laws (such as associativity) [43].
Bird's hand derivation of the Knuth{Morris{Pratt pattern matching algorithm from a naive one is a precursor to the derivations using partial evaluation [23].
Using another set of transformation rules, Scherlis improve programs by specialization and by elimination of unneeded computations [238,239]. An as example, Scherlis systematically develops Earley's parsing algorithm from the `derives' relation of context-free grammars.
Wand [276] and Wegbreit [280] discuss methods to make program transformation more automatic and systematic.
18.5.2Compilation by program transformation
The rst hand derivation of a compiler from an interpreter is probably that given by F. Lockwood Morris [193]. Such derivations have been studied also by Pagan [209,210,213], Wand [277,278], and Mazaher and Berry [179]. Pagan suggested using compiler derivation as a teaching aid in compiler courses [211].
Other topics related to partial evaluation 375
18.5.3Parser generator systems
There are several systems for automatic generation of parsers from grammars. What happens in these generators could be seen as the specialization of a general parser to a given concrete grammar, but this is usually not the way their authors view them. Johnson's Yacc (for `Yet Another Compiler-Compiler') is probably the most well-known LALR(1) parser generator [3,128] and [4, Section 4.9].
Parser generator systems leave it to the user to design and code the `semantic actions' which are executed at certain points during parsing. The semantic actions take care of symbol table manipulation, code generation, and similar compile time tasks. Compiler generation from attribute grammars presents a step towards automatic generation of semantic actions. Kastens and his group constructed a system for generating specialized parsers and attribute evaluators from attribute grammars [146].
18.5.4Compiler generation systems
Further integration of parsing and semantic actions leads to true compiler generators, where the user speci es the semantics of a programming language in some de nition language. The compiler generator constructs a parser and semantic actions from the speci cation without assistance from the user. This comes closer to the kind of compiler generation done with partial evaluation. A collection of papers on semantics-directed compiler generation is found in [129].
Several systems for compiler generation from various kinds of language speci - cations have been proposed. Mosses constructed a system called `Semantics Implementation System' or `SIS', for turning denotational semantics speci cations into compilers [194,195]. A program is compiled by building the symbolic composition of the language de nition (in the style of denotational semantics) and the source program, then reducing this to a lambda term, which is the target program. The compiled programs are, however, very slow.
Gaudel's system Perluette takes care mainly of the syntactic aspects of compilation [97]. Jones and Schmidt's compiler generator is (like Mosses's SIS) based on automatic symbolic composition. A de nition (in terms of the lambda calculus) of a speci c language is composed with a general compilation of the lambda calculus into machine code; the result is then simpli ed by symbolic reductions [134,240].
Christiansen and Jones constructed a system called CERES which further developed the idea of symbolic composition of language de nitions [46]. Tofte noted that compiler generation could be seen as a special case of compilation, and made CERES self-generating [262].
Paulson's system can compile and execute languages de ned by a so-called semantic grammar, an attribute grammar which speci es the static and dynamic aspects of the language [214,215]. More recent work includes that of Pleban [218] and Lee [170,171].
![](/html/616/253/html_BZCYT7bojI.Aa00/htmlconvd-M9dQqO386x1.jpg)
Using the Scheme0 specializer 377
The e ect and use of the main functions de ned by loading scheme0.ss and doing (create) is described below. The precise syntax of plain and annotated Scheme0 programs is described in Section A.2.
(monodiv program sdpattern) Does a monovariant binding time analysis of the subject program program, given the binding times sdpattern of its goal function. Returns the resulting (monovariant) division.
(polydiv program sdpattern) Similar to monodiv, but does a polyvariant binding time analysis. Returns the resulting (polyvariant) division.
(annotate program division) Annotates the Scheme0 program according to the (monovariant or polyvariant) division. May make several copies of each function for a polyvariant division. Returns the annotated (two-level) Scheme0 program or reports that division is not congruent.
Does monovariant binding time analysis and annotation of program given the binding times sdpattern of its goal function. Returns the annotated (two-level) Scheme0 program.
(polytate program sdpattern) Similar to monotate but performs polyvariant binding time analysis and annotation. Returns the annotated (two-level) Scheme0 program.
(spec annprogram staticinputs) Specializes the annotated annprogram with of static argument values. Returns the
(monope program sdpattern staticinputs) Does a monovariant binding time analysis and annotation of program, then specializes it with respect to the given static inputs staticinputs. Returns the residual Scheme0 program.
(polype program sdpattern staticinputs) Similar to monope but does polyvariant binding time analysis and annotation. Returns the residual Scheme0 program.
Converts program from Scheme0 to Scheme and de nes a Scheme function f to invoke the converted program. Returns the name f. Side e ect: De nes function f. This is for executing (residual) Scheme0 programs.
(scheme program) Converts program from Scheme0 to Scheme, possibly renaming functions. Returns a list of Scheme function de nitions. This is for studying residual Scheme0 programs.
onlineunfolding Global Boolean variable. If #t, then annotate gives on the y call unfolding as shown in Section 5.5; if #f, then only calls without dynamic arguments will be unfolded. The default value is #t.
378 The Self-Applicable Scheme0 Specializer
A.1.3 Hints, and requirements on the input
The goal function must never be called (by another function in the program) with other binding time patterns than that speci ed at binding time analysis. This can be relaxed for monovariant analysis: it must not be called with binding times which are not smaller than the given one. For polyvariant analysis: it must not be called with binding times which are smaller than the given one.
Note that all residual programs take exactly one input: the list of dynamic argument values.
A.2 Data structures in the Scheme0 specializer
A.2.1 Representation of Scheme0 programs
The syntax closely follows that given in Figure 5.1, so a program must have at least one de nition, and function de nitions may have zero or more arguments.
program ::= (def ... def)
def |
::= (define (funcname var ... var) exp) |
exp |
::= () |
|
| number |
|
| var |
|
| (quote S-expression) |
|
| (if exp exp exp) |
|
| (call funcname exp ... exp) |
|
| (op basefunction exp ... exp) |
Variables are Scheme symbols, that is, non-numeric atoms di erent from (). Function names may also be simple Scheme symbols, but in residual programs, a function name is a pair (annotatedname . staticvalues) of an annotated function name and the values of the function's static arguments for this variant. Annotated function names are those found in the annotated subject programs given to the specializer (see below).
A.2.2 Representation of two-level Scheme0 programs
The syntax of annotated (or, two-level) Scheme0 programs is very close to that given in Figure 5.5.
|
Data structures in the Scheme0 specializer 379 |
program ::= (def ... def) |
|
def |
::= (define (funcname (var ... var) (var ... var)) exp) |
exp |
::= () |
| number | var
| (quote S-expression) | (ifs exp exp exp)
| (ifd exp exp exp)
| (calls funcname (exp ... exp) (exp ... exp)) | (calld funcname (exp ... exp) (exp ... exp)) | (ops basefunction exp ... exp)
| (opd basefunction exp ... exp) | (lift exp)
A variable is a Scheme symbol as above, but a function name must have the form:
((f . dynvaroccs) . sdpattern)
Here f is a function name from the Scheme0 subject program; dynvaroccs is a list of the number of occurrences of f's dynamic parameters; and sdpattern describes the binding times of the parameters of (this variant of) f. Thus f is a Scheme symbol; dynvaroccs is a list of 0, 1, or 2, where 2 represents any number greater than 1; and sdpattern is a list of S and D.
The sdpattern component is used to distinguish the binding time variants of f (and is present also in monovariantly annotated programs). The dynvaroccs component is used to avoid unfolding static calls to f when this may cause duplication of a non-trivial (non-variable) dynamic argument expression. This component could in principle be computed during specialization (in which case it need not be part of the function name), but this would incur unnecessary recomputation, so we choose to compute it when annotating the program.
A.2.3 Representation of divisions
A division is a list ((fun1 . sdpatterns1) . . . (funn . . . sdpatternsn)), associating with each function a list of sdpatterns. Each list of sdpatterns is sorted in non-decreasing order (the ordering is partial).
This format caters for polyvariant as well as monovariant divisions: in a monovariant division the list sdpatterns has just one element. The list may also be empty if the corresponding function is never called.
![](/html/616/253/html_BZCYT7bojI.Aa00/htmlconvd-M9dQqO390x1.jpg)
380 The Self-Applicable Scheme0 Specializer
A.3 The components of the Scheme0 specializer
The le scheme0.ss de nes syntactic shorthands and the main functions used for experiments.
;File "scheme0.ss" -- Global definitions and syntactic shorthands
;Partial evaluator for first order functional language
;Syntactic sugar for Scheme0. Format: list of triples (abbrev var term),
;meaning: expand (abbrev e) into term with e substituted for var .
(define sugars '(
;Select from e == (tag e1 e2 e3 ...) (tag e (hd e))
(e1 e (hd (tl e)))
(e2 e (hd (tl (tl e))))
(e3 e (hd (tl (tl (tl e)))))
;Select from e == (call funname sfunargs dfunargs)
(funname e (hd (tl e))) (sfunargs e (hd (tl (tl e)))) (dfunargs e (hd (tl (tl (tl e)))))
;Select from e == (call/op funname . callargs) (callargs e (tl (tl e)))
;Select from def == (define (name svar dvar) body)
(name def (hd (hd (tl def)))) (svar def (hd (tl (hd (tl def)))))
(dvar def (hd (tl (tl (hd (tl def)))))) (body def (hd (tl (tl def))))
;Select from def == (define (name . var) body) (var def (tl (hd (tl def))))
;Introducing Scheme0 base operation shorthands
(hd e (op car e)) (tl e (op cdr e))
))
; Desugar: From sugared Scheme0 to plain Scheme0, using the "sugars" table
(define (desugar program) (define (desug e sub)
(if (or (null? e) (number? e)) e
(if (atom? e) (let ((var-term (assoc e sub))) (if var-term (cdr var-term) e))
(if (equal? (car e) |
'quote) e |
|
(if (equal? (car e) 'if) |
|
|
(cons 'if (desug* (cdr e) sub)) |
|
|
(if (member (car e) '(call op)) |
|
|
(cons (car e) (cons (funname e) (desug* (callargs e) sub))) |
||
(if (equal? (car e) '::) |
|
|
(list 'op 'cons (desug (e1 e) sub) (desug (e2 e) sub)) |
||
(if (equal? (car e) 'list) |
; (list e1 e2 ... en) |
|
(foldr (lambda (e lst) (list 'op 'cons (desug e sub) lst)) |
||
() (cdr e)) |
|
|
(if (equal? (car e) 'slet) |
; (slet (var exp) body) |
|
(desug (caddr e) |
|
(cons (cons (caadr e) (desug (cadadr e) sub)) sub)) ; else it must be an abbreviation
(let ((expansion (assoc (car e) sugars))) (if expansion
(let ((var (cadr expansion)) (term (caddr expansion)))
(desug (desug term (cons (cons var (cadr e)) sub)) sub)) (error 'desugar "Unknown operator or macro: ~s" (car e))