• Aucun résultat trouvé

Now that we have shown some basic uses of our system, we will present a more significant application, which is a generational garbage collector, which will demonstrate how strong update can be used even in the absence of static information about the existing aliases.

The basic idea of a type-preserving GC, proposed by Wang and Appel [2001], is to layer a stop&copy collector on top of a region calculus, where the whole heap is placed in a single region and where thecopyfunction copies the heap from thefromregion to a new toregion and then frees thefromregion.

Because the type of the heap contains region annotations which will necessarily be dif-ferent before copying than after, thecopy function cannot just be of type ∀t.t→t but instead has to be of the form∀t.M F t→M T twhereF andTare the source and desti-nation regions,trepresents the source level type that should be preserved, andMis a type function that translates it into its lower-level representation, annotated with details that the mutator does not care about. E.g. in order to work correctly, thecopyroutine might require things like tag bits and mark bits or in case of generational GC it will need to make sure the mutator obeyed the generation barriers and provides a correct remembered set. All those added constraints will need to be somehow encoded inM sincecopy has obviously no control overt. Let us sketch the types of a type-preserving generational GC.

6.1 Generational GC

Let us assume that the source language whose heap we want to collect only has integers, immutable pairs, and mutable ref-cells. Let us take a very simple case where we have 3 regions: all the ref-cells go into regionR, whereas the pairs are divided between the nurseryY and the old space O. Since all data is immutable except for ref-cells, we can take theRregions as a conservative approximation of the remembered-set. We will use the source-level types for theintendedtypes:

τ::=int|pairτ τ |refτ|muτ|t

To translate those source-level types into their low-level representation (their actual type), we create a type functionMwhich takes the three regions and the source type as param-eters: M r o y τ is the low-level representation of the source-level typeτ, it will include extra fields such as a place for forwarding pointers and will encode the generational con-straint to make sure that no object in O points directly into Y. This constraint will be obtained by instantiating theoandyarguments ofM with the same regionOfor objects in the regionO.

M r o y(int) ⇒int

M r o y(refτ) ⇒ ∃n:Nat.τatr.n

M r o y(pairτ1τ2)⇒ ∃x.∃P:x∈ {o, y}.∃n:Nat.(τ1, τ2)atx.n

The translation of a ref-cell is an intuitionistic reference to a location in regionR, as seen by the use of an existential package to hide the actual location insideR. For the translation of a pair, we have to express the fact that it can live either in the nursery or in the old region and we cannot statically tell which, so we use region aliases. The regions’s types while the

GC[t, ...]{r, o, y}(x:M r o y t, x0:∃t.tatr.0, k:∀[y0]{r, o, y0}(M r o y0t)0)

=GCcopy[y, o, ...](x:M r o y t,

λ[...]{...}(x0:M r o o t).

GCredirect[r, o, y, ...](x0:∃t.tatr.0, λ[...]{...}().

freergny;

lety0=newrgn in

GCwiden[...](x0, λ[...]{...}().

k[y0](x0))))

Fig. 13. Sketch of the GC

mutator is running are:

R7→λn.λt.M R O Y t

O7→λn.λ(t1, t2).(M R O O t1)×(M R O O t2) Y 7→λn.λ(t1, t2).(M R O Y t1)×(M R O Y t2)

Note how the type ofO callsM with both parameterso andy set toO such that those objects cannot refer toY, thus enforcing the generation barrier. When the collection ofY takes place, the GC, starting from the roots, copies objects fromY toO. Once this is done, it needs to go through the remembered-setRandredirectany reference still pointing toY. To make it possible to freeY, the type ofRshould end up asR7→λn.λt.M R O O t, so as to reflect the fact that no object inY is reachable from ref-cells inR. The redirection is done by scanningRand updating each ref-cell at a time. The type ofRneeds to be kept up-to-date as this proceeds, of course, recording the progress of the boundarymbetween the ref-cells already redirected and the ones left to process:

