• Aucun résultat trouvé

Typed Memory Management in a Calculus of Capabilities

N/A
N/A
Protected

Academic year: 2022

Partager "Typed Memory Management in a Calculus of Capabilities"

Copied!
14
0
0

Texte intégral

(1)

Typed Memory Management in a Calculus of Capabilities

Karl Crary

Carnegie Mellon University

David Walker Cornell University

Greg Morrisett Cornell University

Abstract

An increasing number of systems rely on programming lan- guage technology to ensure safety and security of low-level code. Unfortunately, these systems typically rely on a com- plex, trusted garbage collector. Region-based type systems provide an alternative to garbage collection by making mem- ory management explicit but verifiably safe. However, it has not been clear how to use regions in low-level, type-safe code.

We present a compiler intermediate language, called the Capability Calculus, that supports region-based memory management, enjoys a provably safe type system, and is straightforward to compile to a typed assembly language.

Source languages may be compiled to our language using known region inference algorithms. Furthermore, region life- times need not be lexically scoped in our language, yet the language may be checked for safety without complex anal- yses. Finally, our soundness proof is relatively simple, em- ploying only standard techniques.

The central novelty is the use of static capabilities to specify the permissibility of various operations, such as memory access and deallocation. In order to ensure ca- pabilities are relinquished properly, the type system tracks aliasing information using a form of bounded quantification.

1 Motivation and Background

A current trend in systems software is to allow untrusted ex- tensions to be installed in protected services, relying upon language technology to protect the integrity of the service instead of hardware-based protection mechanisms [19, 39, 2, 25, 24, 17, 14]. For example, the SPIN project [2] relies upon the Modula-3 type system to protect an operating system kernel from erroneous extensions. Similarly, web browsers rely upon the Java Virtual Machine byte-code verifier [19] to protect users from malicious applets. In both situations, the goal is to eliminate expensive communications or boundary

This research was performed while the first author was at Cornell University. This material is based on work supported in part by the AFOSR grant F49620-97-1-0013 and ARPA/RADC grant F30602-96- 1-0317. Any opinions, findings, and conclusions or recommendations expressed in this publication are those of the authors and do not reflect the views of these agencies.

To appear in the 1999 Symposium on the Principles of Programming Languages, San Antonio, Texas, January 1999.

crossings by allowing extensions to directly access the re- sources they require.

Recently, Necula and Lee [26, 25] have proposed Proof- Carrying Code (PCC) and Morrisett et al.[24] have sug- gested Typed Assembly Language (TAL) as language tech- nologies that provide the security advantages of high-level languages, but without the overheads of interpretation or just-in-time compilation. In both systems, low-level ma- chine code can be heavily optimized, by hand or by com- piler, and yet be automatically verified through proof- or type-checking.

However, in all of these systems (SPIN, JVM, TAL, and Touchstone [27], a compiler that generates PCC), there is one aspect over which programmers and optimizing compil- ers have little or no control: memory management. In par- ticular, their soundness depends on memory being reclaimed by a trusted garbage collector. Hence, applets or kernel extensions may not perform their own optimized memory management. Furthermore, as garbage collectors tend to be large, complicated pieces of unverified software, the degree of trust in language-based protection mechanisms is dimin- ished.

The goal of this work is to provide a high degree of con- trol over memory management for programmers and com- pilers, but as in the PCC and TAL frameworks, make veri- fication of the safety of programs a straightforward task.

1.1 Regions

Tofte and Talpin [35, 36] suggest a type and effects system for verifying the soundness of region-based memory man- agement. In later work, Tofte and others show how to infer region types and lifetimes and how to implement their the- ory [34, 3, 4]. There are several advantages to region-based memory management; from our point of view, the two most important are:

1. Deallocation is explicit but provably safe.

2. The run-time routines necessary for region-based mem- ory management are relatively simple constant-time operations and, in principle, could be formally verified.

The Tofte-Talpin calculus uses a lexically scoped expres- sion (letregion r in e end) to delimit the lifetime of a re- gionr. Memory for the region is allocated when control en- ters the scope of theletregionconstruct and is deallocated when control leaves the scope. This mechanism results in a strictly LIFO (last-in, first-out) ordering of region lifetimes.

(2)

Both Birkedalet al.[4] and Aikenet al.[1] observed that a completely LIFO (de)allocation of regions would make poor use of memory in many cases. They proposed a series of optimizations that often alleviate efficiency concerns and even improve upon traditional tracing garbage collection in some cases. Although their optimizations are safe, there is no simple proof- or type-checker that an untrusting client can use to check the output code. Similarly, even the most straightforward code-generation requires that we stray from the Tofte-Talpin framework and allow arbitrary separation of allocation and deallocation points. Therefore, while re- gion inference brings us half way to our goal, in order to construct a strongly-typed region-based assembly language we must re-examine the fundamental question: “When can we free an objectx?”

One solution is to free xwhen you can guarantee that it will no longer be accessed. Operating systems such as Hydra [41] have solved the access control problem before by associating an unforgeable key or capabilitywith every object and requiring that the user present this capability to gain access to the object. Furthermore, when the need arises, these systems revoke capabilities, thereby preventing future access.

1.2 Overview of Contributions

In the rest of this paper, we describe a strongly typed lan- guage called the Capability Calculus. Our language’s type system provides an efficient way to check the safety of ex- plicit, arbitrarily ordered region allocation and deallocation instructions using a notion of capability. As in traditional capability systems, our type system keeps track of capability copies carefully in order to determine when a capability has truly been revoked. Unlike traditional capability systems, our calculus supports only voluntary revocation. However, the capabilities in our calculus are a purely static concept and thus their implementation requires no run-time over- head.

We have a purelysyntacticargument, based on Subject Reduction and Progress lemmas in the style of Felleisen and Wright [40], that the type system of the Capability Calcu- lus is sound. In contrast, Tofte and Talpin formulate the soundness of their system using a more complicated great- est fixed point argument [36], and the soundness of Aikenet al.’s optimizations [1] depend upon this argument. Part of the reason for the extra complexity is that Tofte and Talpin simultaneously show that regioninferencetranslates lambda calculus terms into operationally equivalent region calculus terms, a stronger property than we prove. However, when system security is the main concern, soundness is the critical property. The simplicity of our argument demonstrates the benefits of separating type soundness from type inference or optimization correctness.

