Professional Documents
Culture Documents
Andrew Tolmach
Portland State University
apt@cs.pdx.edu
take this approach. For example, the Glasgow Haskell instance of = 81 : : : : 8n : , written > , if there
0
compiler now uses an explicit 2nd-order representation exists a substitution S with domain f1 ; : : : ; n g such
to facilitate type class operations [16]. A more elabo- that S ( ) = .
0
rate formal framework also based on this approach has For reasons discussed more fully in the next section, we
recently been proposed by Harper and Morrisett [10]. follow Wright [20] in requiring that the dening expres-
Although this idea is not new, to our knowledge it has sion of a generic let clause be a syntactic Value, i.e.,
never been used to build a working garbage collector. a variable, constant, or -expression. As usual, recur-
There are two main reasons for this lack of implemen- sive denitions must be explicit functions, and recursive
tation experience. First, it has been feared that pass- values cannot be used polymorphically within their own
ing type information explicitly would cause unaccept- denition. let-bound functions that bind type param-
able runtime overheads. Second, existing compilers are eters are termed generic. We term each instantiation
generally not set up to perform the appropriate type point of a variable bound to a generic function a point
manipulations in the front end, nor to pass type infor- of mention for that function.
mation from the front end to the runtime system. This Programs in our explicitly-typed 2nd-order language are
paper attempts to ll the gap by describing a practi- derived from source language programs by explicitly ab-
cal implementation of explicit type parameterization as stracting over the type variables in each generalizing let
part of a tag-free collector for a dialect of ML. and letrec (using the binding operator ), and explic-
A key feature of our implementation is that it manipu- itly applying each generic function to instance types at
lates runtime type descriptions in a lazy manner. Under its point of mention (using curly braces fg to surround
this approach, the executing program can keep track the type arguments). To make the role of each type
of the correct instantiation context for its polymorphic variable clear, we also attach a type annotation to each
variables using only a single dynamic list pointer. variable binding. The resulting syntax is shown in Fig-
Although this paper is devoted to garbage collection, ure 2. The transformed program can be obtained as
the machinery it describes could easily be applied to a byproduct of running standard Hindley-Milner type-
other \type-conscious" applications such as overloaded inferencing process on the original program. Each appli-
printing, debugging, and non-uniform data representa- cation of a (LET1 ) or (LETREC) rule results in a type
tion. abstraction, whose parameters are exactly the bound
types in the type scheme generated using the Clos rule.
Each application of a (CONST) or (VAR) rule results
2 Explicit Parameterization in a type application, whose instance parameters are
exactly the types in the range of the substitution that
We illustrate the principles of explicit parameterization witnesses the > relation. We omit abstraction and ap-
using a simple subset of ML expressions plication when the set of generalized variables is empty.
Mitchell and Harper [9] give a formal framework and
e ::= c j x j e1 e2 j x:e1 j proof of equivalence between original and 2nd-order pro-
let x = e1 in e2 j grams for a similar language.
letrec f = x:e1 in e2 j Figure 3 shows a simple polymorphic program and its
if e1 then e2 else e3 j 2nd-order translation.
e1 ; e2
Here x represents a variable and c represents one of a 3 Runtime Type Parameters
collection of built-in constants including at least unit,
integers,
oats, booleans, and the usual constructors The utility of the 2nd-order representation for theoreti-
and destructors for pairs and lists. cal studies and compile-time optimizations such as rep-
We dene types as resentation analysis [12] is well-established. Our pur-
pose in this paper is to execute 2nd-order programs, but
::= j 1 ! 2 j 1 2 j list j using the standard 1st-order compiler. The key idea is
j j
unit int float boolj j ::: to treat type parameters and values as (more or less)
normal parameters and values. In particular, we dene
where is a type variable. We dene type schemes as a runtime format for type descriptions, and generate
code that builds and passes these descriptions at run-
::= 81 : : : n : time just like normal parameters. For convenience, we
TypeOf(c) >
TE > c : (CONST)
TE (x) >
TE > x : (VAR)
TE fx 7! g > e :
0
(ABS)
TE > x:e : ! 0
TE > e1 : ! TE > e2 :
0 0
(APP)
TE > e1 e2 :
TE > e1 : 0
TE fx 7! ClosTE ( )g > e2 : e1 2 Values
0
TE > let x = e1 in e2 :
(LET1 )
TE > e1 : 0
TE fx 7! g > e2 : e1 62 Values
0
TE > let x = e1 in e2 :
(LET2 )
TE > e1 : TE > e2 :
0
(SEQ)
TE > e1 ; e2 :
let f = x.(x,x)
in let g = (y,z).(f y, f z)
in g (3,2.0);
g (1.0,0)
8
let f: : !
( ) = :x:.(x,x)
8 !
in let g: ,
.(
) (( ) (
)) = ( ,
).(y: ,z:
).(f { } y, f {
} z)
in g {int,float} (3,2.0);
g {float,int} (1.0,0)
8
let g': ,. ! !
( ) = (;).y: .x:.(x,y)
in let f: .8 !
(( int) ( bool)) = :x:.(g' {int,} 2 x, g' {bool,} true x)
in f {int} 3; f {real} 3.14
Figure 4: Code involving free type variables, before and after -lifting.
datatype rttd =
Integer (* simple integer *)
| Float (* simple float; only legal within product list *)
| Static_pointer (* pointer to non-gc'ed memory (e.g., code) *)
| Closure_pointer (* pointer to self-describing closure *)
| Record of rtrd (* pointer to heap record *)
| Type_var of int (* index into type environment *)
and rtrd =
Sum of rtrd vector (* list of variant types; indexed by header tag *)
| Product of rttd vector (* list of record fields *)
datatype type_env =
Tenv of {instance:rttd vector, mentioner:type_env}
| Empty_tenv
a runtime record description (rtrd), which details the tional space and time. Moreover, only the garbage col-
contents of the record. A simple product record is de- lector needs to examine the entirety of a type descrip-
scribed by listing the rttds of its elds, which may in- tion, and it only needs to do so for the types of variables
clude nested Records. For sum types, it is necessary that happen to be live at a collection point. Thus, much
to describe the layout of each possible variant; the ac- of this instantiation work would be completely wasted.
tual variant at hand is determined by consulting a tag We therefore choose instead to implement a lazy form of
in the record. Note that this mechanism can describe instantiation. The key idea is to represent polymorphic
recursive types. Finally, function closure records are types in parametric form and to interpret them relative
self-describing (see Section 5.2), so they receive a spe- to an instantiation vector of types. Concretely, we in-
cial Closure pointer descriptor. troduce an rttd constructor of the form Type var(i),
The key questions are how to represent type expressions where i is an integer index into an auxiliary vector inst
that contain type variables, and how to instantiate such of rttds. To interpret a type description containing the
expressions at runtime, after the actual values of the rttd Type var(i), one simply fetches inst[i]. Instan-
type variables have become available. Polymorphic type tiating a description now amounts simply to forming
expressions arise only inside generic functions, and their an association between an rttd and an appropriate in-
type variables become available when the function is stantiation vector. Of course, whenever we transmit an
passed its type environment vector. In principle, there- rttd containing type variables, e.g., to the garbage col-
fore, every type expression within the function body lector or as a type parameter to a generic function, we
could be instantiated as soon as the type environment must take care to transmit the appropriate instantiation
is seen. Perhaps the simplest approach to instantiation vector as well.
would be to have the compiler generate code to con- Appropriate instantiation vectors are already at hand.
struct, at runtime, a completely fresh description of the Recall that after the -lifting transform described in
instantiated type, using the polymorphic type as a tem- Section 3, each type variable mentioned in a function
plate and lling in the actual type parameters in place appears in that function's formal type-environment vec-
of the type variables. Under this approach, there would tor parameter. Thus we can easily assign the type vari-
actually be no need to represent polymorphic types at able a runtime description Type var(i) where i is the
runtime, and the rttd elds already described would variable's index in the current environment.
suce.
But generating such instantiated descriptions at run- Figure 5 shows a concrete representation for type envi-
time would quite time-consuming; worse, these descrip- ronments, called tenvs. There is a tenv corresponding
tions would have to be heap-allocated, costing addi- to each mention of a generic function; it is constructed
by the function that contains the point-of-mention (the
mentioner of the generic function) and passed as an ex- be stored in a table indexed by function code address.
plicit argument to the mentioned function.2 A tenv con- Of course, actually unfolding the program would prob-
tains a instance vector of rttds corresponding to the ably increase code size unacceptably (although recent
type variables referenced by the function, as described results of Jones [11] suggest otherwise). Moreover, do-
above. ing so might be impractical in a separate compilation
An rttd listed in a tenv instance may be a Record environment, where the \whole program" is never seen
description or any simple data type that occupies one at once. But we could generate code in the ordinary,
word, i.e., Integer or Static pointer. Moreover, it folded-up program to keep track of which \copy" of a
may itself be a type parameter of the form Type var(j). function we would be executing, were we running the un-
In this case, the parameter index j must be interpreted folded program [18]. This tracking could use essentially
in the type environment of the mentioner. The simplest the same dynamic list technique as the one described
way to make this environment available to the generic above. It could also be done using nested lookup tables,
function is for the the mentioner to incorporate a pointer or perhaps a single table with some form of hashing.
to it into the tenv; the mentioner eld contains exactly Moreover, we can certainly generate an appropriate ta-
this pointer. Naturally, this environment may itself con- ble containing fully instantiated type environments for
tain type variables, necessitating the consultation of a each function and \copy." Conveniently, the type infor-
further type environment, and so forth. However, this mation can be digested to an appropriate form for dif-
recursion is limited by the static depth of nesting of ferent applications (e.g., full user-level descriptions for
generic functions, which in practice is surely quite small. debugging, record layout information for garbage collec-
tion, etc.) without changing the copy-tracking code.
Figure 6 shows code from an earlier example re- Some \two-nger" garbage collectors have been im-
expressed using these representations. Type annota- plemented by passing type-specic traversal functions
tions of variables show the rttd that would be passed rather than type descriptions that must be interpreted
to the garbage collector for that variable. by a universal collector function [8]. Such approaches
It is tempting to believe that the mentioner eld is dis- make elegant use of rst-class functions, but we suspect
pensable. Often, the mentioner and caller of a function that the costs of applying the general closure construc-
will be one and the same, in which case the mentioner's tion mechanism for these functions will outweigh the
tenv could, in principle, be obtained via the call stack. benets of avoiding interpretation.
In general, however, the mentioner may no longer exist
at the point of call, and no stack-based mechanism can
connect mentioner and callee appropriately. 5 Implementation
This representation of rttds and tenvs was designed to
minimize the amount of dynamic allocation required to 5.1 Gallium
support runtime type resolution. Note that rttds and
the instance vectors of tenvs are completely static. In These ideas have been implemented by extending Xavier
fact, only the tenv records themselves, i.e., the associ- Leroy's Gallium compiler [12] for the Caml Light dialect
ations of instance vector with mentioner's tenv, ever of ML [13]. The compiler has two main parts: the front
need to be allocated dynamically. Of course, care3 must end generates an intermediate language called \C{ {";
be taken to scan tenvs during garbage collection! the back end generates native code for a MIPS proces-
Harper and Morrisett [10] give an abstract treatment sor, using a direct-style, stack-based compilation model.
of a system similar to ours, intended to support a wide The front end was originally designed to support rep-
range of applications. They characterize type instantia- resentation analysis based on compile-time type infor-
tion as normalization in simply-typed -calculus of type mation. Gallium was chosen for this experiment pri-
expressions. They point out that such normal forms can marily because C{ { already annotates identiers and
be found using a variety of strategies, including call-by- temporaries with crude type descriptions, namely in-
need or call-by-value. Viewed in this framework, our teger,
oat, and pointer (to anything). The back end
\lazy" approach appears to be an implementation of uses this information to type registers and stack frame
call-by-need, while the \eager" instantiation approach locations.
we initially considered but rejected is call-by-value. Ordinarily, Gallium uses a simple, non-generational,
There are many other possible representations for type depth-rst copying collector. Records are self-
environments, which we have not fully explored. One describing: at allocation, each record is given a header
alternative approach is based on the observation that that points to a static record layout description, which
a polymorphic program can be \unfolded" into a is generated from the record constructor denition. The
monomorphic one by making a separate copy of each layout description classies each record eld as integer,
generic function each time it is mentioned. If we gen-
oat, or pointer; it also contains a tag to distinguish
erated, type-checked, and executed the unfolded pro- variants of concrete types. Values in polymorphic elds
gram, each function code address would uniquely de- are always boxed, even if they are integers; the corre-
termine a (monomorphic) type for all the function's sponding eld description is thus always \pointer." This
variables. The resulting environment descriptions could technique obviates the need for tag bits to distinguish
integers from pointers, at the cost of storing integers
2 In some cases, the mentioner can reuse an existing tenv ineciently. Live roots are passed to the garbage col-
rather than constructing a new one; see Section 5.3. lector via frame descriptors, which are embedded in the
3 The rttd for a tenv is a constant known to the collector; it code stream at each function call site, and can be ac-
is similar to that of an integer list. cessed by the collector via the function's return address.
let f = ftenv.x:Type_var(0).(x,x)
in let g = gtenv.(y:Type_var(0),z:Type_var(1)).
(f Tenv{instance=[|Type_var(0)|],mentioner=gtenv} y,
f Tenv{instance=[|Type_var(1)|],mentioner=gtenv} z)
in g Tenv{instance=[|Integer,Float|],mentioner=Empty_tenv} (3,2.0);
g Tenv{instance=[|Float,Integer|],mentioner=Empty_tenv} (1.0,0)
Figure 6: Representing explicit type parameters using the example of Figure 3. The symbols [| and |] construct a
vector from the expressions listed between them.
Each frame descriptor consists of a list of pointers (in rtrd contains type variable elds. If so, the closure
registers or the stack frame) that are live at the given record must contain the tenv current when the closure
function invocation. To trace all local roots, the garbage is built and its rtrd generated. In some cases this tenv
collector must walk the call stack, tracing all pointers will already be present in the closure as a free variable in
in each active frame descriptor. There is also a static its own right; in other cases, it must be forced into the
list of global roots. The collector is coded in C. closure explicitly. In either case, the oset of the tenv
within the closure record varies from one closure to an-
5.2 Adding Type Descriptions other, so this value is also embedded in the code stream
at a (second) xed oset before the function's starting
For these experiments, we made signicant changes to address. Note that the closure may also contain other,
the garbage collector's method for tracing data. Lay- dierent, tenv values needed as free variables, e.g., to
out information is no longer obtained from record head- set the current tenv value within the function itself. In-
ers; indeed, record headers are no longer present except4 deed, a given free variable value in the closure may be
when necessary to distinguish variants in sum types. traced at dierent times by the garbage collector using
Instead, the frame descriptors are expanded to include two completely dierent descriptions and tenvs.
a tenv for the function and an rttd for each live register
or frame slot that might contain a pointer. The collector 5.3 Compiler Changes
traverses type information in parallel with data.
The C representation of an rttd ts in a single word, as The original C{ { attaches crude type descriptions (in-
follows: Type var(i) is represented simply by i, which teger,
oat, or pointer) to the results of loads, function
is certain to be small (say < 0x100); Record(rtrd) is arguments, and function return values. We rene the
represented by the address of the rtrd, which is certain description of pointer-valued data by including the ad-
to be large (say >= 0x1000); the remaining constructors dress of an rttd describing what is pointed to. The
are represented by constants in the intervening range. back end now picks up this address for use in frame
An rtrd is represented as a C union in a straightforward descriptors; it required no other signicant changes.
way. The front end required more substantial revisions. Type
Each rttd is statically allocated; if it corresponds to annotations for generic functions and their mentions are
a polymorphic variable, it will contain one or more extracted during the type-checking phase and stored
Type vars that reference the function's tenv. The back in the abstract syntax tree. A newly-added process-
end arranges to store the current tenv at a xed oset ing phase inserts formal and actual parameters repre-
in the stack frame before each procedure call or invo- senting type environments and code for building tenv
cation of the allocator. The C representation of a tenv pairs where needed. The compiler avoids constructing
is just a two-word pair; each eld of the pair points to a new tenv when the environment of the mentioned
a statically generated record, so the pairs themselves and mentioning function are the same. This is quite
are the only components in the type description scheme common because recursive functions always call them-
that are dynamically allocated. selves with the same tenv they were passed by the ini-
tial, non-recursive call. As another optimization, if the
Closures introduce considerable complications. Even in mentioner eld is empty, the tenv can be statically allo-
monomorphic type system, it is impossible to deter- cated; this circumstance arises naturally when the men-
mine the number and type of a function's free variables tioning function's tenv is empty, and can be forced arti-
from the function's type, so closure records must be cially if the instance vector contains no type variable
self-describing. Gallium uses simple
at closures;5 the elds.
function's code address is always in the rst eld. To
avoid a header eld within the closure itself, we embed This phase precedes the existing uncurrying optimiza-
an rtrd describing the closure record in the code stream tion phase, so that the latter can remove type instanti-
at a xed oset before the function's starting address. ation calls where possible; no changes to the uncurrying
phase were required except minor modications to keep
Polymorphism opens the possibility that a closure's more precise track of type information in the uncurried
4 They are also used to store length information for arrays, code.
which we do not discuss further. The closure construction phase has been modied to
5 Mutually recursive functions share a closure, as described
in [4, Section 10.2]. treat free type variables like ordinary free variables, and
to install tenvs in closures where needed and not al- 6 Assessment
ready present. The free type variables of a function are
calculated as the type variables that appear in the or- 6.1 Performance
dinary free (value) variables of that function. Since a
tenv may itself be a source of free variables, processing We have only preliminary performance information. Ta-
order within this function is delicate. ble 1 shows the relative performance of the new sys-
The nal front end phase, which emits C{ {, is enhanced tem vs. ordinary Gallium on a number of synthetic
to generate data segments containing static rtrd records benchmarks, intended to exercise the allocator and the
and tenv instance vectors; the addresses of these seg- type representation mechanism. Benchmarks were con-
ments are associated with pointer-typed values in C{ {. ducted on a Decstation 5000/240. sieve is a prime
A memoization mechanism is used to avoid generating number generator. sumlist sums a list of integers and
multiple identical rtrds from a single source le; a sim- sumlistf sums a list of
oats. pair repeatedly applies
ilar mechanism at link time would clearly be desirable. a function similar to repair in Figure 7 to integers and
pairf applies it to
oats; each call to pair builds a
fresh dynamic closure. pclist constructs and evalu-
5.4 Garbage Collector Changes ates a list of polymorphic closures. Ratios of run times
(total cpu times) are presented for three dierent heap
The garbage collector is substantially revised to cope sizes selected independently for each benchmark: Small
without record headers. Some records, e.g., members Heap is close to the smallest heap in which the bench-
of variant types, still have header elds, but these are mark would run, with frequent collections; Large Heap
treated as ordinary data elds by the collector. The is large enough that no collections occurred; Medium
principal change is that type information is now taken Heap is a size somewhere in between with a \comfort-
from frame descriptors and, on recursive traversals, from able" number of collections.
rtrds, rather than from headers. The collector's inner
copy loop is now parameterized by an rttd and tenv in The gures presented are encouraging. Total alloca-
addition to source address and contents. tion decreased for all benchmarks but pairf, because
the new system's occasional need to heap-allocate tenvs
We make fundamental use of the collector's depth-rst was more than oset by its use of unboxed integers and
traversal strategy here, since there is no convenient lack of need for headers. In fact, both ordinary Gallium
place to queue typing information that a breadth-rst and our new system use a very naive representation for
collector would require. Of course, we could use the in- sum types which requires each list cell to have a tag
formation available at record creation time to store a eld; if this were xed, the improvement in the new
pointer to a monomorphic type description within the system would be even more marked for list-based pro-
record. This approach would eectively require us to grams like sumlist. The poor behavior of pairf could
reintroduce headers, though it would still permit us to be addressed by the hoisting optimization described in
avoid integer vs. pointer tags. A slightly more at- Section 7.1. Moreover, execution times in the absence of
tractive possibility is to avoid attaching a header un- garbage collection (Large Heap) also decreased (except
til the record is actually copied at collection time6 but for pairf) in line with decreased allocation, suggesting
even this option seems likely to cut signicantly into the that passing type parameters need not be a signicant
space savings we hope to achieve. cost.
Ordinarily, Gallium uses headers to store forwarding ad- Comparing performance when collections occurred is
dresses during copying collection. Since not all bits are dicult because the percentage of live data at a col-
needed for tagging, it is easy to distinguish forwarding lection can vary enormously depending on the precise
pointers from unforwarded headers. In the new scheme, point where the collection occurs. However, we can con-
forwarding pointers must be placed in data elds. Since clude that the new system behaves about as well as the
forwarding pointers cannot, in general, be distinguished old at moderate collection rates (Medium Heap), but
from integers or real numbers, they can only be placed in that some benchmarks behave signicantly worse under
pointer elds. The rst eld guaranteed to be a pointer the new system when garbage collection is very frequent
(if any) is used for forwarding; its oset is stored in (Small Heap). We believe that this behavior is due in
each rtrd. Either heap or static pointer elds can be part to increased amounts of live data (holding tenv
used, as forwarding pointers can be distinguished by parameters and values), but also to poor engineering of
a range check or by setting otherwise unused low-order the collector code, which can be improved. For example,
bits in the pointer. For simplicity, abstract elds, which nearly 25% of sumlistf's execution time was devoted
might or might not contain pointers depending on how to managing the hash table for forwarding
oats, which
they are instantiated, are not used. Records contain- suggests that other forwarding mechanisms should be
ing only numbers (e.g., pairs of integers) are forwarded investigated. We also expect the recursion optimization
by recording them in an auxiliary hash table and using described in Section 7.1 to be useful.
the rst eld to store the forwarding pointer.7 Other
possible approaches include using a bit map in place of
a hash table, or forcing all-numeric records to have an 6.2 Experience
extra eld for forwarding purposes.
6 Greg
Implementing support for explicit type parameteriza-
Morrisett suggested this idea. tion for a full-featured language turned out to be some-
7 John Reppy suggested this technique. what more complex than we had expected, particu-
larly in the front end. All told, at least 20% of the
8
let pair: . !
( ) = .x:.(x,x) in
8 !
let repair: .
(( ) ( )) = .y.pair {( )} (y,y) in
let doit n = if n = 0 then () else repair {int} n; doit (n-1)
front-end code (which totals about 10,000 lines of ML) at one mention at least. This optimization is analo-
required modication, and even more radical changes gous with choosing argument registers for calls to known
would probably improve the reliability of the code. functions so as to avoid register moves [4, p. 159].
The collector itself was not too dicult to implement. If a function's mentioner and caller are the same, as is
One essential aid was was provision of a debugging usually the case, it is possible for the garbage collector to
mode, in which the allocator stores a fully instanti- obtain the mentioner's tenv via the stack frame rather
ated type description for each record in a separate ta- than from the called function's tenv mentioner eld.
ble. Whenever the collector scans a record it can com- The mentioner eld could take on a special constant
pare the stored type with the parametric type it be- value to signal this situation, allowing the tenv to be
lieves to be correct. A formal proof that the collector built statically. The resulting scheme might resemble
views record types correctly at all times would certainly Aditya's use of \hints" [1, 2].
strengthen our work. As noted in Section 5.3, the tenv passed to the rst,
non-recursive call of a recursive function is retransmit-
7 Future Work ted to each recursive call of the function. If this tenv
is heap-allocated, it would be desirable to avoid pass-
ing it repeatedly in this manner. This is because, in
We will continue to explore our collector's performance addition to the usual costs of passing and storing a pa-
on a range of more realistic programs. Since Gallium rameter, each copy of the tenv must itself be scanned
is a stack-based system, many programs will allocate by the garbage collector, so it will appear as an extra
much less rapidly than our benchmarks, so we expect local root in the function's frame. If a collection occurs
performance dierences in heap management will be less during a deep recursion, the addition of a single extra
signicant. root to each recursive frame can considerably increase
its cost, even if the tenv structure itself is trivial to scan.
7.1 Implementation Optimizations A solution would be to maintain the current tenv in a
global location rather than in the stack frame. Ordinar-
Analysis of our preliminary performance results sug- ily, the code to call a function would store the current
gests a number of useful optimizations that we have not tenv into the stack frame before the call and restore it
yet implemented. after the return; the start-up code within the function
It is fairly inexpensive to pass tenvs as parameters or would set a new current tenv. Calls to recursive func-
storing them in the stack frame, but constructing new tions (and any other calls statically known not to alter
tenvs dynamically is expensive because it requires heap the tenv) would simply leave the current tenv unal-
allocation. We already avoid generating a new tenv tered, storing an appropriate mark into the stack frame
in certain common cases, e.g., when the tenvs of men- to advise the garbage collector to continue using the
tioned and mentioning function are the same, but other current tenv when processing this frame.
optimizations are possible. For example, if the tenv Consider again the code in Figure 7, in which the recur-
of the mentioned function is a subset of the mention- sive function doit repeatedly invokes the generic func-
ing function, possibly permuted, the type variables of tion pair indirectly via repair. The optimization just
the mentioned function could be renumbered to refer described won't prevent repair from building n identi-
directly to slots in the mentioning function's tenv. Of cal dynamic tenvs for pair, one on each call. This prob-
course, in general this cannot be done for all mentions lem could be solved by taking advantage of the fact that
of a function, but constructing a tenv can be avoided
the explicitly typed form of repair is curried. We could [6] H.-J. Boehm and M. Weiser. Garbage collection in
arrange to construct pair's tenv just once, by hoisting an uncooperative environment. Software|Practice
the code to generate it above repair's inner abstrac- and Experience, 18(9):807{820, Sept. 1988.
tion, and hoisting the partial application of repair (to
fintg) out of the recursion in doit. Evidently, this opti- [7] B. Goldberg. Tag-free garbage collection for
mization would need to be applied in lieu of uncurrying. strongly typed polymorphic languages. In Proc
ACM SIGPLAN '91 Conf. on Prog. Lang. Design
and Implementation, pages 165{176. ACM Press,
7.2 Alternative GC Methods 1991.
For completeness, we could also compare performance [8] B. Goldberg and M. Gloger. Polymorphic type re-
of our collector with a reconstruction-based collector in construction for garbage collection without tags. In
the style of Goldberg [7, 8] or Aditya [2], possibly using Proc. 1992 ACM Converence on Lisp and Func-
specialized collector functions. It would also be inter- tional Programming, pages 53{65, June 1992.
esting to try producing completely specialized versions [9] R. Harper and J. C. Mitchell. On the type structure
of polymorphic functions, as Jones does for overloaded of standard ml. ACM Trans. Prog. Lang. Syst.,
functions in Gofer [11]. Finally, we should compare our 15:211{252, Apr. 1993.
approach with that of conservative collection [6].
[10] R. Harper and G. Morrisett. Compiling with non-
7.3 Other Applications paramteric polymorphism (preliminary report).
Technical Report CMU-CS-94-122, Carnegie Mel-
We also hope to extend our results beyond garbage col- lon University School of Computer Science, Feb.
lection. If manipulating explicit type information is suf- 1994.
ciently cheap, there are many other potential applica- [11] M. P. Jones. Partial evaluation for dictionary-free
tions. These include providing better type information overloading. Technical Report YALEU/DCS/RR-
for debugging; providing an eval-like capability; use 959, Yale University Dept. of Computer Scinece,
of more ecient and specialized data formats; and im- Apr. 1993.
plementing more sophisticated forms of representation
analysis, e.g., coding parametric polymorphic functions [12] X. Leroy. Unboxed objects and polymorphic typ-
to behave dierently according to the size of their argu- ing. In Proc. Nineteenth Annual ACM Symp. on
ments. Principles of Programming Languages, pages 177{
188, New York, January 1992. ACM Press.
Acknowledgements [13] X. Leroy and M. Mauny. The Caml Light system,
Release 0.6, Documentation and User's Manual,
Xavier Leroy graciously provided access to the code for 1993.
his Gallium system, and permission to modify it for [14] R. Morrison, A. Dearle, R. Connor, and A. L.
these experiments. Robert Harper provided useful com- Brown. An ad hoc approach to the implementa-
ments on an earlier version of this paper. tion of polymorphism. ACM Trans. Prog. Lang.
Syst., 13(3):342{371, July 1991.
References [15] J. Peterson and M. Jones. Implementing type
classes. In Proc. ACM SIGPLAN '93 Conference
[1] S. Aditya and A. Caro. Compiler-directed type re- on Programming Language Design and Implemen-
construction for polymorphic languages. In FPCA tation, pages 227{236, June 1993.
'93 Conference on Functional Programming Lan-
guages and Computer Architecture, pages 74{82, [16] S. L. Peyton Jones, C. Hall, K. Hammond, W. Par-
June 1993. tain, and P. Wadler. The Glasgow Haskell compiler:
a technical overview. In Proc. UK Joint Framework
[2] S. Aditya and C. H. Flood. Garbage collection for for Information Technology (JFIT) Technical Con-
strongly-typed languages using run-time type re- ference, Keele, 1993.
construction. In Proc. 1994 ACM Conference on
Lisp and Functional Programming, June 1994. [17] M. Tofte. Operational Semantics and Polymorphic
Type Inference. PhD thesis, Edinburgh University,
[3] A. W. Appel. Runtime tags aren't necessary. Lisp 1988. CST-52-88.
and Symbolic Computation, 2:153{62, 1989.
[18] A. P. Tolmach. Debugging Standard ML. PhD the-
[4] A. W. Appel. Compiling with Continuations. Cam- sis, Princeton University, Oct. 1992. Also Prince-
bridge University Press, 1992. ton Univ. Dept. of Computer Science Tech. Rep.
[5] L. Augustsson. Implementing haskell overloading. CS-TR-378-92.
In FPCA '93 Conference on Functional Program- [19] P. Wadler and S. Blott. How to make ad-hoc poly-
ming Languages and Computer Architecture, pages morphism less ad hoc. In Proc. Sixteenth Annual
65{73, June 1993. ACM Symp. on Principles of Programming Lan-
guages, pages 60{76, January 1989.
[20] A. K. Wright. Polymorphism for imperative lan-
guages without imperative types. Technical Report
TR93-200, Rice University Dept. of Computer Sci-
ence, Feb. 1993.