R7→λn.λt.letr=if(m > n) (O) (Y)inM R O r t

Note how the type ofRneeds to be updated with each redirection step, requiring a strong update, even though no static information about which other data inRorOmight point to the same location we are updating.

6.2 Overview of the GC

Figure 13 shows a sketch of theGCfunction. If you ignore the nesting due to the use of continuation passing style, you can see that the function does the following:

(1) it takes 3 arguments: a pointerxwhich is the single root of the heap, a pointerx0to the beginning of the regionR, and a continuationk;

(2) it first callsGCcopywhich copies recursivelyxfrom the regionY to the regionO;

(3) it then callsGCredirect which traverses the regionRand redirects all references that point toY to point to new copies inO;

(4) then it can free the nurseryY and allocate a new oneY0;

(5) finally the last step before returning to the continuationkis a call toGCwidenwhich is a big no-op that simply updates all the types of the formM r o otoM r o y0so as to allow the use of the new regionY0.

The GC code is split into the following parts:

GCcopy: depth-first copy of an object fromY toO.

GCrmap: an auxiliary higher-order function that scans the regionR, applying a given function to every element. Used by bothGCredirect andGCwiden.

GCredirect: scan the regionRredirecting pointers toY by copying the object toOand redirecting the reference to use the new copy.

GCwiden: update the type of objects inRto refer to the new regionY0. GC: the toplevel function.

In order forGCredirect andGCwiden to be able to do their job, we need both a reference to the beginning and the end of the regionR. We decided to place the reference to the end directly inside the regionRat its location 0, so that the referencex0that point to the locationR.0gives us access both to the beginning as well as the end of the region, and it also has the benefit of ensuring that we do not need to cater to the special case where the region is empty.

6.3 Depth-first copy

To be able to copy parts of the heap, we need to be able to get runtime type information, which our language does not provide directly. So we need to refine the definition ofM to add tags:

InductiveΩτ :Kind:=int: Ωτ |pair: Ωτ →Ωτ →Ωτ |ref: Ωτ →Ωτ

|mu: Ωτ →Ωτ |var:Nat→Ωτ typeMtag(int) = 0

|(pairt1t2) = 1

|(reft) = 2

|(mut) = 3

|(varn) = 4

typeMdatar o y(int) =∃n.snatn

|r o y(pairt1t2) =∃b.∃ :b∈ {o, y}.∃n.(t1, t2)atb.n

|r o y(reft) =∃n.tatr.(succn)

|r o y(mut) =∀t.t

|r o y(varn) =∀t.t

typet1×t2=tup2 (λi.matchiwith0⇒t1| ⇒t2) typeM0r o y t=snat(Mtagt)×Mdatar o y t

Notice how we mapreftypes to references atsuccnso as to ensure that those references do not point to the special location 0 of the regionR. BothMtagandMdatapay attention to thevarcase, but this case should never occur, since it corresponds to an occurrence out of scope. The definition ofM0 is not very useful for recursive types either since it maps them arbitrarily to the void type∀t.t. So we defineM asM0where recursive types have been unfolded once:

typeunfold(mut) =DIsubstitutet(mut)

|t =t

typeM r o y t=M0r o y(unfoldt)

DIsubstituteτ1τ2is a function not shown which replaces every occurrence of the topmost variable inτ1withτ2. Having to give tags to types such asvarandmu is not very satis-factory, but with a more precise definition ofΩτwe can easily rule out free occurrences of

typeTauxK tag=matchtagwith0Unit|1ττ|2τ|3τ|4Nat typeTaux t=matchtwithint()|pairt1t2(t1, t2)|reftt|mutt|varnn typeTfromTag tag(aux:TauxK tag) =

matchtagwith0int|1pair(fst aux) (snd aux)|2ref aux|3mu aux|4var aux typeEqTfromTagt: (t=TfromTag(Mtagt) (Tauxt)) =

