• Aucun résultat trouvé

Nesting, an alternative to adoption and abandon

A drawback of adoption and abandon is that they incur a runtime cost in time and space. In a sense, this is justified, as they are very powerful. In particular, they allow regaining permanent ownership of an adoptee (bytake-ing it away from its adopter), and they allowtake-ing two distinct adoptees simultaneously from the same adopter.

A purely static mechanism typically cannot support these features, because that would require statically keeping track of which objects have been taken away and exhibiting sophisticated nonaliasing proofs.

If one is willing to give up on these two features, though, it is possible to devise static mechanisms for aliasing exclusively-owned, mutable data, at no runtime cost.

1 abstract nests (y : value ) (p : perm) : perm

2 fact duplicable ( nests y p)

3

4 val nest : [y : value , p : perm]

5 (| consumes p) -> (| nests y p)

6

7 abstract punched (a : type) (p : perm) : type

8

9 val focus : [y : value , p : perm, a] exclusive a =>

10 (| nests y p * consumes y @ a) -> (| p * y @ punched a p)

11

12 val defocus : [y : value , p : perm, a] exclusive a =>

13 (| consumes (p * y @ punched a p )) -> (| y @ a)

Fig. 13. Axiomatization of nesting in Mezzo

Nesting [Boyland 2010] is one such mechanism. In the following, we show how (a sim-plified version of) nesting can be axiomatized in Mezzo.

An axiomatization of nesting. We axiomatize nesting by providing a module, nest, whose interface (shown in Fig.13) offers a small number of types and operations. Each of these operations is a no-op: at runtime, it does nothing and returns a unit value.

In other words, nesting is a purely static mechanism. It is axiomatized, as opposed to defined: that is, its implementation uses unsafe type casts. We have not proved type soundness for Mezzo with nesting. We believe that it should be possible to do so, as an extension of the type soundness proof presented in this paper.

The first item in Fig. 13isnests (line 1). It is an abstract permission, that is, an abstract type of kindperm. It is parameterized with a valueyand a permissionp. The permissionnests y pmeans thatphas been “nested” iny, or delegated toy. In other words, whoever has (exclusive) ownership of the objectyalso (implicitly) possesses the permissionp.

Nesting is similar to adoption and abandon. In both mechanisms, a permission is del-egated to an objecty. In nesting, an arbitrary permissionpcan be delegated. In adop-tion and abandon, it must be a permission of the form x @ u, wherexis the adoptee anduis the agreed-upon type ofy’s adoptees.

In nesting, the permission nests y p serves as a static witness that p has been nested iny, whereas in adoption and abandon, there is no such witness. All we have is x @ dynamic, which allows us to perform a dynamic ownership test, via the instruction take x from y.

The permissionnests y pis duplicable (line2). In other words, the fact thatphas been nested iny can be advertised without restriction. This is sound because such a fact, once true, remains true forever: nesting cannot be undone. Similarly, in adoption and abandon, the typedynamicis duplicable. This serves a common purpose, namely to allow sharing a piece of information about the nestee, or adoptee.

In adoption and abandon, a pair of dual operations,giveandtake, allow delegating the ownership of some object x to an adoptery and taking it back. Nesting offers a set of operations that serve a similar purpose. Their types are crafted in such a way that one cannot simultaneously take two permissions away from a single objecty. Two operations, nestanddefocus, correspond togive. One operation,focus, corresponds totake. We stress, once more, that these operations have no runtime effect.

A new nesting relationship is established by a call tonest(line4). Such a call takes the permissionpaway from the caller and transfers it to the objecty, or in other words,

to whoever ownsy. Thus, the caller losespand gains the (duplicable) nesting witness nests y p. Perhaps surprisingly, no permission foryis required.

