Content uploaded by Marco T. Morazán
Author content
All content in this area was uploaded by Marco T. Morazán on Dec 19, 2013
Content may be subject to copyright.
Improved Graph-Based Lambda Lifting
Marco T. Moraz´an
Department of Mathematics and Computer Science
Seton Hall University
Email: morazanm@shu.edu
Barbara Mucha
Department of Computer Science
Stony Brook University
Email: basia@cs.sunysb.edu
Abstract—Lambda lifting is a technique for transforming a
program with local function definitions into a program consisting
only of global function definitions. The best known lambda lifting
algorithm computes the minimal set of extraneous parameters
needed by each function in O(n3)steps by solving a system
of set equations which are recursive if the functions in the
program are mutually recursive. Mutually recursive functions
give rise to strongly connected components in the call graph of
a program. Danvy and Schultz observed that all functions in a
strongly connected component can be given the same set of free
variables as extraneous parameters. Based on this observation,
they developed an O(n2)graph-based lambda lifting algorithm.
This article illustrates how Danvy’s and Schultz’s algorithm is
an approximation of Johnsson’s algorithm for a certain class of
programs and describes an O(n3)graph-based lambda lifting
algorithm that yields the same results as Johnsson’s algorithm.
I. INTRODUCTION
Lambda lifting is a technique for transforming a program
with local function definitions into a program consisting only
of global function definitions. This program transformation
technique is important for restructuring functional programs
written for the web[1], for partial evaluators[2], and for effi-
cient compilation[3]. The scoping rules of most programming
languages prevent nested functions from directly being lifted
to the global level due to references to free variables and to
references to other functions. In order to lift nested functions to
the global level, a program must be made scope insensitive by
computing for each function the complete set of free variables
that must be explicitly passed to it. Lambda lifting algorithms
spend most of their time searching for free variables that are
introduced to a lifted function by the functions it references.
The search for free variables is difficult, because for any nested
function only a subset of its free variables is known after
parsing. This set of known free variables is the set of free
variables directly referenced in the body of the function. For
mutually recursive functions, the process of lambda lifting may
introduce a set of new hidden free variables to a function by
having to explicitly pass free variables needed by functions it
references.
The best known lambda lifting algorithm, due to Johnsson,
is based on solving a system of recursive set equations and
has a complexity of O(n3)[4]. The system of recursive set
equations captures the lexical dependencies between functions
in the source program. Danvy and Schultz improved the com-
plexity of lambda lifting to O(n2)by formulating it as a graph
algorithm[5]. Their algorithm is based on the observations that
mutually recursive functions give rise to strongly connected
components in the call graph of a program and that all the
functions in a strongly connected component can be given
the same set of extraneous parameters. In their algorithm,
the strongly connected components in the call graph of a
program are coalesced to create an acyclic graph which is
traversed to propagate free variables between components. At
each step, the union of the free variables of a node and one of
its successors is performed. In addition to performing lambda
lifting, Danvy and Schultz reduce the arity of functions in
the transformed program by eliminating variable aliases. That
is, any free variables whose bindings are available within a
strongly connected component under a different name are not
added as parameters to lifted functions. Instead, their aliases
are used to reference them.
Using strongly connected components to propagate free
variables, however, results in an approximation of the min-
imal set of extraneous parameters needed by lifted functions.
Unnecessary extraneous parameters may be added to lifted
functions that are members of a strongly connected component
that contains nested strongly connected components and that
has functions defined at different levels in the program. For
example, consider a program with a single global function that
has local functions and whose call graph contains a strongly
connected component that includes the global function. If the
graph of the strongly connected component contains several in-
dependent loops that include the global function, then Danvy’s
and Schultz’s algorithm identifies as free, for all functions in
the strongly connected component, the free variables that are
only needed by the functions in one of the loops.
This article first reviews Johnsson’s equation-based and
Danvy’s and Schultz’s graph-based lambda lifting algorithms
and illustrates how Danvy’s and Schultz’s algorithm is an
approximation of Johnsson’s algorithm for a certain class
of programs. The article then proceeds to describe a graph-
based lambda lifting algorithm that yields the same results as
Johnsson’s algorithm. The algorithm splits strongly connected
components based on nested strongly connected components.
It is our observation that the functions in distinct nested
strongly connected components (e.g. independent loops within
a strongly connected component) do not need to carry the
same set of extraneous parameters. In addition, our algorithm
does not propagate a free variable beyond the function that
last references it along a path, within a strongly connected
component, that leads to the function where the variable is
(define (F0 w x y z)
(define (H1 a) (....H2....))
(define (H2 k) (....H3....z....))
(define (H3 i j) (....H1....))
(define (R1 a b) (....R2....))
(define (R2 i j) (....R3....))
(define (R3 a b) (....F0....w....))
(define (G1 a) (....G2....x....))
(define (G2 s t)
(define (M1 a) (....s....G2...))
(define (M2 a) (....t....G2...))
(....F0....y....z...M1...M2...))
(....H1....R1....G1....))
Fig. 1. Outline of a Scheme Program.
declared. These improvements come at the cost of taking the
algorithm from O(n2)to O(n3). The advantage of our algo-
rithm, over Johnsson’s algorithm, is its graph-based nature. In
the implementation of functional languages it is common to
represent programs as graphs[6], [7] which makes our lambda
lifting algorithm a natural fit with common program evaluation
techniques and other program transformation techniques. The
article ends with comments on related work, some conclusions,
and brief descriptions of future work on lambda lifting in
quadratic time and on closureless functional languages.
II. LAMBDA LIFTING ALGORITHMS
A. Johnsson’s Algorithm
The source program is traversed top-down to compute the
minimal set of extraneous parameters needed by each function.
At each level of the program, a system of set equations is
solved. Mutually recursive functions give rise to a mutually
recursive set of equations that in the worst case is solved
in O(n3)steps, where nis the number of functions in the
program.
For any given function, f, the equation for the minimal set
of free variables of f,F Vf, is given by:
F Vf=KF VfS((Sg ǫF FfF Vg)TScopeV arsf)
where K F Vfis the set of known free variables of f,gis
a member of the set of functions referenced by f,F Ff,
and ScopeV arsfare the variables defined in f’s enclosing
scope. Once F Vfis known it is used to compute the minimal
set of free variables for functions declared further down the
program’s parse tree.
B. Danvy’s and Schultz’s Graph-Based Lambda Lifting
To perform lambda lifting in quadratic time, a program is
represented as a call graph. Each node in this graph represents
a function. An edge from fto gmeans that there is a reference
to gin the body of f. Mutually recursive functions give rise to
strongly connected components. Danvy and Schultz observed
that a function, f, in a strongly connected component can
be given as extraneous parameters the set of free variables
lexically visible to ffound in the union of the free variables
FVF0= () S((FVH1SFVR1SFVG1)T()) = ()
(a) Level I Equations.
FVH1= () S(FVH2T(w,x,y,z)) = (z)
FVH2= (z) S(FVH3T(w,x,y,z)) = (z)
FVH3= () S(FVH1T(w,x,y,z)) = (z)
FVR1= () S(FVR2T(w,x,y,z)) = (w)
FVR2= () S(FVR3T(w,x,y,z)) = (w)
FVR3= (w) S(FVF0T(w,x,y,z)) = (w)
FVG1= (x) S(FVG2T(w,x,y,z)) = (x,y,z)
FVG2= (y,z) S((FVF0SFVM1SFVM2)T(w,x,y,z))
= (y,z)
(b) Level II Equations.
FVM1= (s) S(FVG2T(w,x,y,z,s,t)) = (y,z,s)
FVM2= (t) S(FVG2T(w,x,y,z,s,t)) = (y,z,t)
(c) Level III Equations.
Fig. 2. System of set equations for each level of the program in Figure 1.
of the functions that constitute the component. Therefore,
strongly connected components can be coalesced in the call
graph of a program to yield a directed acyclic graph that is
traversed to propagate free variables between nodes.
The nodes are processed in a reverse breadth-first order-
ing. Free variables are first distributed between the functions
represented by a node that does not have successors by
computing the union of the sets of free variables of the node’s
member functions. The free variables of these functions are
then propagated to their calling functions before processing
any other coalesced nodes. Once all the successors of a node,
N, have been processed the union of the set of free variables
of the functions in Nis computed and distributed to the
individual functions.
C. An Illustrative Example
To illustrate how the above lambda lifting algorithms work
consider the program outline, written using Scheme-like syn-
tax, in Figure 1. For each function only variable declarations,
free variable references, and function references are displayed.
1) Free Variables Using Johnsson’s Algorithm: Figure 2
displays the systems of equations that need to be solved at
each level of the program’s parse tree and the solution of
each equation. At the first level the only function that is
defined is F0and since there are no variables defined in its
enclosing scope it follows that its minimal set of free variables
is empty. This result is displayed in Figure 2(a). For the
functions at the next level, Figure 2(b) displays the variables
declared by F0as the variables defined in the lexical scope
of the functions defined at this level. The equation for each
function also depends on the function’s known free variables
F0
H1 R1 G1
H2 R2 G2
H3 R3 M1 M2
(a) Call Graph.
SCC Member Functions Known Free Vars Final Free Var Set
S1 F0, R1, R2, R3, G1, G2, M1, M2 w, x, y, z, s, t w, x, y, z, s, t
S2 H1, H2, H3 z z
(b) Strongly connected components, known free variables, and final free variable sets.
S1 S2
(c) Coalesced Graph.
Fig. 3. Call graph, strongly connected components, free variables, and coalesced call graph used for the program in Figure 1.
and the functions it references. When solving each equation
(e.g. by substitution) any reference to F VF0uses the known
result computed in the previous step. Figure 2(c) displays the
system of equations and their solution for the final level of our
sample program. To solve this system of equations, the results
obtained for the functions in the previous levels are used.
2) Free Variables Using Danvy’s and Schultz’s Algorithm:
Figure 3(a) displays the call graph for the program in Figure
1. In this graph there are two strongly connected that we have
called S1and S2. The first three columns of Figure 3(b)
display these components, the member functions of each of
these components, and the initial set of known free variables
associated with each component. The initial set of known free
variables of a component is obtained by taking the union
of the known free variables of each of the functions in the
component.
The acyclic graph obtained by coalescing the strongly
connected components is displayed in Figure 3(c). The graph
traversal attempts to propagate zas a free variable from S2
to S1by passing zas a free variable to F0. Since F0
declares z, nothing is propagated between components in this
example. The complete set of free variables associated with
each component is displayed in column 4 of Figure 3(b). After
the traversal, each function of a strongly connected component
receives as extraneous parameters the free variables, in its
enclosing lexical scope, that are associated with its component.
D. Discussion
Figure 4 displays the complete set of free variables identi-
fied by the equation-based and the graph-based algorithms. For
several functions, the graph-based algorithm identifies more
variables as free. For example, the lifted versions of R1,R2,
R3,G1, and G2will all carry as extraneous parameters the
set {w,x,y,z}. In contrast, when using Johnsson’s algorithm
the lifted versions of R1,R2, and R3are only extended by
the set {w}, the lifted version of G1is only extended by the
set {x,y,z}, and the lifted version of G2is only extended
by the set {y,z}.
Notice, for example, that R1,R2, and R3do not need x,
y, and zas extraneous parameters. The only free variable that
needs to be carried by these functions is w, because it is the
only free variable that is actually referenced in the original
version of these functions (specifically in the body of R3). The
free variables x,y, and zare extraneous in R1,R2, and R3
using the graph-based algorithm only because they belong to
the same strongly connected component as G1and G2where
these free variables are actually needed. Similarly, wis an
extraneous parameter for G1and G2only because it is needed
in R3which is in the same strongly connected component.
We define a free variable as local to a component if it is
declared by a function in the component. The free variables
w,x,y, and zare all declared by, F0, the entry function to
S1. Therefore, w,x,y, and zare free variables that are local
Function Equation-Based Graph-Based
F0 none none
H1 z z
H2 z z
H3 z z
R1 w w, x, y, z
R2 w w, x, y, z
R3 w w, x, y, z
G1 x, y, z w, x, y, z
G2 y, z w, x, y, z
M1 y, z, s w, x, y, z, s, t
M2 y, z, t w, x, y, z, s, t
Fig. 4. Sets of free variables identified for each function in Figure 1.
to S1. If a strongly connected component, has nested strongly
connected components (e.g. two independent loops), then free
variables local to the component may not have to be carried
by all of its member functions. In our example, S1has nested
strongly connected components (i.e. the two independent loops
that contain F0). In the subgraph containing only the functions
in S1, all paths from either G1,G2,M1, or M2to either R1,
R2, or R3(and vice versa) must go through F0. That is, F0
dominates the paths between these two sets of functions. This
means that any variables declared by F0that are known free
variables in the first set and are not known free variables in the
second set do not need to be identified as free for the second
set. The same holds true for variables declared by F0that are
known free variables in the second set and are not known free
variables in the first set. This follows from the fact that any
sequence of calls between these two sets of functions must
contain a call to F0that instantiates all of the variables it
declares.
The variable xis a known free variable in G1that is
unnecessarily identified as free for G2,M1, and M2when
using the graph-based algorithm. Notice that there does not
exist a path in the call graph of the program from G1to
a function that has xas a known free variable that fails to
go through x’s declaring function (i.e. F0). Therefore, the
successors of G1(i.e. G2,M1, and M3) do not need to carry
xas a free variable. In general, for functions fand gin the
same strongly connected component, a variable that is known
to be free in fand is declared by gonly needs to be carried by
successors of fif there is a path, that does not contain g, from
fto another function where the same variable is known to be
free. This follows from the observation that the successors of
fdo not need to make the free variable available to any other
function if such a path does not exist.
In summary, the graph-based algorithm may identify as free
more variables than necessary for any given function. This
only occurs when a strongly connected component contains
nested strongly connected components with functions declared
at different levels in the program. The entry function to such
a strongly connected component may declare variables that
are only free in a subset of the nested strongly connected
<program>→(<define-expr>)∗
<define-expr>→(define (<id>+) (<define-expr>)∗<expr>)
<expr>→<mtscheme-val>|<var>|<lambda-expr>
→<cond-expr>|<if-expr>|<app-expr>
<lambda-expr>→(lambda (<id>∗)<body>)
<if-expr>→(if <expr> <expr> <expr>)
<cond-expr>→(cond ((<expr>)<expr>)+(else <expr>))
→(cond ((<expr>)<expr>)+)
<app-expr>→(<expr>+)
Fig. 5. The BNF Grammar for MT-Scheme’s Core Language.
components that include the entry function or in a subset of
the functions within an individual loop in a strongly connected
component. Johnsson’s algorithm avoids adding unnecessary
extraneous parameters by requiring that systems of equations
be solved level by level in the program. In essence, this means
that free variables declared by the entry function to such a
strongly connected component are not allowed to propagate
between independent loops that contain the entry function and
are not carried by functions beyond their last reference within
an individual loop.
III. THE MT-SCHEME LAMBDA LIFTING ALGORITHM
The MT-Scheme lambda lifting algorithm is a graph-based
algorithm that computes the minimal set of extraneous para-
meters needed by lifted functions yielding the same results as
Johnsson’s algorithm. This is achieved by splitting strongly
connected components into multiple components based on
the number of nested strongly connected components they
contain and by ignoring edges to the entry function within
a strongly connected component. Splitting strongly connected
components guarantees that free variables are not unnecessar-
ily propagated between independent loops within a strongly
connected component. Ignoring edges to the entry function
within a component guarantees that free variables local to a
component are not unnecessarily propagated beyond their last
reference in a loop.
A. General Description
The algorithm presented is used to lambda lift programs
written in a pure subset of Scheme, called MT-Scheme, defined
by the grammar in Figure 5. In MT-Scheme a program has zero
or more function definitions. Each function definition has a
function header, zero or more local function definitions, and
an expression as its body. An expression can be a value, a
variable reference, a lambda expression, an if expression, a
conditional expression, or an application expression.
Figure 6 displays pseudo-code for the MT-Scheme lambda
lifting algorithm. The algorithm starts by parsing a program P.
The parser lifts anonymous functions (i.e. lambda expressions)
within scope and for each function, g, computes both, gG,
g’s function references and, gkf var s,g’s known free vari-
ables. Variable references are represented using an index into
Parse(P)
For each global function gf ∈P:
AllF = all functions under gf
Tgf = CallGraph(gf,Allf )
C= ComputeAndFlagComponents(gf ,Tg f)
UnprocC omps =U nprocComps SC
∀K∈C:Kkf vars =Sh∈Khkf v ars
PropagateFreeVars(CoalescedCallGraph(C))
∀S∈UnprocC omps:
Skf vars =Sh∈Shk f vars
if Sis flagged
f= EntryFunction(S)
Skf vars ={v∈Sk f vars |vdepth < fdepth}
∀g∈S:gkf vars =gkf v ars SSkf vars
T= LocalCallGraph(f,S,Tgf )
C= ComputeAndFlagComponents(f,T)
UnprocC omps =U nprocComps SC
∀K∈C:Kkf vars =Sh∈Khk f vars
PropagateFreeVars(CoalescedCallGraph(C))
else
∀g∈S:gkf vars =Skf v ars
LiftFunctions(gf )
Fig. 6. Pseudo-code for the MT-Scheme Lambda Lifting Algorithm.
the symbol table and absolute addresses1in order to easily
determine if a variable can occur free in a given function.
For further details on how syntactic analysis is exploited for
lambda lifting the reader is referred to a previous article
which describes a list-based implementation of Johnsson’s
algorithm[8].
For each global function, gf , in the source program, the
call graph rooted at gf is computed using only the functions
defined under gf . This graph is broken into components
loosely based on strongly connected components and each
component that contains functions defined at different levels
in P’s parse tree is flagged. Instead of breaking the call
graph solely into strongly connected components, the strongly
connected component containing gf , if possible, is itself
broken into multiple components based on the number of
independent loops that include gf . Furthermore, any edges
into gf in the construction of these multiple components are
ignored. The algorithm to group functions in this manner
was obtained by making small changes to the well-known
algorithms to compute strongly connected components that
appear in the textbooks by Gibbons and Gould[9], [10].
After components are computed, each of the components
discovered is added to the set of unprocessed components. The
set of known free variables associated with each component
is computed by taking the union of the known free variables
of its member functions. The coalesced call graph based on
these components is constructed to propagate free variables.
1An absolute address is the declaring function’s depth in Pand the position
of the variable in the declaring function’s parameter list.
Any variable declared at a depth greater than or equal to the
depth of the entry function to a component is not propagated
to that component.
Each of the identified components must be independently
processed to correctly distribute free variables that are local
to each component. If a component, S, is not flagged, then
it can not contain any free variables declared by any of its
member functions. Thus, it suffices to set the free variables
for each function in Sto the union of all the free variables
associated with the functions in S. If a component, S, is
flagged then each function in Sis extended with the free
variables associated with Sthat are declared before the entry
function of S. If Shas more than one entry function, then any
one of the entry functions is used2. The call graph, T, rooted
at the entry function for Sis constructed only considering
edges to functions in S.Tis then broken into subcomponents
using the entry function to S(not gf ) to separate independent
nested strongly connected components. The new components
identified are added to the set of unprocessed components. The
known free variables associated with each of these new com-
ponents is computed and then free variables are propagated
between components of the coalesced graph of S.
B. Revisiting the Example of Section II-C
The call graph in Figure 3(a) is decomposed into the
7 components, M T 0-M T 6, displayed in Figure 7(a). The
strongly connected component S1in Figure 3(b) is divided
into 6 components, namely M T 0and M T 2-MT 6.S1’s entry
function, F0, is an independent component. The components
M T 2and M T 3represent the loop that includes G1and G2.
M T 3is flagged as a strongly connected component that has
functions defined at different levels. M T 4,M T 5, and M T 6
represent the loop that includes R1,R2, and R3. Finally, M T 1
represents the strongly connected component that includes H1,
H2, and H3. This component is not flagged, because all of its
member functions are defined at the same level in the source
program displayed in Figure 1.
The coalesced acyclic graph based on the computed compo-
nents is displayed in Figure 7(b). Free variables are propagated
between components by traversing the coalesced graph as
is done in Danvy’s and Schultz’s algorithm. This traversal
propagates yand zfrom M T 3to M T 2and wfrom M T 6
to M T 5and then to M T 4. The variables sand tare not
propagated from M T 3to M T 2, because they have the same
depth as, G1, the entry function of M T 2. The free variables
associated with each component after propagation through the
coalesced graph are displayed in column 4 of Figure 7(a).
Each function, except those in M T 3, get the free variables
associated with the component that contains them.
The strongly connected component M T 3requires further
processing, because it contains functions defined at different
levels. Each function in M T 3receives yand zas a free
variables, because these are free variables that are associated
2For a component with multiple entry functions a free variable declared by
one entry function can not be free in the other entry functions.
MT Components Member Functions Known Free Vars Propagation Free Vars
MT0 F0 () ()
MT1 H1, H2, H3 z z
MT2 G1 x x y z
MT3 G2, M1, M2 y z s t y z s t
MT4 R1 () w
MT5 R2 () w
MT6 R3 w w
(a) MT-Scheme components, member functions, known free variables, and free variables after propagation.
MT1 MT0 MT2 MT3
MT4 MT5 MT6
(b) Call graph using MT-Scheme components.
Fig. 7. MT-Scheme components, free variables, and coalesced call graph.
MT Components Member Functions Known Free Vars Propagation Free Vars
MT1G2 y z y z
MT2M1 y z s y z s
MT3M2 y z t y z t
(a) M T 3subcomponents, member functions, known free variables, and free variables after propagation.
MT2M T1M T3
(b) Coalesced Graph for M T 3.
Fig. 8. Subcomponents, free variables, and coalesced graph for component MT 3.
with M T 3that are not local to M T 3. To correctly propagate
local free variables, M T 3is itself divided into three com-
ponents, which are displayed in Figure 8(a). None of these
new components are flagged. The graph used to propagate
M T 3’s local free variables is displayed in Figure 8(b). Neither
sfrom M T2nor tfrom M T3are propagated to M T1, because
their depth is the same as the depth of the entry function, G2,
of M T1. The free variables associated with each component
after propagation are displayed in column 4 of Figure 8(a). An
inspection of the final set of free variables identified for each
function reveals the same results as Johnsson’s algorithm.
Figure 9 displays the result of applying the MT-Scheme
lambda lifting algorithm to the program outline in Figure 1. To
lift all functions to the global level in Scheme, currying must
be done explicitly. This means that for a lifted function the
set of free variables becomes its parameter list and its original
parameters become the parameters of a lambda expression that
constitutes its body. The body of the lambda expression is
the body of the original function with references to functions
with free variables transformed into application expressions by
explicitly passing free variables.
C. Discussion and Complexity Analysis
In the worst case, a program with nfunctions only has one
global function, the program’s parse tree has a height of n−1,
every variable declared at level iis referenced at all levels
below i, and every function calls every single function that is
lexically visible to it. This means that every time components
are computed and flagged there will only be 2 components:
one containing the entry function to the component and once
(define (F0 w x y z) (....(H1 z)....(R1 w)....(G1 x y z)....))
(define (H1 z) (lambda (a) (....(H2 z)....))))
(define (H2 z) (lambda (k) (....(H3 z)....z....)))
(define (H3 z) (lambda (i j) (....(H1 z)....)))
(define (R1 w) (lambda (a b) (....(R2 w)....)))
(define (R2 w) (lambda (i j) (....(R3 w)....)))
(define (R3 w) (lambda (a b) (....F0....w....)))
(define (G1 x y z) (lambda (a) (....(G2 y z)....x....)))
(define (G2 y z)
(lambda (s) (...F0...y...z...(M1 y z s)...(M2 y z t)...)))
(define (M1 y z s) (lambda (a) (....s....(G2 y z)...))
(define (M2 y z t) (lambda (a) (....t....(G2 y z)...))
Fig. 9. The lambda lifted version of the source program of Figure 1.
containing the remaining functions in a strongly connected
component which will always be flagged. Thus, we have that
in the worst case n−2flagged components must be processed.
For each of these flagged components, the algorithm must
perform O(n2)steps for the union over the free variables of
its member functions, O(n)steps to find the non-local free
variables of the component, O(n2)steps to distribute non-local
free variables to each member function, O(n)steps to compute
the call graph of every nested component, O(n2)steps to
split the component into subcomponents (the same complexity
as computing strongly connected components), O(n2)steps
to compute the known free variables associated with each
subcomponent, and O(n2)steps to propagate variables through
the component’s coalesced graph. Therefore, the complexity of
the algorithm presented is O((n−2) ∗(5n2+ 2n)) or simply
O(n3).
IV. CONCLUDING REMARKS AND FUTURE WORK
This article presents an improvement to graph-based lambda
lifting that computes the minimal set of extraneous parameters
needed by each lifted function. Our algorithm reduces extra-
neous parameters by not always distributing to every function
in a strongly connected component the free variables that are
local to the component. This improvement comes at the cost
of making the algorithm cubic as Johnsson’s equation-based
algorithm. The advantage of our algorithm is that it exploits
the representation of programs as graphs which is a common
technique used in the implementation of functional languages.
Based on the observations made in this article, the first
author and Ulrik P. Schultz are collaborating on an effort to
develop an O(n2)graph-based lambda lifting algorithm that
computes the minimal set of extraneous parameters needed by
each function. The process of distributing local free variables
takes place when the entry function to a strongly connected
component, with functions defined at different levels in the
program, dominates the paths between mutually exclusive
subsets of functions within a component. This has lead us to
believe that dominator trees[11] can be exploited in the process
of lambda lifting. An interesting feature of the dominator
tree of a call graph is that independent loops dominated by
a function are represented as different branches out of the
dominating function. Since dominator trees can be computed
in linear time[11], the need to repeatedly compute subcompo-
nents may be eliminated leading to a quadratic time optimal
lambda lifting algorithm.
Our main motivation for developing the algorithm presented
in this article is partial evaluation. At runtime, functions can be
specialized based on the bindings of their free variables[12].
To specialize functions in this manner, programs are lambda
lifted in order to know the complete set of free variables
at specialization time during program execution. Instead of
allocating a closure and copying the bindings of free variables
to the allocated closure, functions can be specialized at runtime
by replacing references to free variables with references to
constant values. These constant values are the bindings of the
free variables that would be stored in the closure. A lambda
lifter that computes the minimal set of extraneous parameters
needed by a function reduces the amount of work dynamically
done by a partial evaluator.
V. ACKNOWLEDGEMENTS
The authors would like to thank Ulrik P. Schultz, Olivier
Danvy, and Sven-Bodo Scholz for our discussions about
lambda lifting. These discussions lead to new perspectives on
our insights that have significantly improved the presentation
in this article.
REFERENCES
[1] Jacob Matthews and Robert Bruce Findler and Paul Graunke and Shri-
ram Krishnamurthi and Matthias Felleisen, “Automatically Restructuring
Programs for the Web,” Automated Software Engineering, vol. 11, no. 4,
pp. 337–364, 2004.
[2] C. Consel, “A Tour of Schism: A Partial Evaluation System for
Higher-Order Applicative Languages,” in Proceedings of the ACM
SIGPLAN Symposium on Partial Evaluation and Semantics-Based
Program Manipulation. ACM Press, June 1993, pp. 145–154.
[Online]. Available: citeseer.ist.psu.edu/consel93tour.html
[3] D. P. Oliva, J. D. Ramsdell, and M. Wand, “The VLISP Verified
PreScheme Compiler,” Lisp and Symbolic Computation, vol. 8, no. 1-2,
pp. 111–182, 1995.
[4] T. Johnsson, “Lambda Lifting: Transforming Programs to Recursive
Equations,” in Proceedings of a Conference on Functional Programming
Languages and Computer Architecture. Springer-Verlag New York,
Inc., 1985, pp. 190–203.
[5] O. Danvy and U. P. Schultz, “Lambda-Lifting in Quadratic Time,”
Journal of Functional and Logic Programming, vol. 2004, no. 1, July
2004.
[6] S. L. Peyton Jones, The Implementation of Functional Programming
Languages, ser. Prentice-Hall International Series in Computer Science.
Prentice-Hall International, 1987.
[7] S. L. P. Jones and D. Lester, Implementing Functional Languages, ser.
Prentice-Hall International Series in Computer Science. Prentice-Hall
International, 1992.
[8] M. T. Moraz´an and B. Mucha, “Exploiting Syntactic Analysis for
Lambda Lifting,” in Proceedings of the International Conference on
Programming Languages and Compilers, H. Arabnia, Ed. CSREA
Press, 2005, pp. 130–136.
[9] A. Gibbons, Algorithmic Graph Theory. Cambridge University Press,
1985.
[10] R. Gould, Graph Theory. The Benjamin/Cummings Publishing Com-
pany, Inc., 1988.
[11] Stephen Alstrup and Dov Harel and Peter W. Lauridsen and Mikkel
Thorup, “Dominators in Linear Time,” SIAM Journal on Computing,
vol. 28, no. 6, pp. 2117–2132, 1999.
[12] M. T. Moraz´an, “Towards Closureless Functional Languages,” in Pro-
ceedings of the International Conference on Programming Languages
and Compilers, H. Arabnia, Ed. CSREA Press, 2005, pp. 57–63.