matchtwithinteq refl|pairt1t2eq refl|refteq refl|muteq refl|varneq refl typeEq2Eqt1t2f(P:t1=t2) : (f t1=f t2) =matchPwitheq refleq refl

typeGCdispatchTyper o y hfk tag

=∀aux:TauxK tag.lett0=Tfromtag tag auxin{h}(M0r o y t0, fkt0)0

var. We cannot on the other hand rule outmu becauseunfold only does one step of un-folding and removing allmus may require a variable or even infinite number of unfolding steps, so we cannot code it in CiC. All in all, the only remaining problem is that we have to occasionally worry about tags 3 and 4 even though they are never used, and that we have to be careful to only use recursive types whose unfolding is not itself amutype, which is usually easy to ensure. It is interesting to see how thanks to the extra indirection provided by the regions’s types, we can encode the source-level recursive types even though our language does not itself provide recursive types.

Figure 14 shows an auxiliary functionGCdispatchwhich implements a switch statement on objects of typeM r o y t. This is similar to theif function presented in Sec. 5.1. All it does, is extract the tag of its argumentxand use it to look up a branch table. The subtlety in the code is due to the fact that we need the encoded switch statement to refine the typet in each branch although the switch only examines some integer that depends ont. For that we define the functionsTfromTag andTauxsuch thatt = TfromTag(Mtagt) (Tauxt).

This allows us to reflect the info we get about the tag into knowledge aboutt. The function TfromTagis used inGCdispatchType, which is the function that describes the commonal-ity between each branch in the branch table.EqTfromTagandEq2Eqare auxiliary proofs used to show that those functions indeed correctly split and reconstruct the samet. Note that although we have 5 different tags, GCdispatch only has three branches, because it automatically directs the two impossible cases (themuandvarcases) toGCdispatchVoid.

ThisGCdispatch function reproduces within our low-level language the functionality

GCid[tf, hf, hd,aux]{hf hd}(x:tfaux, k:∀[hd]{hf hd}(tfaux)0) =k[hd]x GCidInt=GCid[λaux.snat0× ∃n.snatn]

GCidRef[r] =GCid[λaux.snat2× ∃n.aux atr.(succn)]

GCcopyPair[r, o, on, y, yn, h, by, P:by∈ {o, y},aux] test=rcmp[inskip inbase]x00y inif(test)

GCcopy[r, o, on, y, yn, h,snd aux, y,inskip inbase]