We have a formal translation of a variant of the Tofte- Talpin language into our calculus. We describe the transla- tion in this paper by example; the full details appear in the companion technical report [5]. We also illustrate how some region-based optimizations may be coded in our language, taking advantage of our extra freedom to place allocation and deallocation points.

We have adapted the type system for the Capability Cal- culus to the setting of Typed Assembly Language, providing for the first time the ability for “applets” to explicitly con- trol their memory management without sacrificing memory- or type-safety. As the typing constructs at the intermediate

and assembly levels are so similar, we discuss here only the intermediate language and refer the interested reader to the companion technical report [5] for details on the assembly level.

2 A Calculus of Capabilities

The central technical contribution of this paper is the Ca- pability Calculus, a statically typed intermediate language that supports the explicit allocation, freeing and accessing of memory regions.

Programs in the Capability Calculus are written in continuation-passing style (CPS) [29]. That is, functions do not return values; instead, functions finish by calling a continuation function that is typically provided as an argu- ment. The fact that there is only one means of transferring control in CPS—rather than the two means (call and re- turn) in direct style—simplifies the tracking of capabilities in our type system. A direct style formulation is possible, but the complications involved obscure the central issues.

In the remainder of this paper, we assume familiarity with CPS.

The syntax of the Capability Calculus appears in Fig- ure 1. In the following sections, we explain and motivate the main constructs and typing rules of the language one by one. The complete static and operational semantics are specified in Appendix A.

2.1 Preliminaries

We specify the operational behavior of the Capability Calcu- lus using an allocation semantics [22, 23, 24], which makes the allocation of data in memory explicit. The semantics is given by a deterministic rewriting systemP 7−→P0map- ping machine states to new machine states. A machine state consists of a pair (M, e) of a memory and a term being ex- ecuted. A memory is a finite mapping of region names(ν) toregionswhere a region is a block of memory that holds a collection of heap-allocated objects. Regions are created at run time by the declaration newrgnρ, x, which allocates a new region in the heap, bindsρto the name of that region, and bindsxto thehandle(handle(ν)) for that region.

Region names and handles are distinguished in order to maintain a phase distinction between compile-time and run- time expressions. Region names are significant at compile time: The type-checker identifies which region an object inhabits via a region name (see below). However, region names, like other type constructors, have no run-time sig- nificance and may be erased from executable code. In con- trast, region handles hold the run-time data necessary to manipulate regions. In addition to accounting for a phase distinction, the separation of region names and handles also allows us to refine the contexts in which region handles are needed. Handles are needed when allocating objects within a region and when freeing a region, but are not needed when reading data from a region.

Regions are freed by the declarationfreergnv, wherev is the handle for the region to be freed. Objects h large enough to require heap allocation (i.e.,functions and tu- ples), called heap values,are allocated by the declaration x=hatv, wherevis the handle for the region in whichh is to be allocated. Data is read from a region in two ways:

functions are read by a function call, and tuples are read by the declarationx=πi(v), which bindsxto the data re- siding in the ith field of the object at address v. Each of

(3)

kinds κ ::= Type|Rgn|Cap constructor vars α, ρ,

constructors c ::= α|τ |r|C

types τ ::= α|int|r handle| ∀[∆].(C, τ1, . . . , τn)0 atr| hτ1, . . . , τniatr

regions r ::= ρ|ν

capabilities C ::= | ∅ | {rϕ} |C1⊕C2|C multiplicities ϕ ::= 1|+

constructor contexts ∆ ::= · |∆, α:κ|∆, ≤C value contexts Γ ::= · |Γ, x:τ

region types Υ ::= {`1:τ1, . . . , `n:τn} memory types Ψ ::= 1: Υ1, . . . , νn: Υn} word values v ::= x|i|ν.`|handle(ν)|v[c]

heap values h ::= fixf[∆](C, x11, . . . , xnn).e| hv1, . . . , vni arithmetic ops p ::= +| − | ×

declarations d ::= x=v|x=v1pv2|x=hatv|x=πiv|newrgnρ, x|freergnv terms e ::= letdine|if0vthene2elsee3|v(v1, . . . , vn)|haltv

memory regions R ::= {`17→h1, . . . , `n7→hn} memories M ::= 17→R1, . . . , νn7→Rn} machine states P ::= (M, e)

Figure 1: Capability Syntax

these operations may be performed only when the region in question has not already been freed. Enforcing this restric- tion is the purpose of the capability mechanism discussed in Section 2.2.

A region maps locations (`) to heap values. Thus, an address is given by a pairν.`of a region name and a loca- tion. In the course of execution, word-sized values (v) will be substituted for value variables and type constructors for constructor variables, but heap values (h) are always allo- cated in memory and referred to indirectly by an address.

Thus, when executing the declarationx=hatr(whereris handle(ν), the handle for regionν),his allocated in region ν (say at`) and the addressν.`is substituted forx in the following code.

A term in the Capability Calculus consists of a series of declarations ending in either a branch or a function call (or a halt). The class of declarations includes those constructs discussed above, plus two standard constructs, x = v for binding variables to values andx=v1p v2 (wherepranges over +,and×) for arithmetic.

Types The types of the Capability Calculus include type constructor variables and integers, a type of region handles, as well as tuple and function types. Ifr is a region, then r handleis the type of r’s region handle. The tuple type 1, . . . , τniatr contains the usual n field tuples, but also specifies that such tuples are allocated in regionr, whereris either a region nameνor, more frequently, a region variable ρ.

The function type ∀[ ].(C, τ1, . . . , τn)0 atr contains functions taking n arguments (with types τ1 through τn) that may be called when capabilityC is satisfied (see the next section). The0return type is intended to suggest the fact that CPS functions invoke their continuations rather than returning as a direct-style function does. The suffix

“atr”, like the corresponding suffix for tuple types, indicates

the region in which the function is allocated.

Functions may be made polymorphic over types, regions or capabilities by adding a constructor context ∆ to the function type. For convenience, types, regions and capabili- ties are combined into a single syntactic class of “construc- tors” and are distinguished by kinds. Thus, a type is a con- structor with kindType, a region is a constructor with kind Rgn, and a capability is a constructor with kind Cap. We use the metavariable cto range over constructors, but use the metavariables τ, r and C when those constructors are types, regions and capabilities, respectively. We also use the metavariablesρ andfor constructor variables of kind Rgn andCap, and use the metavariableαfor type variables and generic constructor variables. When ∆ is empty, we abbre- viate the function type∀[∆].(C, ~τ)→0 atrby (C, ~τ)→0 atr.

For example, a polymorphic identity function that is al- located in regionr, but whose continuation function may be in any region, may be given type

[α:Type, ρ:Rgn].(C, α,(C, α)0 atρ)→0 atr for some appropriate C. Letf be such a function, letv be its argument with typeτ, and letgbe its continuation with type (C, τ)0 atr. Thenf is called byf[τ][r](v, g).

The typing rules also make use of region types (Υ), which assign a type to every location allocated in a region, and memory types (Ψ), which assign a region type to every re- gion allocated in memory.

2.2 Capabilities

The central problem is how to ensure statically that no re- gion is used after it is freed. The typing rules enforce this with a system of capabilities that specify what operations are permitted. The main typing judgement is

Ψ; ∆; Γ;C`e