If and when one wishes to regainp, one can do so by invoking focus(line 9). This operation requires proof that p has been nested in y: this is encoded by requiring nests y p. Furthermore, exclusive ownership ofyis needed. This is expressed by re-quiringy @ a, for an arbitrary exclusive typea.5 Naturally, one must not allow this operation to be performed twice in sequence: that would allow the user to obtain two copies ofp, which may be nonduplicable. In order to disallow this,focusconsumes the exclusive permissiony @ a. In its stead, it produces the permissiony @ punched a p, which is not exclusive (hence, does not allowfocusing onyagain).

The permissiony @ punched a pis, in essence, a magic wand (in Boyland’s words, a linear implication). It means that, once the user is done working with p, she may give it up and recovery @ a. This is done via a call todefocus(line12).

In a typical call tonest,focus, ordefocus, the parametersyandpmust be explicitly instantiated by the programmer, as they cannot be inferred. This is illustrated further on in our re-implementation of graphs using nesting.

Our version of nesting is not as powerful as Boyland’s6. For instance, Boyland al-lows per-field nesting: one may nest piny.f. Furthermore, his theory includes frac-tional permissions, which interact with nesting in subtle ways. Nevertheless, nesting in Mezzo has potentially interesting applications. In the following, we re-formulate the example of graphs (§2.5) using nesting.

Implementing graphs with nesting. The code in Fig.14defines graphs, constructs a cyclic graph, and defines depth-first search, based on nesting. It should be compared with the code in Fig.12, which is based on adoption and abandon.

In both approaches, in order to allow aliasing, we wish to conceptually place all of the graph nodes in a group and to have just one permission for the entire group, as opposed to one permission per node.

In the present case, we do this by nesting the permission for every node in a single object. This could be the graph g itself. Here, we prefer to use a separate (dummy) object r, which we then store in a field ofg. This objectrserves no purpose besides nesting every node. We refer to it as a “region”.

We begin by defining the algebraic data typeregion(line3). Like the unit type(), it has just one data constructor, of arity zero. Unlike theunittype, it is declaredmutable, which implies that a region has a unique owner and that the permissionr @ region is exclusive. This allows us to userinfocusanddefocusoperations.

For every graph node x, we intend to nest the permission forxin the regionr. In other words, we intend every node to become an inhabitant of this region. For greater clarity, a type of “region inhabitants” is made explicit via a type abbreviation (line6).

(The type unknown is surface syntax for >, that is, a duplicable type that carries no information.) Thus, by definition, the permissionx @ inhabitant r ais synonymous withnests r (x @ a).

We are now ready to define the typenode(line9). We parameterize it not only over the typeaof thecontentfield, as before (Fig.12), but also over a regionr. Indeed, this time, we intend to statically keep track of which region the nodes inhabit. We declare

5Likeduplicable a,exclusive ais an assertion about the typea, and can be viewed as a permission. It is not formalized in this paper. Intuitively,exclusive aholds if and only if (1) the permissiony @ aimplies exclusive ownership of the object that exists at addressyin the heap and (2)yhas not beenfocused. It is not synonymous withaffinea, which in Mezzo would be true of every typea.

6The operationnestcorresponds roughly to transformation 7 in Boyland’s Theorem 6.4 [2010]. The opera-tionsfocusanddefocuscorrespond roughly to the second item and fourth items, counting from the end, in Boyland’s Theorem 6.2.

1 open nest

2

3 data mutable region =

4 Region

5

6 alias inhabitant (r : value ) a =

7 (x: unknown | nests r (x @ a ))

8

9 data mutable node (r: value ) a =

10 Node {

11 visited : bool ;

12 content : a;

13 neighbors : list ( inhabitant r ( node r a ))

14 }

15

16 alias inode (r: value ) a =

17 inhabitant r ( node r a)

18

19 alias graph a =

20 (r: region , roots : list ( inode r a ))

21

22 val g : graph int =

23 let r = Region in

24 let n = Node {

25 visited = false ;

26 content = 10;

27 neighbors = nil ;

28 } in

29 let ns = Cons { head = n; tail = Nil } in

30 nest [r , (n @ node r int )] ();

31 focus [r] ();

32 n. neighbors <- ns ;

33 defocus [r] ();

34 (r , ns )

