A Syntactic Approach to Program Transformations
Zena M. Ariola
Aiken Computational Laboratory
Harvard University
Abstract: Kid, a language for expressing compiler opti-
mizations for functional languages is introduced. The language is -calculus based but treats let-blocks as rst class
objects. Let-blocks and associated rewrite rules provide the
basis to capture the sharing of subexpressions precisely. The
language goes beyond -calculus by including I-structures
which are essential to express ecient translations of list and
array comprehensions. A calculus and a parallel interpreter
for Kid are developed. Many commonly known program
transformations are also presented. A partial evaluator for
Kid is developed and a notion of correctness of Kid transformations based on the syntactic structure of terms and
printable answers is presented.
Keywords and phrases: -calculus, Term Rewriting Systems, Contextual Rewriting Systems, Con uence, Optimizations, Correctness, Intermediate Language, Non-strictness.
1 Introduction
For a number of years at MIT we have been working with
an implicit parallel language called Id. Id is a high level
functional language [16] augmented with a novel data structuring facility known as I-structures [5]. Id, like most modern functional languages, is non-strict, and has higher-order
functions and a Milner-style type system. It also has a
fairly large syntax to express currying, loops, list and array comprehensions, and pattern matching for all algebraic
types. We have found it dicult to give direct operational
semantics of Id because of its syntactic complexities. Kid,
a kernel language for Id, has been developed to give precise
(though indirect) operational semantics of Id. Kid has also
proved to be an extremely useful intermediate language for
the Id compiler [3]. Within the compiler, all machine in-
Arvind
Laboratory for Computer Science
Massachusetts Institute of Technology
dependent optimizations are expressed as source-to-source
program transformations in Kid. Kid is a much more rened version of the language P-TAC presented earlier by
the authors [2].
Kid's operational semantics and associated calculus are
innovative, and given in terms of a Contextual Rewriting
System (CRS) [4]. Since Kid, relative to the -calculus, is a
large language, we introduce it in steps, introducing a new
feature at each step. No prior knowledge of Id or CRS's is
assumed on the part of the reader. However, we assume a
good understanding of non-strict functional languages and
some knowledge of Term Rewriting Systems (TRS).
Experience of many researchers has shown that the calculus with constants is not adequate as an intermediate
language for compilers of functional languages. One of its
primary de ciency is the inability to capture the sharing
of subexpressions. Avoiding repeated evaluation of an expression is a central concern in implementations of non-strict
functional languages. Not surprisingly, graph reduction [21],
[19], [13], has been one of the popular ways of implementing functional languages, but it is only recently that people
have investigated suitable calculii for graph rewriting [7],
[8], and [9]. Contextual Rewriting Systems, which we introduce in Section 2, represent another formalism to capture
the sharing of subexpressions. The idea of having a calculus that re ects what happens operationally in a sequential
implementation goes back to the work of Plotkin [18].
The core of Kid consists of the -calculus with constants
and letrec or block expressions (Section 3). A block in Kid
is not treated as syntactic sugar for applications; it is central to expressing the sharing of subexpressions. Kid also
embodies the novel idea of multiple values (Section 4). In
Section 5, we enrich our language by introducing I-structures
[5], which takes us beyond the realm of pure functional languages. Kid without loops is presented in Section 6, where
we also introduce the notion of printable value and answer
associated with a Kid term. Informally, the answer is the
maximal information that a term can produce and plays a
crucial role in de ning the correctness of optimizations. We
will consider an optimization to be correct if for any two
programs M and N , where N is the optimized version of
M , the answer of N is the same or more de ned than the
answer of M . Our approach shows that term models are
good enough to formulate many interesting questions about
program equivalences.
In Section 7 a parallel interpreter for Kid is developed.
In Section 8 optimizations for Kid programs are introduced
and a partial evaluator for Kid is presented. Finally loops are
introduced in Kid in Section 9 and our thoughts on future
work are given in Section 10.
2 Contextual Rewriting Systems (CRS)
Consider the term (S f g (K x y)) from combinatory logic.
It reduces to ((f x) (g x)) using the S-rule and K-rule. The
redex (K x y) will be evaluated once or twice depending upon
whether graph or string reduction is used. A CRS prohibits
this duplication of work by assigning a unique name to each
subterm and by allowing the substitution only when the
subterm becomes a value.
2.1 Syntax of CRS Terms
A natural way to represent a graph textually is to associate
an identi er to each node, and then write all the interconnections as a recursive let-block. The terms of a CRS with
signature F are described by the grammar of Figure 1 where
Term is the start symbol. Superscript on a function symbol indicates its arity, and the constants are assumed to be
function symbols of arity 0. The textual order of bindings in
a block does not matter, that is, bi ; bj bj ; bi . A binding
in a CRS block may be viewed as an instruction because
its right-hand-side (rhs) consists of an operator followed by
variables and constants. When functions have arity 2, this
format recalls the three-address-code used as an intermediate language by conventional compilers [1].
2.2 A CRS for Combinatory Logic
The signature F for combinatory logic contains two constants S and K, and one function symbol Apply of arity two.
S and K rules for combinatory logic which are normally written as
Apply(Apply(Apply(S;X );Y );Z ) ??!
Apply(Apply(X;Z ); Apply(Y; Z ))
Apply(Apply(K;X );Y ) ?! X
2
2
Fi
2
Constant 2
SE
E
Simple Expression
Expression
F
F
::= V ariable j Constant
::= SE
j Fn (SE1 ; ;SEn )
j Block
Block
::= f [Binding; ] In SE g
Binding ::= V ariable = E
Term
::= Block
SE
E
Figure 1: Syntax of terms of a CRS with signature F
will be written as follows in the CRS notation:
T1 = Apply (S; X ) j
T2 = Apply (T1 ; Y )
Apply (T2; Z ) ??! f t1 = Apply (X; Z );
t2 = Apply (Y; Z );
t = Apply (t1 ; t2 );
In tg
T = Apply (K; X )
Apply (T; Y ) ??! X
The separator (j) between the two bindings in the precondition of the S-rule is used to indicate that the textual order is irrelevant. All the variables that appear on the lefthand-side (lhs) of the rules or in the preconditions are metavariables that range over appropriate syntactic categories.
By convention, we use capital letters for meta-variables and
small letters for CRS variables. All variables that appear on
the rhs of the rules are either meta-variables or \new" CRS
variables, which will be usually represented by ti . Throughout the paper we will make use of the following convention
regarding meta-variables:
Xi ; Zi ; Yi ; Fi 2 V ariable and Constant
C
2 Constant
Si ; SSi ; S 0
2 Statement
Ei
2 Expression
where Statement is a generalization of Binding, as will be
discussed later.
Consider the CRS reduction of the term (S f g (K x y))
discussed earlier. As shown below, the CRS version of the
S-rule guarantees that (K x y) will be evaluated only once,
regardless of the reduction strategy.
Apply (S; f );
f ww21 == Apply
(w1 ; g);
w3 = Apply (K; x);
w4 = Apply (w3 ; y);
w5 = Apply
({z
w2 ; w4 );}
|
In w5 g
In the above term the two bindings in the box match the
precondition of the S-rule, and the subterm () matches the
lhs of the S-rule. We call a CRS redex. The matching gives
rise to the following substitution for the meta-variables of
the S-rule:
T1 = w1 ; T2 = w2 ; X = f ; Y = g; Z = w4
Thus, the above term rewrites to:
f w1 = Apply (S; f );
w2 = Apply (w1 ; g);
w3 = Apply (K; x);
w4 = Apply (w3 ; y);
w5 = f t01 = Apply (f; w4 );
t02 = Apply (g; w4 );
t0 = Apply (t01 ; t02 );
In t0 g
In w5 g
Notice that each application of the rule introduces fresh
copies of the bound variables of the rhs of the rule.
2.3 Basic Rules of Contextual Rewriting System's (RCRS )
All CRS's share the following two rules, named RCRS .
De nition 2.2 (Canonical form) Let N be the normal form
of a term M with respect to RCRS . M , the canonical form
of M, is the term obtained by deleting from N all bindings
of the form x = y (where x and y are distinct variables) or
x = c ( where c is a constant).
De nition 2.3 ( -equivalence) Two terms M and N are
said to be -equivalent, if M and N can be transformed into
each other by a consistent renaming of bound variables.
Intuitively the canonical form of a term corresponds to
the graph associated to the term. The reader may refer to
[4] for a more rigorous de nition of term graphs and alphaequivalence as rooted graph isomorphism.
Lemma 2.4 Each term has a unique canonical form.
3 B -calculus
The same term sharing ideas can be applied to the -calculus;
for which we introduce the B -calculus, given in Figure 2.
SE
E
Substitution rules
X=Y
X ?! Y
X=C
X ?! C
where X and Y stand for distinct variables, and metavariable C stands for a constant.
Block Flattening rule
fX
f SS1 ; SS2 ;
fX = Y ;
In Y g ??!
SS1 ; SS2 ;
S1 ; Sn
S1 ; Sn
In Z g
In Z g
=
Lemma 2.1 RCRS is Strongly Normalizing and Con uent.
Proof: See [2].
2.4 Canonical Forms of terms in a CRS
The following two terms have apparently di erent syntactic
structure.
fx = 8; t0 = fy = x; t = x + y In tg In t0 g and
fx = 8; y = x; t = x + y In tg
However, we consider the di erence between the above two
terms merely \syntactic noise". Eliminating this syntactic
noise is the motivation behind the following de nitions.
Block
Binding
Term
::=
::=
j
j
j
j
::=
::=
::=
V ariable j Constant
SE
F n (SE1 ; ; SEn )
Block
V ariable : E
Ap (SE1 ;SE2 )
f [Binding; ] In SE g
V ariable = E
Block
Figure 2: Syntax of terms of B -calculus
The rule in the B -calculus is expressed as follows:
F =Z :E
Ap (F; X ) ??! (RB[[E ]]) [X=Z ]
where the notation E [X=Z ] means the substitution of X
for Z in E , and the RB function is needed to rename the
bound variables of E. After the discussion of function RB,
given below, it will become clear that E [X=Z ] stands for
naive substitution, that is, substitution where no danger of
free-variable-capture exists.
In the -calculus one has to deal with the problem of
free-variable capture. The problem is usually solved either
by making some variable convention, as for example, assuming that all free variables are di erent from the bound variables, or by adopting a variable-free notation such as that of
DeBruijn [11]. According to the variable convention given in
[6], the term (x:xx)(x:xx) is a legal term, while the term
(y:(x: +(x;y))x is not a legal term, because the variable x
appears both free and bound. The convention allows one to
express application in terms of a naive form of substitution,
which does not require -renaming at execution time. In
a system with let-blocks, in order to avoid the free-variable
capture as well as to allow naive substitution, we have to
adopt an even more stringent convention: all bound variables in an expression must be unique and di erent from
variables free in the whole program. In order to maintain
this invariant we will occasionally rename all bound variables of a term to completely new variables explicitly, by
applying the function RB to the term. For example,
0
0
RB [ fx = + (a; 1) In xg] = fx = + (a; 1) In x g
At the implementation level the e ect of function RB is to
make a copy of the expression. The most important thing to
notice is that the -rule in the B -calculus allows variables
to be substitutable, which in turn may be bound to unevaluated expressions. Thus, sharing aspects of CRS's have not
ruled out non-strict implementations. However, the use of
the function RB automatically rules out \context sharing",
which is needed for optimal interpreters of the -calculus
[14].
The S combinator can be expressed as follows in the B calculus:
f:g:x:ft1 = Ap(f; x); t2 = Ap(g; x); t = Ap(t1 ; t2 ); In tg
Notice, however, that Ap(S; w) produces a -term while
Apply(S; w) in the SK-CRS is not reducible any further. This
discrepacy can be removed by extending the B -calculus
with multiple values.
Arity Detection rule
F
=
Z:E
Apply (F; X ) ??! Ap (F; X )
F
=
!
n Zn :E
Apply (F; X ) ??! Apply1 (F; n; X )
where n stands for numeral n.
!
Fi = Applyi (F; n; Xi )
n>1
!
i < (n ? 1)
Apply (Fi ; Xi+1 ) ??! Applyi+1 (F; n; Xi+1 )
!
Fi = Applyi (F; n; Xi )
i = (n ? 1)
!
Apply (Fi ; Xi+1 ) ??! Apn (F; Xn )
Thus, Apply (S; w) will produce Apply1 (S; 3; w) and not a
-term. Multiple variables play an important role in avoid-
In the above de nition 3 indicates that a multiple value of
arity 3 is expected as argument, and (f;g; x) is a multiple
variable of arity 3. The following rule captures the application in the presence of multiple values.
ing the construction of unnecessary closures. For example,
without multiple values, a term like (S e1 e2 e3 ) will involve
copying the body of S twice. While according to the multiple variable de nition of S the values of e1 , e2 , and e3 will
be substituted directly into a single copy of S.
Another important use of multiple values is in elimination of tuples which are used just to \package" several values. Consider the following binding
(x; y) = f a = ; b = ; In (a; b)g
where (a; b) on the rhs indicates a 2-tuple and (x; y) on the
lhs indicates destructuring via pattern matching. Such tuples are ubiquitous and put unnecessary demands on the
storage manager. It is possible to transform this binding
into a multiple value binding as follows:
x; y = f2 a = ; b = ; In a; bg
where \x; y" indicates multiple variables, and the 2 after
the curly brace indicates that two values are to be returned
by this block expression. Thus, a binding has the form
MVm = Em , where MVm stands for m multiple variables,
and the expression Em on the rhs must return m values.
We add the following basic destructuring rule to RCRS involving multiple values:
Application rule
Multivariable rule
4 Multiple Values
Consider the following de nition of S using multiple values:
3 (f; g; x):f t1 = Apply(f; x);
t2 = Apply(g; x);
t = Apply (t1; t2 );
In tg
!
!
F
=
!
n Zn : E
!
! !
Apn (F; Xn ) ??! (RB[[E ]]) [Xn = Zn ]
where X n stands for multiple variables (X1 ; ; Xn ), and
! !
E [Yn = Xn ] stands for E [Y1 =X1 ; ; Yn =Xn ], which is the
same as ( ((E [Y1 =X1 ]) [Y2 =X2 ]) ) [Yn =Xn ]). The following rules relate Apply to Apn using another function Applyi
which behaves like a closure.
! ??!
Xn = Yn
(X =
1
Y1 ; Xn = Yn )
Notice we need to generalize function Apn to be able to deal
with functions that return multiple values as follows:
F
!
=
!
n;m Zn : E
! !
Apn;m (F; Xn ) ??! (RB[[E ]]) [Xn = Zn ]
However, we insist that the general Apply function always
return one value.
5 I-structures
Another major short coming of -calculus, or any other
purely functional language, is the inability to express \efcient" translations of list and array comprehensions. The
usual translations [17] are unnecessarily sequential and elude
to other implementation tricks to avoid construction of intermediate data structures. We believe I-structures [5] are
essential to express ecient translation of many constructs
in a language such as Haskell [12] and Miranda [20].
I-structures are \write-once" structures and di er from
functional data structures in the sense that an I-structure
can be de ned incrementally. It is possible to de ne an array
by giving its bounds only, i.e., I-array(xb ). At some other
stage in the program, the de nition for an element of an
array, say the ith element of array x, can be given by writing
the store command P-store(x; i; z ). If two de nitions for
the same element are given then some subexpression in the
program may go into inconsistent state, represented by >.
These aspects of I-structures are captured by the following
rules.
Array rules
X = I array (Xb )
Bounds (X ) ??! Xb
P store (X; I ; Z )
P select (X; I ) ??! Z
P store (X; I ; Z )
P store (X; I ; Z 0 ) ?! >s
P-store and P-select do not require bounds checking; it is as-
sumed that code for bounds checking is inserted explicitly
during the translation from Id to Kid.
An important fact to realize is that a functional language
augmented with I-structures does not preserve \referential
transparency" [5]. For example:
f
x
=
I array(xb);
In (x; x)g
6 (I array( b) I array( b))
x
;
x
It is, therefore, essential to specify rules for sharing of
subexpressions to give the semantics of I-structures. CRS
have the necessary expressive power for this purpose.
We also need to specify how >s propagates through an
expression. If this propagation is not done carefully then the
addition of I-structures can destroy the con uence property
of the language. For example, the expression
f P store(
x; i; z1 );
P store(x; i; z2 );
w = P select(x; i);
In wg
has four possibilities for the next reduction. These are
fP store( 1 ); >s ; = P select(
f>s; P store( 2 ); = P select(
fP store( 1 ); P store( 2 );
fP store( 1 ); P store( 2 );
x; i; z
In wg
In wg
w = z1 ;
In wg
w = z2 ;
In wg
x; i);
w
x; i; z
x; i);
w
x; i; z
x; i; z
x; i; z
x; i; z
If we want to preserve con uence then we must guarantee that the eventual result is the same in all these cases.
The following rules for propagating > were motivated by
these concerns and will eventually produce > in the above
example.
Propagation of >
!
fm >s ; 1 ; n In m g
S
fm
X
=
??!
>
n In m g ??!
>
S
>;
S1 ;
Z
S
!
Z
These rules for propagating > were motivated by a discussion with Vinod Kathail. It should be noted that the >
propagation rule for a block implies that the rhs of all bindings in a block must be evaluated. This idea is in con ict
with lazy evaluation of a block. However, I-structures have
no impact on the non-strict semantics of a functional language.
The idea of I-structures is generalizable to all functional
data structures such as tuples and other algebraic types. As
an example, the rules for the I-structure version of lists, the
so-called open lists, are given below
List rules
Cons (X;
Y
)
??! f
t
= Open cons ();
Cons store 1 (t; X );
Cons store 2 (t; Y )
In tg
Cons store 1 (X; Y )
Cons 1 (X ) ?! Y
Cons store 2 (X; Y )
Cons 2 (X ) ?! Y
Cons store 1 (X; Y )
Cons store 1 (X; Y 0 ) ??! >s
Cons store 2 (X; Y )
Cons store 2 (X; Y 0 ) ??! >s
We brie y describe the use of I-structures in the translation of list comprehensions. A typical translation of a listcomprehension is given in terms of nested map-list operations followed by a list attening operation [17]. In Id, we
make use of \open lists" , a type of I-structure, to generate
a tail recursive program. The translation of the Id list comprehension f: e || x <- xs ; y <- ysg may be given as
follows:
PFim
and m outputs
Ap Em
2 Applicative Expression with m outputs
Case Em 2 Case Expression with m outputs
Lambda E 2 Lambda Expression
f h1 = Open cons();
hn = fFor x <? xs do
Next h1 = Case ys of
j Nil = h1
j y : yss = fh = Open cons();
h1 :Cons 2 = h:Cons 2;
In fFor y <? ys do
t = Open cons();
t:Cons 1 = e;
h:Cons 2 = t;
Next h = t;
Finally hgg;
Finally h1 g;
hn :Cons 2 = Nil;
In h1 :Cons 2g
V ariable
MV m
Constant
SE
SE m
PF 11
6.1
Figure 3: The Grammar of Kid
rules
+ (m; n)
??!
+(m; n)
..
.
Equal? (n; n) ??! True
Equal? (m; n) ??! False
Case rules
Kid Rewrite Rules
We now present a set of rewrite rules, RKid , which give an
intuitive understanding of how Kid terms may get evaluated. We assume that a primitive function is applied only
to arguments of appropriate types, i.e., the type checking
has been done statically. RKid , in addition to the rules given
below, contains RCRS , the Application, the Arity detection,
the Array, the List, and the Propagation rules, introduced in
the previous sections.
j Open cons j Cons 1 j Cons 2
::= Detuplem
::= + j j Apply j Cons j Make tuple2 j P select
::= Applyn-2 j Make tuplen
::= Apn;m (SEn+1 )
::= Bool casem(SE; Em ; Em )
j List casem(SE;Em ;Em )
Lambda E ::= n;m MVn:Em
E1
::= SE 1 j PF 11 (SE ) j PF 21 (SE2 ) j PFN1 (SEn )
j Lambda E j Ap E1 j Case E1 j Block1
Em
::= SE m j PF 1m (SE ) j Ap Em j Case Em j Blockm
Blockm ::= fm [Statement; ] In SEm g
Statement ::= Binding j Command
Binding ::= MVm = Em
Command ::= P store(SE3 ) j Cons store 1 (SE2 )
j Cons store 2 (SE2 ) j Store error j >s
Program ::= Block1
Kid : The Kernel Id Language
Kid is a CRS containing B -calculus, multiple values and
I-structures. Kid also contains functions WLoop and FLoop
to capture those aspects of tail recursion that are very important for optimizations. However, we postpone a discussion of loops until Section 9. The syntax of Kid is given in
Figure 3.
Note that the function symbol Apply appears as a PF 21 in
the grammar because we assume that all user-de ned procedures return only one result. We also use subscripted function symbols to express a family of functions. For example,
Make tuplen stands for Make tuple2 , Make tuple3 , etc. Subscripts in a function symbol do not necessarily represent the
number of values to be returned by the application of the
function. By convention, we drop the subscript when its
value is one. (Exception Apply1 6 Apply).
A procedure to translate Id into Kid is given in [3].
::= x j y j z j j a j b j j f j j x1 j
::= V ariable1 ; ; V ariablem
::= Integer j Boolean j Nil j Error j >
::= V ariable j Constant
::= SE1 ; ; SEm j SE;SEm?1 j
::= Negate j Not j Bounds j I array
PF 1m
PF 21
PFN1
Ap Em
Case Em
In the above program, an open list (signi ed by h in the
inner loop) is generated for each element of xs and then
these open lists are \glued" together in the outer loop.
6
2 Primitive Function with i arguments
Tuple rule
(if m 6= n)
Bool casem (True; E1 ; E2 ) ??! E1
Bool casem (False; E1 ; E2 ) ??! E2
??! E1
List casem (Nil; E1 ; E2 )
X = Open cons ()
List casem (X; E1 ; E2 ) ??! E2
!
X = Make tuplen (Xn )
!
Detuplen (X ) ??! Xn
Theorem 6.1 Kid is con uent upto -renaming on canon-
P
ical terms.
h
::= Integers Booleans Error \Function"
::= List PV List Nil
::= 2 Tuple PV PV 3 Tuple PV PV PV
::= n Array Tuple PV PV
::= Atoms List Tuple Array
j
h
j
j
j
i j
h
i j h
h
j
j
i j
i
j
j >
Figure 4: Grammar of Printable Values
The constant stands for no information. The following procedure, P , produces the printable value associated
with a term. is the list of bindings that have as rhs either a -expression or an allocator (i.e., Make tuple, I array,
Open cons), and is the list of store commands (i.e., the Istructure store). To simplify the notation, we will use x:i to
refer to the ith component of any type of data structure in
the store. x:i may be thought of as a location. We will also
represent a block as Ss ; As ; Bs In X , where Ss represent
all the store commands, As all the allocators bindings and
Bs all other bindings in the block, respectively.
The procedure L is used to lookup the value of a variable
or a location in and , respectively. Given a program, i.e.,
a closed term, M , the P rint procedure is invoked as follows:
f
g
P rint (M ) = P [ M ] Nil Nil
where M represents the canonical form of M and P is:
[ Ss; As ; Bs In X ] = [ X ] (As : ) (Ss : )
[ n] = n
[ True] = True
[ False] = False
[ ]=
[ Nil] = Nil
P f
P
P
P >
P
P
i
g
>
h
P
h
h
P
i
L
6.2 Printable Values and Answer of a Kid Term
We now de ne the printable information associated with a
term. The grammar for printable values for Kid is given in
Figure 4. A precise notion of printable values is essential
to develop an interpreter for Kid as well as to discuss the
correctness of optimizations (see Sections 7 and 8.4, respectively).
P
P
L
Proof: See [2].
Atoms
List
Tuple
Array
PV
[ X ] 8 =
n Tuple ( [ X1] ) ( [ Xn] )
>
>
if (X, ) = Make tuplen (X1 ; ; Xn )
>
>
List ( [ X:1]] ) ( [ X:2]] )
>
>
if (X, ) = Open cons()
>
>
< Array 2 Tuple l u ( [ X:l] ) ( [ X:u] )
if (X, ) = I array(Y )
and (Y, ) = Make tuple2 (l; u)
>
>
\Function"
>
!
>
>
if (X, ) = Xn :E or
>
!
>
if (X, ) = Applyi (F; n; Xi )
>
:
Otherwise
[ Y ] if (X.i, ) = Y
[ X:i] =
if (X.i, ) = not found
P
i
P
P
i
L
L
L
L
P
P
L
L
Notice that the printable value of a nite term can be an
in nite tree. For example, the P rint of :
x = Open cons ();
Cons store 1 (x; 1);
Cons store 2 (x; x);
In x
is List 1 List 1
, that is, an in nite list of 1's.
Intuitively, the answer associated with a term is the maximum printable information that can be extracted from the
term by repeatedly rewriting it. We need to de ne an order
on printable values to give a precise de nition of answer.
f
g
h
h
h i i i
De nition 6.2 (Partial Order on Printable Values)
Let a and b be printable values; a v b i
(i) a = ; or
(ii) b = >; or
(iii) both a and b are Atoms and a = b; or
(iv) a = hn Tuple a1 an i and b = hn Tuple b1 bn i
and ai v bi 8 1 i n; or
(v) a = hList a1 a2 i and b = hList b1 b2 i and a1 v b1 and
a2 v b2 ; or
(vi) a = hn Array abounds a1 an i and
b = hn Array bbounds b1 bn i and abounds = bbounds
and ai v bi 8 1 i n.
Theorem 6.3 PV is a complete partial order with respect
to v.
Proof: [10].
Lemma 6.4 Given a term M,
M ?! N =) P rint (M ) v P rint (N ):
Pictorially:
?! M1
?! M2
M
P rint(M ) v P rint(M1 ) v P rint(M2 )
Thus, the answer is the limit of this chain.
De nition 6.5 (Reduction Graph) Given a term M, the
reduction graph of M, G (M ), is de ned as
G (M ) fN j M ?!
! N g:
De nition 6.6 (Printable Reduction Graph) Given a
term M, the printable reduction graph of M, PG (M ), is dened as
PG (M ) fP rint (N ) j M ?!
! N g:
De nition 6.7 (Answer) Given a term M, the answer of
M, P rint (M ), is de ned as
P rint (M ) t PG (M ):
Notice that this notion is independent of any interpreter,
i.e., the method for computing it. In order for the above
de nition to make sense we need to show that the limit of
the printable reduction graph exists and is unique.
Theorem 6.8 8 terms M, PG (M) is directed.
Proof: Follows trivially from the con uence of Kid [22].
Since PG (M) P V is directed by theorem 6.3 it follows
that the answer of a term exists and is unique.
7 A Parallel Interpreter to Compute the Answer
The set of rewrite rules given in Section 6.1 per se do not
de ne the operational semantics of Kid; rather the rules
de ne a calculus to prove properties of programs. From a
computational point of view we need to specify a strategy
to apply these rules. Furthermore, the reduction strategy
should be normalizing with respect to the de nition of the
answer. Intuitively the interpreter should stop when it is
known that no more printable information can be gotten by
further reductions.
The interpreter E keeps track of store commands it encounters in an I-structure store, . It also keeps track of
-bindings and allocator bindings encountered in . At the
start of the evaluation, both and are empty. Given a
program M , the E interpreter is invoked as follows:
E val [ M ] = E[[M ] Nil Nil
The strategy presented here consists of evaluating the
redexes from outside-in. In case of a block all rhs redexes
are evaluated in parallel using the function ERHS described
below:
ERHS[[fSs; As; X1 = E1 ; Xn = En In Xi g]] =
fSs; As; X1 = E[[E1] ; Xn = E[[En] In Xi g
After the application of ERHS , we substitute variables
and constants and eliminate the corresponding binding from
the top block. We also atten the top-level block by merging
the block expressions on the rhs of bindings with the toplevel block. Since any substitution or block attening can
create new redexes on the rhs of bindings in a block, the
top-level block is evaluated again, if necessary. The function
F &S (for atten and substitute) accomplishes this, and in
addition to the new block, it returns a ag showing if any
substitutions or attening was done. Thus,
F&S[[fX = 1; Y = fP store (W; I; Z ); Z = X + 3 In Z g In Y g]]
= fP store (W; I; Z ); Z = 1 + 3; In Z g; True
The only remaining complication in evaluating a block,
is the propagation of >. When a block is evaluated, all
the store commands, Ss, in the block are merged with .
The function CC is used to check for inconsistencies, that
is, multiple writes into the same location. Thus, CC ( [
Ss) returns either a new consistent store or >s. According
to the > propagation rules, if a binding of the form x = >
exists in a block then that block goes to >. We assume that
the F &S function returns > if it encounters such a binding.
The Interpreter:
E[[+ (m; n)]] = +(m; n)
E[[Equal? (n; n)]] = True
E[[Equal? (n; m)]] = False
E[[Bool casem (True; E1 ; E2 )]] = E[[E1 ]
E[[Bool casem (False; E1 ; E2 )]] = E[[E2]
E[[List casem (Nil; E1 ; E2 )]] = E[[E1]
E[[List casem (X; E1 ; E2 )]] = E[[E2 ]
if L(X , ) = Open cons ()
E8
[ Apply (F; X )]] =
>> E[[Ap (F; X )]] if L(F , ) = Z :!E
>< Apply1 (F; n; X ) !if L(F , ) = n Zn :E ^ n > 1 !
Apply (F; n; Xi+1 ) if L(F , ) = Applyi (F; n; Xi ) ^
> i+1 ! i < (n?1)
>> E[[Apn (F; Xn)]] if L(F , ) = Applyi (F; n; X!i) ^
:
i = (n ?1)
!
!
!
E[[Apn;m (F; Xn )]] = E[[(RB [ E ] ) [Xn = Zn ]]]
!
where L(F , ) = n;m Zn :E
!
!
E [ Detuplen (X )]] =Xn if L(X , ) = Make tuple (Xn )
E [ Cons(X;Y )]] = E[[f t = Open cons();
Cons store 1(t;X );
Cons store 2(t; Y )
In tg]]
E [ Cons 1 (X )]] = Y
if L(X , ) = Cons store 1 (X; Y )
E [ Cons 2 (X )]] = Y
if L(X , ) = Cons store 2 (X; Y )
E[[Bounds (X )]] = Xb
if L(X , ) = I array (Xb )
E[[P select (X; Y )]] = Z if L(X , ) = P store (X; Y; Z )
E[[fSs; As; Bs In X g]] = e
where e is obtained by the execution of the following
program written in pseudo-Id
f blk; ? = F&S[[fSs; As; Bs In X g]];
flag = True;
In If blk = > then >
else fWhile flag do
Suppose blk is fSs; As; Bs In X g
is = CC( [ Ss);
fs = [ As;
next blk; next flag = If is = >s then (>; False)
else F&S[[ERHS[[blk] is fs]
nally blkgg
If none of the above clauses apply, then
E[[E ] = E
Theorem 7.1 (Soundness) Given a term M,
E val[ M ] = N =) P rint(M ) = P rint(N ):
8 Optimizations of Kid Programs
Any of the Kid rewrite rules given in Section 6.1 can also be
applied to an open term (that is, at compile time) and thus,
viewed as an optimization. In addition, we can give some
new rewrite rules which do not necessarily make sense as
part of the Kid interpreter, because of their complexity and
interference with other rules. Care needs to be exercised in
applying some of the Kid rules at compile time, because they
may cause non-termination. For example, if the applicationrule is invoked on a recursive function at compile time, the
optimizer will keep producing a bigger and bigger expression and not terminate. We solve this problem by requiring
some annotations by the user. This additional information
is reminiscent of a mechanism, called underlining in term
rewriting systems, which has the e ect of turning a set of
rules into an equivalent strongly-normalizing set of rules [15].
There are also cases where we want to postpone the application of a certain Kid rule because it can obscure and,
possibly, prohibit the applicability of some other optimizations because of loss of information. The cons-rule in Kid
is a good example to illustrate this point. Once the cons
data structure is expressed in terms of I-structures the optimizer will not be able to recognize that the cons is a functional data structure, and is, therefore, amenable for common subexpression elimination or loop code hoisting.
Optimizations should be performed after type checking
and after all bound variables have been assigned unique
names. Applicability of certain rules requires some semantic
check such as \n > 1". We write such semantic predicates
above the line but following an \&".
We have divided the optimizations in two categories, the
ones that are applied from outside in and the one that are
applied from inside out.
8.1 Outside-in Optimizations rules
As discussed earlier all Kid rules except the cons-rule and
the application-rule can be applied at compile time. Since
the application-rule is too important an optimization, Id
provides the Defsubst annotation for the user to indicate
that the function is substitutable at compile time. In the
translation from Id to Kid such s are underlined.
Since the cons-rule is eliminated from the optimizations,
we need the following additional rules to restore the fetchelimination possibilities. Similar ideas are applicable to all
functional data structures, particularly the make-array primitive.
Fetch Elimination
X = Cons (X1 ; X2 )
Cons 1 (X ) ??! X1
X = Cons (X1 ; X2 )
Cons 2 (X ) ??! X2
X = Cons (X1 ; X2 )
List case (X; E1 ; E2 ) ?! E2
The following algebraic identities in conjunction with
other normal optimizations often lead to dramatically more
ecient programs.
Algebraic Identities
We have classi ed the algebraic identities into three groups
because they have slightly di erent properties. Only the
rst group preserve total correctness.
Alg1
And (True; X ) ??! X
Or (False; X ) ??! X
+ (X; 0)
(X; 1)
..
.
??! X
??! X
Alg2
And (False; X ) ?! False
?! True
Or (True; X )
?! 0
(X; 0)
Equal? (X; X ) ?! True
..
.
Alg3
& m>0
X = + (X1 ; m)
Less (X1 ; X ) ??! True
& m>0
X = + (X1 ; m)
Equal? (X1 ; X ) ??! False
..
.
We have shown in [2] that Alg3 rules cause the optimization rules to be non-con uent. However, this is not a
serious drawback because the cases where the con uence is
lost are the ones where the unoptimized program would have
produced no information.
Specialization
Applysp is a user annotated version of Apply to indicate that a
new (specialized) de nition be generated corresponding to
this closure. The rational for this annotation is that we want
to avoid the generation of too many specialized functions.
The user has to exercise some caution with this annotation
as well because this rule can also cause non-termination in a
recursive de nition. A more sophisticated strategy, that we
have not explored yet, would try to avoid non-termination
by computing some xpoint in case of such an annotation.
!
F = n;m Zn : E & n > 1
Apply sp(F; X ) ??!
!
ff = n?1;m zn!?1 :(RB[[E ] ) [zn!?1 = Z2;n ; X=Z1 ]
In f g
!
Thus, PE is derived from E 0 by adding the following
boolean-case:
PE[[Bool casem (X; E1 ; E2 )]] =
Bool casem (X; (PE[[E1 ] ); (PE[[E2 ]]] ))
and replacing the block evaluation by the following program:
PE[[fSs; As; Bs In X g]] = e
where e is obtained by the execution of the following
program written in pseudo-Id
f blk; ? = F&S[[fSs; As; Bs In X g]];
flag = True;
If blk = > then >
else
f blk0 = f While flag do
Suppose blk is fSs; As; Bs In X g
is = CC( [ Ss);
fs = [ As;
next blk; next flag =
If is = >s then (>; False)
else
F&S[[E 0 RHS[[blk] is fs]
nally blkg
Suppose blk0 is fSs; As; Bs In X g
is = CC( [ Ss0 );
fs = [ As0 ;
blk00 = If is = >s then (>; False)
else (PERHS[[blk0 ] is fs)
00
In blk gg
!
Fi = Applyi (F; n; Xi ) j F = n;m Zn : E & i < (n ? 1)
Applysp (Fi ; Xi+1 ) ??!
ff = n?i?1;m zn?!i?1 :
!
!
!
(RB [ E ] ) [zn?!i?1 = Zi+2;n ; Xi+1 = Zi+1 ]
In f g
8.2 A Partial Evaluator for Kid
Applying the rules given in Section 6.1 to a term is like
partial evaluation. In the following, we present an ecient
partial evaluator, PE , which is based on extending the interpreter, E , given in Section 7. Let E 0 be an interpreter
derived from E by disallowing the cons-rule and by restricting the application rule to underlined s. Furthermore, E 0
contains cases corresponding to all optimizations given in
Section 8. Notice that to write E 0 we will also need E 0 RHS
function, which is analogous to the ERHS function.
The main di erence between PE and E 0 shows up in
expressions that encapsulate other expressions, e.g., caseexpressions and blocks. PE , unlike E 0 , will eventually cause
the evaluation of both arms of a conditional. We call a
block stable when it does not contain any redexes at the
top-level. Notice that when E 0 is applied to a block, it terminates when the block is stable, while PE evaluates all the
rhs in the top-level block after it becomes stable, using the
function PERHS :
PERHS[[fSs; As; X1 = E1 ; Xn = En In Xi g]] =
fSs; As; X1 = PE 0[ E1 ] ; Xn = PE 0 [ En] In Xi g
Let R be the set of rules used by the interpreter E 0 .
Theorem 8.1 (Normalization Theorem) Given terms M
and N,
M
?!
!
R
N
=) PE [ M ] Nil Nil = N:
8.3 Inside-out Optimization rules
Common subexpression elimination is one of the best known
compiler optimizations. Though it can be expressed as a
rewrite rule, it requires determining if an expression is sidee ect-free (sef) as de ned below:
De nition 8.2 (Side e ect free (sef))
(1) Variables and constants are sef;
!
(2) all PFn;m (Xn ) , except I array, Open cons, and Apply
are sef;
(3) Bool Casem (X; E1 ; E2 ) is sef, if E1 and E2 are sef;
similarly for List Case.
(4) n;m X:E is sef, if E is sef;
(5) Apn;m (F; X ) is sef, if F is bound to a sef -abstraction;
Common Subexpression Elimination
!
!
!
Ym = E & E is sef ^ Ym 6Xm
!
!
!
?! Xm =Ym
Xm = E ?
For correctness, observational equivalence is not enough.
It has to be shown that no context can distinguish between
optimized and unoptimized term.
De nition 8.4 (Observational Congruence) Given two
Lift Free Expressions
Let FE (e; e0 ) return true if the expression e is free in e0 .
!
!
& FE (E; n;m Zn :fm Y = E ; S In Xm g) ^ E is sef
!
!
?!
n;m Zn : fm Y = E ; S In Xm g ?
fm t1 = E ; !
!
t = n;m Zn : fm Y = t1 ; S In Xm g
In t g
This rule in conjunction with the loop rules, (see Section 9)
will cause loop invariants, that is, expressions that do not
depend on the nexti ed variables, to be lifted from loops.
Hoisting Code out of a Conditional
fn Y = E ; S In Xn!g;
fn Y 0 = E ; S 0 In Xn0 g)))
!
!
Bool casen(X; fn Y = E ; S In Xn g; fn Y 0 = E ; S 0 In Xn0 g) ??!
fn t1 = E ;
!
tn = Bool casen (X;
!
fn Y = t1 ; S In X!n g;
fn Y 0 = t1 ; S 0 In Xn0 g)
!
In tn g
&
FE (E; (Bool casen(X;
!
All the optimizations discussed in this section need to be
applied \inside-out", because one wants to nd the largest
common subexpression or the largest expression to be lifted.
It is dicult to give a precise algorithm for applying these
optimizations because it requires choosing a data structure
for Kid programs. However, an overall strategy for optimizations is given below:
(1) apply PE given in Section 8.2;
(2) apply the inside out rules;
(3) if there is any change in the expression in step (2) go to
step (1), else stop.
Step (3) is needed because step (2) can enable new outsidein optimizations. For example, the cse-rule may trigger the
algebraic equality rule. We believe that the above strategy
is normalizing.
8.4 Correctness of Optimizations
De nition 8.3 (Observational Equivalence) Given two
terms M and N, M and N are said to be Observationally
Equivalent i P rint (M ) P rint (N ):
terms M and N, M and N are said to be Observationally
Congruent i 8 C [2]; P rint (C [M ]) P rint (C [N ]) :
If we let (A;R) be a CRS, where A represents the set of
terms, and R a set of rules, then correctness can be formulated as follows:
De nition 8.5 (Correctness) An optimizer (A,Ro)
for (A,R) is correct i
8 M 2 A; M ?!
! N =)
Ro
(i) P rint (M ) v P rint (N ) and
(ii) P rint (N ) = > =) P rint (M ) = >:
Notice that condition (ii) is needed because > is higher than
all printable values and we do not want the optimizer to take
all programs to >.
It is believed that all optimizations presented in Section
8 preserve correctness, though this has been proven for only
a small subset of them so far [2]. In general, correctness
of an optimization is dicult to prove. However, some optimizations can be proven correct easily because they are
derived rules.
De nition 8.6 (Derived rule) A rule r is said to be de-
rived in R i
! M2 ^ M1 ?!
! M2
M ?r! M1 =) 9 M2 ; M ?!
R
R
Corollary 8.7 Given an optimizer (A,Ro) of (A,R),
8 r 2 Ro ; r is derived =) (A; Ro ) is correct :
Lemma 8.8 All RKid rules, Inline Substitution and Fetch
Elimination are derived rules, and hence correct.
Unfortunately, it is not always possible to mimic inside
RKid what an optimizations does. Therefore, we introduce
a notion of tree equivalence, which is useful for proving the
correctness of cse-like rules. The tree associated to a term
is obtained intuitively by repeatetly substituting the rhs of
a binding for each occurrence of the lhs. Let F l be the
function that given a term produces the corresponding unravelled term [4].
De nition 8.9 (Tree-equivalence t ) Two terms M and
N are said to be tree-equivalent, if F l(M ) F l(N ):
Lemma 8.10 Given an optimizer (A,Ro) of (A,R),
8 M; N 2 A; [M ?! N =) M t N ] =)
(A;Ro ) is correct
Ro
Corollary 8.11 Let R be the set fCommon subexpression
elimination rule, lift free expressions rule, Hoisting code out
of a conditional rule g, M ?! N =) M t N:
R
Hence cse -like rules are correct.
9 Loops
Even though iteration can be expressed in terms of recursion, it is generally accepted that iterative forms can be
supported more eciently on all architectures. Moreover,
explicit loop expressions make it easy to express additional
optimizations.
We add two new function symbols to the syntax of Kid,
given if Figure 3, as follows:
Em ::= WLoopm (SEm+3 ) j FLoopm (SEm+4 ) j
During the translation phase from Id to Kid the predicate and loop body are transformed into -expressions. The
WLoop combinator has as parameters the predicate identier (P ), the loop body identi er (B ), the nexti ed variables
!
(Xn ), and the loop predicate variable. The rewrite rules for
WLoop are given below:
!
WLoopn (P; B; Xn ; True) ??!
!
!
fn tn = Apn;n (B; Xn );
!
tp = Apn (P; tn );
!0
!
tn = WLoopn (P; B; tn ; tp)
!
In t0n g
!
!
WLoopn (P; B; Xn ; False) ??!Xn
For the FLoop, the predicate P of the WLoop is replaced
by an index variable (by convention X1 ), and an upper
bound (U ), and the delta D by which the index variable
is to be incremented in each iteration. The rewrite rules for
FLoop are given below:
!
FLoopn (U; D; B; Xn ; True) ??!
!
!
fn t2;n = Apn;n?1 (B; Xn );
t1 = + (X1 ; D);
tp = < (t1; U );
!
!
t0n = FLoopn (U; D; B; tn ; tp )
!0
In tn g
!
!
FLoopn (U; D; B; Xn ; False) ??!Xn
Loop Optimizations
Peeling the Loop
!
FLoopn (U; D; B; Xn ; X ) ??!
!
!
Bool casen (X; fn t2;n = Apn;n?1 (B; Xn );
t1 = + (X1 ; D);
tp = < (t1; U );
!
!
t0n = FLoopn (U; D; B; tn ; tp)
!0
In tn g;
!
Xn )
Notice that the above rule is again applicable to the loop
expression generated on the rhs. To avoid unbounded number of applications of this rule the user will have to indicate
how many times the loop peeling should be performed.
Loop Body Unrolling (K times)
& remainder ((U ? X1 )=D;k) = 0
!
FLoopn (U; D; B; Xn ; Xp ) ??!
!
fn b = n;n?1 x!n : fn?1 t12;n = Apn;n?1 (B; x!n );
t11 = +(X1 ;D);
!
!
t22;n = Apn;n?1 (B; t1n );
t21 = +(t11; D);
..
.
!
!
k
t2;n = Apn;n?1 (B; tkn?1 )
!
In tk2;n g ;
!
!
tn = FLoopn (U;D; b; Xn ;Xp );
!
In tn g
In the above rule we suppose r = remainder((U ? X1 )=D;k),
and r is not zero. We can still apply the above transformation by rst peeling the loop r times. Notice, k has to be
supplied by the user.
When a loop in Id is annotated to be unfolded or peeled,
the translation from Id to Kid generates underlined 's for
P and B .
Eliminating Circulating Variables
Suppose in the loop body of an Id program there exists an
expression like \Next x = x " , then the variable x can be
made into a free variable of the loop and its circulation can
be avoided. Without loss of generality, we assume that the
nexti ed variable to be eliminated is the last one.
!
j
P = n Xn : E
!0
!
0
B = n;n Xn : fn S In Zn?1 ; Xn g
!
WLoopn (P; B; Yn ; Yp ) ??!
!
f p = n?1 xn!?1 :RB[[E ] [xn!?1 = Xn?1 ; Yn =Xn ];
!
b = n?1;n?1 x0n?1 :
!
!
!
RB[[fn?1 S In Zn?1 g]] [x0n?1 = Xn?1 ; Yn =Xn0 ];
!
!
tn?1 = WLoopn?1 (p; b; Yn?1 ; Yp )
!
In tn?1 ; Yn g
A similar optimization applies to for-loops.
Eliminating Circulating Constants
Suppose in the loop body there exists an expression like
\Next x = t ", where the variable t is a free variable of
the loop body, then its circulation can be avoided. Such
situations may arise as a consequence of lifting invariants
from a loop. For example,
f While (p x y) do
Next x = t;
Next y = f x y;
Finally yg
=) f If (p x y) then
fy1 = f x y;
In f While (p t y1 ) do
Next y1 = f t y1 ;
Finally y1 gg
else yg
Notice that it is only after the rst iteration that the
value of the variable x is t. Thus, to avoid the circulation
of the nexti ed variable x, the loop has to be peeled once.
This rule can be expressed as follows.
!
j
P = n Xn :E
!
!
B = n;n Xn0 :fn S In Zn g & F E (Zn ; )
{z
}
|
!
WLoopn (P; B; Yn ; Yp ) ??!
Bool casen(Yp ;
!
fn p = n?1 xn!?!1 :RB[[E ] [xn!?1 = Xn?1 ; tn =Xn ];
b = n?1;n x0n?1 :
!
!
!
RB[[fn?1 S In Zn?1 g]] [x0n?1 = Xn0 ?1 ; tn =Xn0 ];
!
!
tn = Apn;n (B; Yn );
!
tp = Apn?1 (p; tn?1 );
!
!
t0n?1 = WLoopn?1 (p; b; tn?1 ; tp );
!
In t0 ; tn g;
! n?1
Yn )
Please note that we could have also written Zn instead
of tn on the rhs.
10 Conclusions
Kid goes a long way towards expressing many machine independent implementation concerns. Since Kid has a proper
calculus associated with it, correctness issues can be handled
at an abstract level. The major de ciency of Kid is its inability to express storage reuse. We believe a language like Kid
with a proper calculus which does not obscure parallelism
and which can express storage reuse would be extremely useful in practical compilers for functional and other declarative
languages. In fact, Kid is central to the current restructuring
of the Id compiler which is already being used by 4 groups
to generate code for their machines by just changing back
end of the compiler.
Some of the important optimizations that we currently
perform in the Id compiler but have not discussed are dead
code elimination, loop variable induction to hoist array bound
checking, and array subscript analyses. These optimizations
do not t in the CRS model very well. A better formalization of these optimizations would also be very useful.
Acknowledgements
Funding for this work has been provided in part by the Advanced Research Projects Agency of the Department of Defense under the Oce of Naval Research contract N0001484-K-0099 (MIT) and N0039-88-C-0163 (Harvard).
Many thanks to Paul Barth, Shail Gupta, Jamey Hicks,
Yuli Zhou and Bob Muller for reading the current draft of
the paper and for providing insightful comments.
References
[1] A. Aho, J. Ullman, and R. Sethi. Compilers: Principles, Techniques, Tools. London, Addison-Wesley, 1986.
[2] Z. M. Ariola and Arvind. P-TAC: A Parallel Intermediate Language. In Proc. ACM Conference on Functional Programming Languages and Computer Architecture, London, 1989.
[3] Z. M. Ariola and Arvind. Compilation of Id? : a Subset
of Id. Technical Report CSG Memo 315, MIT Laboratory for Computer Science, July 1990.
[4] Z. M. Ariola and Arvind. Contextual Rewriting. Technical Report CSG Memo 323, MIT Laboratory for
Computer Science, 1991.
[5] Arvind, R. S. Nikhil, and K. K. Pingali. I-Structures:
Data Structures for Parallel Computing. ACM Transactions on Programming Languages and Systems, 11(4),
October 1989.
[6] H. P. Barendregt. The Lambda Calculus: Its Syntax
and Semantics. North-Holland, Amsterdam, 1984.
[7] H. P. Barendregt, T. H. Brus, M. C. J. D. van Eekelen, J. R. W. Glauert, J. R. Kennaway, M. O. van Leer,
M. J. Plasmeijer, and M. R. Sleep. Towards an Intermediate Language based on Graph Rewriting. In
Proceedings of the PARLE Conference, Eindhoven, The
Netherlands, Springer-Verlag LNCS 259, June 1987.
[8] H. P. Barendregt, M. C. J. D. van Eekelen, J. R. W.
Glauert, J. R. Kennaway, M. J. Plasmeijer, and M. R.
Sleep. Term Graph Rewriting. In Proceedings of
the PARLE Conference, Eindhoven, The Netherlands,
Springer-Verlag LNCS 259, pages 141{158, June 1987.
[9] T. Brus, M. van Eekelen, M. vam Leer, and M. Plasmeijer. Clean - A Language for Functional Graph Rewriting. In Proc. ACM Conference on Functional Programming Languages and Computer Architecture, Portland,
Oregon,Springer-Verlag LNCS 274, 1987.
[10] B. Courcelle. Fundamentals Properties of In nite Trees.
D. Reidel Publishing Company, 1982.
[11] N. de Bruijn. Lambda Calculus Notation with Nameless
Dummies: A Tool for Automatic Formula Manipulation, with Application to the Church-Rosser Theorem.
In Koninkijke Nederlandse Akademie van Wetenschappen, Series A, Mathematical Sciences, 1972.
[12] P. Hudak and P. Wadler. Report on the Programming Language Haskell, A Non-strict Purely Functional Language (Version 1.0). Technical Report
YALEU/DCS/RR777, Yale University, Department of
Computer Science, April 1990.
[13] T. Johnsson. Ecient Compilation of Lazy Evaluation.
In Proceedings of the ACM SIGPLAN '84 Symposium
on Compiler Construction, pages 58{69, June 1984.
[14] V. Kathail. Optimal Interpreters for Lambda-calculus
Based Funtional Languages. May 1990. Ph.D. thesis,
Dept. of Electrical Engineering and Computer Science,
MIT.
[15] J. Klop. Term Rewriting Systems. 1s t Autumn workshop on Reduction Machines, Italy, September 1985.
[16] R. S. Nikhil. Id (Version 90.0) Reference Manual. Technical Report CSG Memo 284-a, MIT Laboratory for
Computer Science, July 1990.
[17] S. L. Peyton Jones. The Implementation of Functional
Programming Languages. Prentice-Hall International,
Englewood Cli s, N.J., 1987.
[18] G. Plotkin. Call-by-name, Call-by-value and the
Lambda Calculus. Theoretical Computer Science,
1:125{159, 1975.
[19] D. A. Turner. A New Implemetation Technique for
Applicative Languages. In Software - Practice and Experience, volume 9, pages 31{49, 1979.
[20] D. A. Turner. Miranda: a non-strict functional language with polymorphic types. In IFIP Int'l Conf. on
Functional Programming and Computer Architecture,
Nancy, France, Springer-Verlag LNCS 201, 1985.
[21] C. Wadsworth. Semantics And Pragmatics Of The
Lambda-Calculus. Ph.D. thesis, University of Oxford,
Semtember 1971.
[22] C. Wadsworth. Approximate Reduction and Lambda
Calculus Models. Theoretical Computer Science, 7,
1978.