(4)

which states that (when memory has type Ψ, free construc- tor variables have kinds given by ∆ and free value variables have types given by Γ) it is legal to execute the terme,pro- vided that the capabilityC is held. A related typing judge- ment is

Ψ; ∆; Γ;C`d⇒0; Γ0;C0

which states that if the capability C is held, it is legal to execute the declarationd, which results in new constructor context ∆0, new value context Γ0and new capabilityC0.

Capabilities indicate the set of regions that are presently valid to access, that is, those regions that have not been freed. Capabilities are formed by joining together a collec- tion of singleton capabilities{r}that provide access to only one region, and capability variables that provide access to an unspecified set of regions. Capability joins, written C1⊕C2, are associative and commutative, but are not al- ways idempotent; in Section 2.3 we will see examples where C⊕Cis not equivalent toC. The empty capability, which provides access to no regions, is denoted by∅. We will often abbreviate the capability{r1} ⊕ · · · ⊕ {rn}by{r1, . . . , rn}. In order to read a field from a tuple in region r, it is necessary to hold the capability to accessr, as in the rule:

`C=C0⊕ {r}:Cap Ψ; ∆; Γ`v:1, . . . , τniatr

Ψ; ∆; Γ;C`x=πi(v)∆; Γ{x:τi};C (x6∈Dom(Γ)) The first subgoal indicates that the capability held (C) is equivalent to some capability that includes{r}.

A similar rule is used to allocate an object in a region.

Since the type of a heap value reflects the region in which it is allocated, the heap value typing judgement (the second subgoal below) must be provided with that region.

`C=C0⊕ {r}:Cap Ψ; ∆; Γ`hatr:τ Ψ; ∆; Γ`v:r handle

Ψ; ∆; Γ;C`x=hatv⇒∆; Γ{x:τ};C (x6∈Dom(Γ)) Functions Functions are defined by the form fixf[∆](C, x11, . . . , xnn).e, where f stands for the function itself and may appear free in the body, ∆ spec- ifies the function’s constructor arguments, and C is the function’s capability precondition. When ∆ is empty and f does not appear free in the function body we abbreviate thefix form byλ(C, x11, . . . , xnn).e.

In order to call a function residing in regionr, it is again necessary to hold the capability to accessr, and also to hold a capability equivalent to the function’s capability precon- dition:

`C=C00⊕ {r}:Cap ∆`C=C0:Cap Ψ; ∆; Γ`v: (C0, τ1, . . . , τn)0 atr

Ψ; ∆; Γ`vi:τi Ψ; ∆; Γ;C`v(v1, . . . , vn)

The body of a function may then assume the function’s capa- bility precondition is satisfied, as indicated by the capability Cin the premise of the rule:1

Ψ; ∆; Γ{x11, . . . , xnn};C`e

Ψ; ∆; Γ`λ(C, x11, . . . , xnn).e (xi6∈Dom(Γ))

1This rule specializes the full rule forfix to the case where the function is neither polymorphic nor recursive.

Often, we will extend the required capability for a func- tion with a quantified capability variable (similar to a row variable). This variable may be instantiated with whatever capabilities are leftover after satisfying the required capabil- ity. Consequently, the function may be used in a variety of contexts. For example, functions with type

[:Cap].({r} ⊕, . . .)→0 atr may be called with any capability that extends{r}.

Allocation and Deallocation The most delicate issue is the typing of region allocation and deallocation. Intuitively, the typing rules for the newrgn and freergn declarations should add and remove capabilities for the appropriate re- gion. Naive typing rules could be:

Ψ; ∆; Γ;C`newrgnρ, x⇒

{ρ:Rgn}; Γ{x:ρ handle};C⊕ {ρ}

(wrong)

Ψ; ∆; Γ`v:r handle C0=C\ {r}

Ψ; ∆; Γ;C`freergnv⇒∆; Γ;C0 (wrong) We will be able to use something much like the first rule for allocation, but the naive rule for freeing regions is fundamen- tally flawed. For example, consider the following function:

fixf1:Rgn, ρ2:Rgn](1, ρ2}, x:ρ1 handle, y:hintiatρ2).

let freergnxin letz=π0yin· · ·

This function is well-formed according to the naive typing rule: The function begins with the capability 1, ρ2}and ρ1is removed by thefreergndeclaration, leaving2}. The tupleyis allocated inρ2, so the projection is legal. However, this code is operationally incorrect ifρ1andρ2are instanti- ated by the same regionr. In that case, the first declaration freesrand the second attempts to read fromr.

This problem is a familiar one. To free a region safely it is necessary to delete all copies of the capability. However, instantiating region variables can create aliases, making it impossible to tell by inspection whether any copies exist.

2.3 Alias Control

We desire a system for alias control that can easily be en- forced by the type system, without expensive and complex program analyses. One possibility is a linear type system [12, 37, 38]. In a linear type system, aliasing would be triv- ially controlled; any use of a region name would consume that name, ensuring that it could not be used elsewhere.

Thus, in a linear type system, the naive rules for allocating and deallocating regions would be sound. Unfortunately, a linear type system is too restrictive to permit many useful programs. For example, supposef has type

∀[ρ1:Rgn, ρ2:Rgn].