35

36 val dfs [a] (g: graph a , f: a -> ()): () =

37 let (r , roots ) = g in

38 let s : stack ( inode r a) = stack :: new roots in

39 stack :: work [ inode r a] (s , fun (n: inode r a

40 | r @ region * s @ stack ( inode r a )) : () =

41 focus [r] ();

42 if not n. visited then begin

43 n. visited <- true ;

44 f n. content ;

45 let ns = n. neighbors in

46 stack :: push [ inode r a] (ns , s );

47 end;

48 defocus [r] ()

49 )

Fig. 14. Alternative implementation of graphs using nesting

that theneighborsfield holds a list of nodes in the regionr(line13). It is important to note that, althoughnode r ais affine,inhabitant r (node r a)is duplicable. This stems from the fact thatnests r pis duplicable even ifpis affine. Thus, the type of theneighborsfield indicates that the neighbors are nodes, and indicates which region they inhabit, but does not claim that “each node owns its successors”. The ownership of all nodes, collectively, lies with the unique permissionr @ region.

We defineinode r aas an abbreviation forinhabitant r (node r a)(line16), and define a graph as a pair of a regionrand a listrootsof nodes that inhabitr(line19).

The ability for the components of a tuple to refer to one another is exploited here.

As before, we construct a cyclic graphg, composed of just one node that is its own suc-cessor (lines22–34). Whereas our earlier construction based on adoption and abandon (Fig. 12, lines13–24) involved just onegive instruction, placed after the assignment n.neighbors <- ns, here we must usenest before the assignment (line 30) and use focusanddefocusafterwards (lines31and33). The reason why we can get away in Fig. 12 with just one give instruction is that the permission ns @ list dynamic al-ready exists before we giventog. (Every node has typedynamicat every time, regard-less of whichgiveandtakeinstructions have been executed.) In Fig.14, in contrast, we have a chicken-and-egg problem of sorts. In order to argue that the singleton listnshas typelist (inode r a), we need the node nto inhabit the regionr. So, we must first nestninr. To do this, however, we must first prove thatnis a well-formed node, that is, we must exhibitn @ node r int. And, to do this, we needn.neighborsto have type list (inode r a). We work around this circularity by initializingn.neighbors with the empty list. This allows us to nestninr, which is good, but unfortunately takes the permissionn @ node r intaway from us. We temporarily recover this permission via focusanddefocus, which allows us to justify the assignmentn.neighbors <- ns.

The call tonest(line30) uses an explicit type application. Indeed, the type-checker cannot guess which permission we wish to nest in which object. The calls to focus anddefocus(lines31and33) also use explicit type applications. There, it is sufficient to instantiate r. The type-checker is able to guess thatp must be instantiated with n @ node r int, as there is no other choice.

The code for depth-first search (lines 36–49) is very similar to its counterpart in Fig. 12. Instead of a pair of give and take operations, we use a pair of focus and defocusoperations (lines41and48).

Discussion.In light of this example, nesting may appear preferable to adoption and abandon. Indeed, it has no runtime cost. Furthermore, it gives rise to more precise types: inode r ais arguably a more satisfactory piece of information than dynamic.

However, adoption and abandon is more flexible. In particular, only adoption and aban-don allows permanently detaching a node from a graph, perhaps in order to attach it to some other graph, or perhaps in order to permanently reclaim unique ownership of the value stored in itscontent field. Also, only adoption and abandon allows tak-ing two elements at the same time. Finally, because every mutable object has type dynamiceven before it is adopted, adoption and abandon makes it easy to build cyclic data structures. Nesting, in comparison, requires an object to have its final (fully ini-tialized) type before it is nested, which may make it awkward or impossible to build a cyclic data structure.

The fact that nesting can be axiomatized in Mezzo seems a testimony to the expres-sive power of Mezzo’s basic type discipline. The distinction between duplicable and affine types (and permissions), and the ability of types (and permissions) to refer to values, are powerful features, which may well allow a variety of ownership disciplines to be axiomatized.