(x1, λ[on]{GCcopyRenvr o on y yn h}(x01:M r o y(snd aux)).

letnew=put[o,aux] (x0, x1)

ink[on+ 1]((1,ho,hinbase,hon, newiii))))) (λ[P6=:¬((b7→ Θ)b=y)]{GCcopyRenvr o on y yn h}().

GCidInt[(λon.GCcopyRenvr o on y yn h), on], GCcopyPair[r, o, on, y, yn, h, by, P],

GCidRef[(λon.GCcopyRenvr o on y yn h), on], k

typically provided by refining pattern matching on inductive definitions or GADTs [Xi et al. 2003], including the fact that some of the branches are eliminated because their typing context contains a contradiction, witnessing the fact that the code is unreachable.

TheGCcopyentry point is shown in Fig. 15. It just usesGCdispatchto either do nothing (viaGCid), if the value to copy is a ref-cell or an integer, or delegate toGCcopyPair. A more interesting part ofGCcopyis the way it describes its region’s types:

typeTupRgno y r=λn.λt.(M r o y(fstt))×(M r o y(sndt)) typeGCcopyRenvr o on y yn h

= (h⊕y7→(TupRgno y r, yn)⊕o7→(TupRgno o r, on))

wherefst andsnd are the usual projection functions for tuples. Since it only accesses

GCrmap[i, r, rn, o, y, y0, hf, hd]

values in regiony and only modifies regiono, the rest of the memory is abstracted inh.

GCcopyPairchecks withrcmpif the pair is inyand if so, recurses on both fields. In case it does not point toy, we would really want to return justx, but instead we rebuild a new pair of the tag and the value, because the existential package in theMdatapart needs to be changed to reflect the fact that we know the content does not point toy. We could easily improve this code to avoid this reconstruction and returnxdirectly if we add coercions such as the ones presented in [Monnier 2007].

6.4 Mapping overR

One of the unusual functionalities offered by typed regions is the ability to scan a region, which we need for our GC. Figure 16 shows theGCrmap function that abstracts such a scan.

It take two referencesxandlimthat point in a regionr, along with a functionfto apply to all elements inrfrom right afterxupto and including lim. Of course it also takes a continutationk. Since this function only cares about the regionRits region’s types only describeRand abstract the rest inh:

typeRefRgni r rn o y y0=

(λn.matchnwithzero⇒λ .∃t.tatr.rn|succn0⇒M r o(if(i <succn0)y y0), succrn)

typeGCrmapRenvi r rn o y y0h=h⊕r7→RefRgni r rn o y y0

In theGCrmap code,his actually decomposed intohf andhd whereh=hf hdbecause the functionfmay modifyh, sohf describes the invariant part ofhandhdis the variable part.

GCredirectAux[r, rn, o, y, h, i, t, on]

{GCrmapRenvi r rn o y o(GCcopyRenvr o on y yn h)}

x:M r o y t,

k:∀[on]{GCrmapRenvi r rn o y o(GCcopyRenvr o on y yn h)}(M r o o t)0

«

=cast[...](GCrmapRenvi r rn o y o(GCcopyRenvr o on y yn h))

(GCcopyRenvr o on y yn(GCrmapRenvi r rn o y o h)) GCcopy[r, o, on, y, yn,(GCrmapRenvi r rn o y o h), t, y,inskip inbase]

(x, λ[on]{GCcopyRenvr o on y yn(GCrmapRenvi r rn o y o h)}(x0:M r o o t).

cast[...](GCcopyRenvr o on y yn(GCrmapRenvi r rn o y o h))

(GCrmapRenvi r rn o y o(GCcopyRenvr o on y yn h)) k[on](x0))

GCredirect[r, rn, o, on, y, yn, h]

{GCrmapRenv0r rn o y o(GCcopyRenvr o on y yn h)}

x0:∃t.tatr.0,

k:∀[on]{GCrmapRenv0r rn o o o(GCcopyRenvr o on y yn h)}()0

«

=leth, x00i=openx0

lim=get[inbase]x00

inGCrmap[0, r, rn, o, y, o,(λon.GCcopyRenvr o on y yn h), on](x0,lim,GCredirectAux, k)

Fig. 17. TheGCredirectcode.

The typeRefRgnused to describeRgives a special type to its element 0, which is hence forced to hold a pointer to the last element ofR; also the type is parameterized byiwhich is the index of the scan: all elements beforei(other than the element 0) have a type of the formM r o y t, whereas all the ones afterihave a type of the formM r o y0 t, so the scan start with a region with all non-zero elements of typeM r o y tand at the end, they have all been updated toM r o y0 t. This is a prime example of a code that uses a strong update, yet does not have any particular aliasing information available about the locations it updates: there can be any number of references pointing from anywhere into any part of regionr.

6.5 Redirecting pointers

Once the copy from the heap root is performed, we need to check every reference cell in regionRand if it points to an object inY redirect it by copying it into regionO. The code is shown in Fig. 17. We also need to update the type of regionRat each step, so that at the end of the redirection loop, the type ofRdoes not refer toY any more. This redirection loop basically uses GCrmap to applyGCcopy to every element of R. But of course, GCcopycannot be passed directly toGCrmap: the main problem is that the two functions expects their regions in a different order, so we introduce a wrapperGCredirectAuxwhich usescast to reorder the regions back and forth between the order expected by the two functions.

6.6 WideningRto allow references to the newY0

After all the objects have been copied and the references redirected, the nurseryY is not needed any more and can be freed, and a new one allocated in its place. But we cannot yet return to the main continuation: all our objects now have types of the formM r o o t, whereas the continuation expects objects of typeM r o y t. This is no problem since M r o o tis a sort of subtype ofM r o y t, but since our type system does not provide subsumption, we have to explicitly cast those objects by unpacking and repacking them.

GCwidenPair[i, r, rn, o, y, h,aux]

Luckily it only needs to be done at the roots, i.e. onxand on all elements ofR. The GCwiden takes care of updating the type ofRaccordingly, by scanning it again, using GCrmap.

GCwidenAux is the function that update a single element ofR. Because of how we structured our objects, we only have to unpack&repack an existential when the pointed object is a pair. That turns out to be unfortunate here, since we now have to useGCdispatch to as to callGCwidenPairwhen applicable.

Notice that this whole loop is nothing more than a complex no-op in the sense that it only modifies existentials. And indeed, this loop is equivalent to the no-op that was calledwiden in [Monnier et al. 2001]. This code was one of the main motivation for the development of the coercion calculus presented in [Monnier 2007], and indeed such a coercion calculus could be used here to replaceGCwidenby a coercion. Notice that this coercion would still have to do the same loop, examine the same data, unpack and repack the same existentials, except that it would be completely done at the level of types.

6.7 Putting it all together

Figure 19 shows theGC function itself which binds all the previous functions together.

Compared to the sketch shown at the beginning, the main difference is the addition of a fewcastoperations. They do nothing more than re-order regions in the region environment to match the order expected by the next function, except for thecastperformed right after

typeGCrenvr rn o on y yn h=GCcopyRenvr o on y yn(GCrmapRenv0r rn o y y h) GC[r, rn, o, on, y, yn, t, h]

{GCrenvr rn o on y yn h}

(x:M r o y t, x0:∃t.tatr.0, k:∀[y0, on00]{GCrenvr rn o on00y00h}(M r o y0t)0)

=GCcopy[r, o, on, y, yn,(GCrmapRenv0r rn o y y h), t, y,inskip inbase]

(x:M r o y t,

λ[on0]{GCrenvr rn o on0y yn h}(x0:M r o o t).

cast[...](GCcopyRenvr o on0y yn(GCrmapRenv0r rn o y o h))

(GCrmapRenv0r rn o y o(GCcopyRenvr o on0y yn h)) GCredirect[r, rn, o, on, y, yn,(GCcopyRenvr o on0y yn h)]

(x0:∃t.tatr.0,

λ[on00]{GCrmapRenv0r rn o o o(GCcopyRenvr o on00y yn h)}().

cast[...](GCrmapRenv0r rn o o y(GCcopyRenvr o on00y yn h))

(ho7→...r7→...y7→...) freergny;

lety0=newrgnλn.λt.snat0in cast[...]y07→TupRgno y0r

cast[...](ho7→...r7→...y07→(TupRgno y0r,0))

(GCrmapRenv0r rn o o y(GCcopyRenvr o on00y00h)) GCwiden[r, rn, o, y, h]

(x0, λ[ ]{(GCrmapRenv0r rn o y y(GCcopyRenvr o on00y00h))}().

cast[...](GCrmapRenv0r rn o y y(GCcopyRenvr o on00y00h))

(GCcopyRenvr o on00y00 (GCrmapRenv0r rn o y y h)) k[y0, on00](x0))))

Fig. 19. Toplevel of the GC

allocating the new nursery. This one addresses the problem of self-reference: the type of the new regiony0refers toy0, so when we allocate that region, the initial type that we pass tonewrgncannot be the proper type since we do not knowy0yet. For this reason,newrgn is provided with a dummy typeλn.λt.snat0, which is promptly replaced by the proper TupRgno y0rvia acast.

7. RELATED WORK

Documents relatifs