(1, ρ2},hintiatρ1,hintiatρ2, . . .)→0 atr0 andv1andv2are integer tuples allocated in the same region r. Thenf could not be called withv1 andv2 as arguments, because that would require instantiatingρ1 andρ2 with the same region. More generally, one could not type any func- tion that takes two arguments that might or might not be allocated in the same region.

Approaches based on syntactic control of interference [30, 31] are more permissive than a linear type system, but are still too restrictive for our purposes; it is still impossible to instantiate multiple arguments with the same region.

(5)

Uniqueness Our approach, instead of trying to prevent aliasing, is to use the type system totrack aliasing. More precisely, we tracknon-aliasing, that is, uniqueness. We do this by tagging regions with one of twomultiplicities when forming a capability. The first form,{r+}, is the capability to access regionras it has been understood heretofore. The second form,{r1}, also permits accessing regionr, but adds the additional information thatris unique; that is,rrepre- sents a different region from any other region appearing in a capability formed using{r1}. For example, the capability {r+1, r12}not only indicates that it is permissible to accessr1 andr2, but also indicates thatr1 andr2 represent distinct regions.

Since{r1}guarantees that rdoes not appear anywhere else in a capability formed using it, it is the capability, not just to accessr, but also to freer. Thus we may type region deallocation with the rule:

∆; Γ`v:r handle ∆`C=C0⊕ {r1}:Cap Ψ; ∆; Γ;C`freergnv⇒∆; Γ;C0

Allocation of a region accordingly adds the new capability as unique:

6∈Dom(∆), x6∈Dom(Γ)) Ψ; ∆; Γ;C`newrgnρ, x⇒

∆{ρ:Rgn}; Γ{x:ρ handle};C⊕ {ρ1} Note that joining capabilities is only idempotent when the capabilities in question contain no unique multiplicities. For instance, the capabilities{r+}and{r+, r+}are equivalent, but the capabilities {r1} and {r1, r1} are not; the latter capability ({r1, r1}) asserts thatris distinct from itself and consequently that latter capability can never be satisfied.

The rules for capability equivalence appear in Appendix A.

WhenCis equivalent toC⊕C, we say thatCisduplicat- able.Note that capability variables are unduplicatable, since they can stand for any capability, including unduplicatable ones. Occasionally this prevents the typing of desired pro- grams, so we provide a stripping operator C that replaces all 1 multiplicities inCwith + multiplicities. For example, {r11, r+2} = {r+1, r2+}. For any capabilityC, the capability C is duplicatable. When programs need an unknown but duplicatable capability, they may use a stripped variable. Subcapabilities The capabilities{r1}and{r+}are not the same, but the former should provide all the privileges of the latter. We therefore say that the former is a subcapability of the latter and write{r1} ≤ {r+}. In the complete system, the various rules from Section 2.2 are modified to account for subcapabilities. For example, the function call rule becomes:

Ψ; ∆; Γ`v: (C0, τ1, . . . , τn)0 atr

`C≤C00⊕ {r+}`C≤C0 Ψ; ∆; Γ`vi:τi

Ψ; ∆; Γ;C`v(v1, . . . , vn)

Supposefhas type∀[ρ1:Rgn, ρ2:Rgn].({ρ+1, ρ+2}, . . .)→0atr0. If we hold capability {r+}, we may call f by instanti- ating ρ1 and ρ2 with r, since {r+} = {r+, r+}. Using the subcapability relation, we may also call f when we hold {r1}, again by instantiating ρ1 and ρ2 with r, since {r1} ≤ {r+}={r+, r+}.

The subcapability relation accounts only for the forget- ting of uniqueness information. Intuitively there could be a second source of subcapabilities, those generated by for- getting an entire capability. For example, {r+1, r+2} seems to provide all the privileges of{r1+}, so it is reasonable to suppose {r+1, r+2}to be subcapability of {r1+}. Indeed, one can construct a sound Capability Calculus incorporating this axiom, but we omit it because doing so allows us to specify memory management obligations and to prove a stronger property about space usage. One may write a function that can be called with extra capabilities using a capability vari- able, as discussed in Section 2.2.

By omitting the axiomC1⊕C2 ≤C1, our type system may formally specify who has responsibility for freeing a region. Failure to follow informal conventions is a common source of bugs in languages (such as C) that use manual memory management. Our type system rules out such bugs.

For example, consider the type:

[ρ:Rgn, :Cap].

(1} ⊕, ρ handle,()0 atr0)0 atr In our system⊕ {ρ1} 6≤. Consequently, before any func- tion with this type can return (i .e ., call the continuation of type ()0 at r0), it must take action to satisfy the capability, that is, it must freeρ.

In general, our type system prevents “region leaks”: pro- grams must return all memory resources to the operating system before they terminate (Theorem 2.4). The operat- ing system does not have to clean up after a program halts.

The typing rule forhaltstates that no capabilities may be held, and since capabilities may not be forgotten, this means that all regions must have been freed.

Ψ; ∆; Γ`v:int ∆`C=:Cap Ψ; ∆; Γ;C`haltv

Bounded Quantification The system presented to this point is sound, but it is not yet sufficient for compiling real source languages. We need to be able to recover uniqueness after a region name is duplicated. To see why, suppose we hold the capability{r1}andf has type:

1:Rgn, ρ2:Rgn].

(+1, ρ+2}, . . . ,(+1, ρ+2}, . . .)→0 atr0)0 atr00 We would like to be able to instantiate ρ1 and ρ2 with r (which we may do, since {r1} ≤ {r+, r+}), and then free r when f calls the continuation in its final argument. Un- fortunately, the continuation only possesses the capability {r+, r+}={r+}, not the capability {r1} necessary to free r. It does not help to strengthen the capability of the con- tinuation to (for example)11}, because thenfmay not call it.

We may recover uniqueness information by quantifying a capability variable. Suppose we again hold capability{r1} andghas type:

1:Rgn, ρ2:Rgn, :Cap].(, . . . ,(, . . .)0 atr0)0 atr00 We may instantiate with{r1} and then the continuation will possess that same capability, allowing it to freer. Un- fortunately, the body of functiongno longer has the capa- bility to accessρ1 andρ2, since its type draws no connection between them and.

(6)

We solve this problem by using bounded quantification to relateρ1,ρ2 and. Suppose hhas type:

1:Rgn, ρ2:Rgn, ≤ {ρ+1, ρ+2}].

(, . . . ,(, . . .)0 atr0)0 atr00

If we hold capability{r1}, we may callhby instantiatingρ1 and ρ2 with r and instantiating with {r1}. This instan- tiation is permissible because{r1} ≤ {r+, r+}. As withg, the continuation will possess the capability{r1}, allowing it to freer, but the body ofh (like that of f) will have the capability to accessρ1andρ2, since≤ {ρ+1, ρ+2}.

Bounded quantification solves the problem by revealing some information about a capability , while still requir- ing the function to be parametric over. Hence, when the function calls its continuation we regain the stronger capa- bility (to freer), although that capability was temporarily hidden in order to duplicate r. More generally, bounded quantification allows us to hide some privileges when calling a function, and regain those privileges in its continuation.

Thus, we support statically checkable attenuation and am- plification of capabilities.

2.4 Formal Properties of the Calculus

The most important properties of the Capability Calculus are Type Soundness and Complete Collection. Each can be proven from the formal semantics in Appendix A.

Type Soundness states that we will never enter astuck stateduring the execution of a well-typed program. A state (M, e) is stuck if there does not exist (M0, e0) such that (M, e) 7−→ (M0, e0) and e is not halt i. For example, a state that tries to project a value from a tuple that does not appear in memory is stuck.

Theorem 1 (Type Soundness)

If ` (M, e) and (M, e) 7−→ (M0, e0) then (M0, e0) is not stuck.

The proof of soundness is straightforward, making use of the standard Subject Reduction and Progress lemmas.

Progress states that well-typed states are not stuck, and Subject Reduction states that evaluation steps preserve well- typedness.

Lemma 2 (Subject Reduction)

If`(M, e)and(M, e)7−→(M0, e0)then`(M0, e0) Lemma 3 (Progress) If`(M, e)then either:

1. There exists(M0, e0)such that(M, e)7−→(M0, e0), or 2. e=halt i

The Complete Collection property guarantees that well- typed terminating programs return all of their memory re- sources to the system before they halt.

Theorem 4 (Complete Collection) If`(M, e)then ei- ther(M, e) diverges or(M, e)7−→({ },halt i).

By Subject Reduction and Progress, terminating pro- grams end in well-formed machine states (M,halti). The typing rule for thehaltexpression requires that the capa- bilityC be empty. Using this fact, we can infer that the memoryM contains no regions.

3 Expressiveness

Our work provides a type system in which to check region- annotated programs for safety, but we do not provide any new techniques for determining those annotations. Instead, we rely on existing region inference strategies [34, 1] to infer appropriate region annotations. In this section we illustrate how a variant of the Tofte-Talpin region language can be translated into the Capability Calculus. The translation is formalized in the companion technical report [5]. By com- posing Tofte and Birkedal’s region inference algorithm [34]

with this translation, we have a way to compile high-level languages into the Capability Calculus.

Our example considers a function count that counts down to zero. In order to have interesting allocation be- havior the integers involved in the count are boxed, and hence are allocated in a region.

%%% count in a Tofte-Talpin calculus variant letregion ρ1, xρ1 in

letregion ρ2, xρ2 in

letrec count[ρ] (xρ:ρ handle, x:hintiatρ)atxρ1 =

% count :

% [ρ].(ρ handle,hintiatρ){access(ρ1),access(ρ)}

−→ unit

let n=π0(x) in % (1)

if0 n then ()

else count[ρ] (xρ,hn−1iatxρ) % (2) end

in

count[ρ2] (xρ2,h10iatxρ2) end % letrec

end % region ρ2 scope and deallocate end % region ρ1 scope and deallocate

Thecountfunction is stored in regionρ1 and takes two arguments, a handle for regionρand a boxed integer xal- located in region ρ. If xis nonzero, count decrements it, storing the result again inρ, and recurses. The function has two effects: a read onρ1, resulting from the recursive call, and a read/write effect on ρ, resulting from line 1’s read and line 2’s store. Therefore, we give the functioncountthe effect{access(ρ1),access(ρ)}.

Operationally, theletregioncommand serves a purpose similar to a pair of newrgn and freergn declarations. A new region is allocated at the beginning of the letregion block and is automatically deallocated at the end of the block, resulting in a stack-like (LIFO) allocation pattern.

Hence, the code above allocates two regions (ρ1 and ρ2), storescountinρ1, stores a boxed integer inρ2, callscount, and then deallocatesρ1 andρ2.

The translation of this program into the Capability Cal- culus rewrites it in continuation-passing style, and converts effects information into capability requirements. One of the main tasks of the translation is the compilation ofletregion blocks intonewrgnandfreergndeclarations. The resulting program appears below. In the interest of clarity, we have simplified the actual output of the formal translation in a few ways.

(7)

%%% count in the Capability Calculus let newrgn ρ1, xρ1 in

let newrgn ρ2, xρ2 in let newrgn ρ3, xρ3 in

% capability held is 11, ρ12, ρ13} let count =

(fix count

[ρ:Rgn, ρcont:Rgn, ≤ {ρ+1, ρ+, ρ+cont}]

(, xρ:ρ handle, x:hintiatρ, k:()→0 atρcont).

% capability held is ≤ {ρ+1, ρ+, ρ+cont}

let n = π0(x) in % ρ ok

if0 n

then k() % ρcont ok

else

let n0 = n−1 in

let x0 = hn0iatxρ in % ρ ok count[ρ, ρcont, ] (xρ, x0, k) % ρ1 ok ) at xρ1 in

let ten = h10i at xρ2 in let cont =

(λ(11, ρ12, ρ13}).

% capability held is 11, ρ12, ρ13}

let freergn xρ1 in % ρ1 unique let freergn xρ2 in % ρ2 unique let freergn xρ3 in % ρ3 unique

halt 0 ) at xρ3 in

count[ρ2, ρ3,{ρ11, ρ12, ρ13}] (xρ2,ten,cont)

The translated program begins by allocating regionsρ1 andρ2, and also allocates a third regionρ3 to holdcount’s continuation. Thecountfunction requires a capabilityat least as good as the capability+1, ρ+, ρ+cont}needed to ac- cess itself, its argument, and its continuation; and it passes on that same capability to its continuation. The contin- uation requires the capability 11, ρ12, ρ13} in order to free the three regions. Henceis instantiated with the stronger capability needed by the continuation.

The power of bounded quantification comes into play when a function is called with several regions, some of which may or may not be the same. For example, the above exam- ple could be rewritten to havetenandcontshare a region, without changing the code ofcountin any way:

%%% count with ten and cont sharing ρ2 let newrgn ρ1, xρ1 in

let newrgn ρ2, xρ2 in

% capability held is 11, ρ12} let count = ... as before ...

let ten = h10i at xρ2 in let cont =

(λ(11, ρ12}) ...) at xρ2 in

count[ρ2, ρ2,{ρ11, ρ12}] (xρ2,ten,cont)

In this example, ρcont is instantiated with ρ2 and is instantiated with 11, ρ12} (which is again the capability required by cont). However, count proceeds exactly as before because is still as good as +1, ρ+, ρ+cont} (since 11, ρ12} ≤ {ρ+1, ρ+2}=+1, ρ+2, ρ+2}).

In the examples above, even though count is tail- recursive, we allocate a new cell each time around the loop and we do not deallocate any of the cells until the count is

complete. However, since ρ never contains any live values other than the current argument, it is safe to reduce the program’s space usage by deallocating the argument’s re- gion each time around the loop, as shown below. Note that this optimization is not possible when region lifetimes must be lexically scoped.

%%% count with efficient memory usage let newrgn ρ1, xρ1 in

let newrgn ρ2, xρ2 in let newrgn ρ3, xρ3 in

% capability held is 11, ρ12, ρ13} let count =

(fix count

[ρ:Rgn, ρcont:Rgn, ≤ {ρ+1, ρ+cont}]

(⊕ {ρ1}, xρ:ρ handle, x:hintiatρ, k:∀()0 atρcont).

% capability held is ⊕ {ρ1}

let n = π0(x) in % ρ ok

let freergn xρ in % ρ unique

% capability held is if0 n

then k() % ρcont ok

else

let n0 = n−1 in let newrgn ρ0, xρ0 in

% capability held is ⊕ {ρ01} let x0 = hn0iatxρ0 in % ρ0 ok

count[ρ0, ρcont, ] (xρ0, x0, k) % ρ1 ok ) at xρ1 in

let ten = h10i at xρ2 in let cont =

(λ({ρ11, ρ13}).

% capability held is 11, ρ13}

let freergn xρ1 in % ρ1 unique let freergn xρ3 in % ρ3 unique

halt 0 ) at xρ3 in

count[ρ2, ρ3,{ρ11, ρ13}] (xρ2,ten,cont)

In order to deallocate its argument, the revisedcountre- quires a unique capability for its argument’s regionρ. Note that if the program were again rewritten so that ten and contshared a region (which would lead to a run-time er- ror, sinceten is deallocated early), the program would no longer typecheck, since 11, ρ12} 6≤ {ρ+1, ρ+2, ρ12}. However, the program rewritten so that countandcontshare a re- gion does not fail at run time, and does typecheck, since 11, ρ12} ≤ {ρ+1, ρ+1, ρ12}.

4 Discussion

We believe the general framework of our capability system is quite robust. There are several ways to extend the language and a number of directions for future research.

4.1 Language Extensions

The primary goal of this work was the development of a low-level, type-safe language that gives compilers and pro- grammers control over the allocation and deallocation of data. The language that we have described so far is rel- atively high-level as it includes abstract closures and high-

(8)

level operations such as the atomic allocation and initializa- tion of data. In the companion technical report [5], we show that the capability constructs interact benignly with the pro- cess of type-preserving compilation described by Morrisett et al.[24] and we use the techniques described in this paper to modify their typed assembly language to allow explicit deallocation of data structures.

In this paper, we have concentrated on using the Capa- bility Calculus to implement safe deallocation of memory, but with a few changes, we believe our capability appara- tus may be used in a variety of other settings as well. One potential application involves reducing the overhead of com- munication across the user-kernel address space boundary in traditional operating systems. Typically, in such systems, when data in user space is presented to the kernel, the ker- nel must copy that data to ensure its integrity is preserved.

However, if a user process hands-off a unique capability for a region to the kernel, the kernel does not have to copy that region’s data; without the capability, the user can no longer read or modify the contents of that region.

Capabilities can also be used to ensure mutually exclu- sive access to shared mutable data in a multi-threaded en- vironment, by viewing locks as analogous to regions. If we associate each piece of sensitive data with a lock, we can statically check that every client to sensitive data obtains the corresponding lock and its associated capability before accessing that data. When the code releases the lock, we revoke the capability on the data, just as we revoke a capa- bility when we free a region.

In general, whenever a system wishes statically to restrict access to some data, and/or to ensure a certain sequence of operations are performed, it may consider using capabilities to check that the appropriate invariants are maintained.

4.2 Related Work

The Capability Calculus derives its lineage from the work of Gifford and Lucassen on type and effect systems [11, 20] and the subsequent study by many others [16, 33, 36, 34]. The relationship between effects and capabilities is quite close. A necessary prerequisite for the use of either system is type in- ference, performed by a programmer or compiler, and much of the research into effects systems has concentrated on this difficult task. Because of the focus on inference, effect sys- tems are usually formulated as a bottom-up synthesis of ef- fects. Our work may viewed as producing verifiable evidence of the correctness of an inference. Hence, while effect sys- tems typically work bottom-up, specifying the effects that might occur, we take a top-down approach, specifying by capabilities the effects that arepermitted to occur.

The addition of aliasing information to our capabilities also separates them from earlier work on effects systems.

However, capabilities only express the simplest aliasing re- lationships: a region is either completely unaliased or it may alias any other region. Our capabilities reveal very little of the structure of the store. A number of other re- searchers [10, 7, 32] have studied static analyses that infer the shapes of data structures and the aliasing relationships between them. We plan to investigate how to use these finer- grained memory models to increase the flexibility of our type system.

A connection can also be drawn between capabilities and monadic type systems. Work relating effects to monads [21, 28, 18, 8] has viewed effectful functions as pure func- tions that return state transformers. This might be called

anex post view: the effect takes place after the function’s execution. In contrast, we take an ex ante view in which the capability to perform the relevant effect must be satis- fied before the function’s execution. Nevertheless, there is considerable similarity between the views; just as the monad laws ensure that the store is single-threaded through a com- putation, our typing rules thread a capability (which sum- marizes aspects of the store) along the execution path of a program.

The experience of Birkedalet al.[4] with the ML Kit re- gion compiler shows that there are many refinements to the basic system that will be necessary to make our Capability Calculus a practical intermediate language. In particular, Birkedal found that allocation often occurs in two different contexts: one context in which no live object remains in the region and a second context in which there may be live objects remaining in the region. In order to avoid code du- plication and yet ensure efficient space usage, they check at run time to find out which situation has occurred. In the for- mer case, they reset the region (deallocate and reallocate in our formalism) and in the latter case, they do not reset but continue allocating at the top of the region. The type sys- tem we present here is not powerful enough to encode these storage-mode polymorphic operations. In fact, it must be refined in two ways. First, this optimization demands finer- grained aliasing specifications that declare a region ρdoes not alias some particular regionρ0 but may alias other re- gions. Second, after we dynamically check which of the two contexts above we are in, we must refine the type of our capability. Harper and Morrisett’s typecase [13] mechanism developed for the TIL compiler and further refined by Crary et al.[6] allows the sort of type refinement required here.

Aiken et al. [1] have also studied how to optimize the initial Tofte-Talpin region framework and they also allow regions to be independently deallocated. Furthermore, their system separates the naming of a region from its allocation.

Our language, as presented, does not make such a distinc- tion, but it is straightforward to add one. With such a mech- anism in place, we conjecture, based on the soundness proof for Aikenet al.’s analyses, that those analyses may be used to produce type correct code in the Capability Calculus.

Gay and Aiken [9] have developed a safe region imple- mentation that gives programmers control over region al- location and deallocation. They use reference counting to ensure safety. Hawblitzel and von Eicken [15] have also used the notion of a region in their language Passport to support sharing and revocation between multiple protection domains. Both of these groups use run-time checking to en- sure safety and it would be interesting to investigate hybrid systems that combine features of our static type system with more dynamic systems.

5 Conclusions

We have presented a new strongly typed language that ad- mits operations for explicit allocation and deallocation of data structures. Furthermore, this language is expressive enough to serve as a target for region inference and can be compiled to a typed assembly language. We believe that the notion of capabilities that support statically checkable atten- uation, amplification, and revocation is an effective new tool for language designers.

(9)

6 Acknowledgements

We would like to thank Lars Birkedal, Martin Elsman, Dan Grossman, Chris Hawblitzel, Fred Smith, Stephanie Weirich, Steve Zdancewic, and the anonymous reviewers for their comments and suggestions.

References

[1] Alexander Aiken, Manuel F¨ahndrich, and Raph Levien.

Better static memory management: Improving region- based analysis of higher-order languages. InACM SIG- PLAN Conference on Programming Language Design and Implementation, pages 174–185, La Jolla, Califor- nia, 1995.

[2] Brain Bershad, Stefan Savage, Przemyslaw Pardyak, Emin Sirer, Marc Fiuczynski, David Becker, Craig Chambers, and Susan Eggers. Extensibility, safety and performance in the SPIN operating system. In Fif- teenth ACM Symposium on Operating Systems Princi- ples, pages 267–284, Copper Mountain, December 1995.

[3] Lars Birkedal, Nick Rothwell, Mads Tofte, and David N. Turner. The ML Kit (version 1). Techni- cal Report 93/14, Department of Computer Science, University of Copenhagen, 1993.

[4] Lars Birkedal, Mads Tofte, and Magnus Vejlstrup.

From region inference to von Neumann machines via region representation inference. InTwenty-Third ACM Symposium on Principles of Programming Languages, pages 171–183, St. Petersburg, January 1996.

[5] Karl Crary, David Walker, and Greg Morrisett. Typed memory management in a calculus of capabilities. Tech- nical report, Cornell University, 1999.

[6] Karl Crary, Stephanie Weirich, and Greg Mor- risett. Intensional polymorphism in type-erasure se- mantics. InACM SIGPLAN International Conference on Functional Programming, pages 301–312, Baltimore, September 1998.

[7] Alain Deutsch. Interprocedural may-alias analysis for pointers: Beyond k-limiting. InACM SIGPLAN Con- ference on Programming Language Design and Imple- mentation, pages 230–241, Orlando, June 1994.

[8] Andrzej Filinski. Controlling Effects. PhD thesis, Carnegie Mellon University, School of Computer Sci- ence, Pittsburgh, Pennsylvania, May 1996.

[9] David Gay and Alex Aiken. Memory management with explicit regions. InACM SIGPLAN Conference on Pro- gramming Language Design and Implementation, pages 313 – 323, Montreal, June 1998.

[10] Rakesh Ghiya and Laurie J. Hendren. Is it a tree, a DAG, or a cyclic graph? A shape analysis for heap- directed pointers in C. InTwenty-Third ACM Sympo- sium on Principles of Programming Languages, pages 1–15, St. Petersburg Beach, Florida, January 1996.

[11] D. K. Gifford and J. M. Lucassen. Integrating func- tional and imperative programming. InACM Confer- ence on Lisp and Functional Programming, Cambridge, Massachusetts, August 1986.

[12] Jean-Yves Girard. Linear logic. Theoretical Computer Science, 50:1–102, 1987.

[13] Robert Harper and Greg Morrisett. Compiling poly- morphism using intensional type analysis. InTwenty- Second ACM Symposium on Principles of Program- ming Languages, pages 130–141, San Francisco, Jan- uary 1995.

[14] Chris Hawblitzel, Chi-Chao Chang, Grzegorz Cza- jkowski, Deyu Hu, and Thorsten von Eicken. Imple- menting multiple protection domains in Java. In 1998 USENIX Annual Technical Conference, New Orleans, June 1998.

[15] Chris Hawblitzel and Thorsten von Eicken. Shar- ing and revocation in a safe language. Unpublished manuscript., 1998.

[16] Pierre Jouvelot and D. K. Gifford. Algebraic recon- struction of types and effects. InEighteenth ACM Sym- posium on Principles of Programming Languages, pages 303–310, January 1991.

[17] Dexter Kozen. Efficient code certification. Technical Report 98-1661, Cornell University, January 1998.

[18] John Launchbury and Simon L. Peyton Jones. State in Haskell. LISP and Symbolic Computation, 8(4):293–

341, December 1995.

[19] Tim Lindholm and Frank Yellin.The Java Virtual Ma- chine Specification. Addison-Wesley, 1996.

[20] John M. Lucassen. Types and Effects—Towards the In- tegration of Functional and Imperative Programming.

PhD thesis, MIT Laboratory for Computer Science, 1987.

[21] Eugenio Moggi. Notions of computation and monads.

Information and Computation, 93:55–92, 1991.

[22] Greg Morrisett, Matthias Felleisen, and Robert Harper.

Abstract models of memory management. In ACM Conference on Functional Programming and Computer Architecture, pages 66–77, La Jolla, June 1995.

[23] Greg Morrisett and Robert Harper. Semantics of mem- ory management for polymorphic languages. In A.D.

Gordon and A.M. Pitts, editors, Higher Order Oper- ational Techniques in Semantics, Publications of the Newton Institute. Cambridge University Press, 1997.

[24] Greg Morrisett, David Walker, Karl Crary, and Neal Glew. From System F to Typed Assembly Language.

InTwenty-Fifth ACM Symposium on Principles of Pro- gramming Languages, San Diego, January 1998.

[25] George Necula. Proof-carrying code. InTwenty-Fourth ACM Symposium on Principles of Programming Lan- guages, pages 106–119, Paris, 1997.

[26] George Necula and Peter Lee. Safe kernel extensions without run-time checking. In Proceedings of Operat- ing System Design and Implementation, pages 229–243, Seattle, October 1996.

(10)

[27] George Necula and Peter Lee. The design and imple- mentation of a certifying compiler. InACM SIGPLAN Conference on Programming Language Design and Im- plementation, pages 333 – 344, Montreal, June 1998.

[28] Simon L. Peyton Jones and Philip Wadler. Imper- ative functional programming. In Twentieth ACM Symposium on Principles of Programming Languages, Charleston, South Carolina, January 1993.

[29] John C. Reynolds. Definitional interpreters for higher- order programming languages. In Conference Record of the 25th National ACM Conference, pages 717–740, Boston, August 1972.

[30] John C. Reynolds. Syntactic control of interference. In Fifth ACM Symposium on Principles of Programming Languages, pages 39–46, Tucson, Arizona, 1978.

[31] John C. Reynolds. Syntactic control of interference, part 2. InSixteenth International Colloquium on Au- tomata, Languages, and Programming, July 1989.

[32] M. Sagiv, T. Reps, and R. Wilhelm. Solving shape- analysis problems in languages with destructive updat- ing. ACM Transactions on Programming Languages and Systems, 20(1):1–50, January 1996.

[33] J.-P. Talpin and P. Jouvelot. Polymorphic type, region, and effect inference. Journal of Functional Program- ming, 2(3):245–271, July 1992.

[34] Mads Tofte and Lars Birkedal. A region inference al- gorithm.Transactions on Programming Languages and Systems, November 1998. To appear.

[35] Mads Tofte and Jean-Pierre Talpin. Implementation of the typed call-by-valueλ-calculus using a stack of re- gions. InTwenty-First ACM Symposium on Principles of Programming Languages, pages 188–201, Portland, Oregon, January 1994.

[36] Mads Tofte and Jean-Pierre Talpin. Region-based memory management. Information and Computation, 132(2):109–176, 1997.

[37] Philip Wadler. Linear types can change the world! In M. Broy and C. Jones, editors,Programming Concepts and Methods, Sea of Galilee, Israel, April 1990. North Holland. IFIP TC 2 Working Conference.

[38] Philip Wadler. A taste of linear logic. InMathemat- ical Foundations of Computer Science, volume 711 of LNCS, Gdansk, Poland, August 1993. Springer-Verlag.

[39] Robert Wahbe, Steven Lucco, Thomas Anderson, and Susan Graham. Efficient software-based fault isolation.

InFourteenth ACM Symposium on Operating Systems Principles, pages 203–216, Asheville, December 1993.

[40] Andrew K. Wright and Matthias Felleisen. A syntactic approach to type soundness. Information and Compu- tation, 115(1):38–94, 1994.

[41] W. A. Wulf, R. Levin, and S. P. Harbison. Hy- dra/C.mmp: An Experimental Computer System.

McGraw-Hill, New York, NY, 1981.

A Formal Semantics of the Capability Calculus We use the following notational conventions:

- Alpha-equivalent expressions are considered identical.

- Memories, memory regions, memory types, and region types that differ only in the order of their fields are considered identical.

- The expression E[E0/X] denotes the capture-avoiding substitution ofE0forX inE.

- Updates of finite maps M are denoted byM{X7→E} orM{X:E}.

- Juxtaposition of two mapsM and M N as inM N de- notes an update of the first with the elements of the second.

- The notation M\X excludes X from the domain of mapM.

- We abbreviateM(ν)(`) byM(ν.`).

- We abbreviate M{ν 7→ M(ν){` 7→E}} by M{ν.`7→

E}.

Références

Documents relatifs

In the first part, by combining Bochner’s formula and a smooth maximum principle argument, Kr¨ oger in [15] obtained a comparison theorem for the gradient of the eigenfunctions,

TOMI, A finiteness result in the free boundary value problem for minimal surfaces, preprint. [16]

Notice that the part containing derivatives is due to the variation of the point in which the field is evaluated (while keeping fixed the label x), so it is simply due to the fact

In order to do so, compare the free energy of a perfectly ordered system with the free energy of a partially disordered case that introduces the less energetic frustrations

Given a Hilbert space H characterise those pairs of bounded normal operators A and B on H such the operator AB is normal as well.. If H is finite dimensional this problem was solved

This could lead to huge orders for electronics firms, 71___ is why, in his recent jobs speech, President Barack Obama called for $1 billion of extra _72__ to speed up

L’archive ouverte pluridisciplinaire HAL, est destinée au dépôt et à la diffusion de documents scientifiques de niveau recherche, publiés ou non, émanant des

Our research showed that an estimated quarter of a million people in Glasgow (84 000 homes) and a very conservative estimate of 10 million people nation- wide, shared our