Typed Memory Management in a Calculus of Capabilities∗ Karl Crary Carnegie Mellon University David Walker Cornell University Abstract An increasing number of systems rely on programming language technology to ensure safety and security of low-level code. Unfortunately, these systems typically rely on a complex, trusted garbage collector. Region-based type systems provide an alternative to garbage collection by making memory 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 lifetimes need not be lexically scoped in our language, yet the language may be checked for safety without complex analyses. Finally, our soundness proof is relatively simple, employing 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 capabilities 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 extensions 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-961-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. Greg Morrisett Cornell University crossings by allowing extensions to directly access the resources they require. Recently, Necula and Lee [26, 25] have proposed ProofCarrying Code (PCC) and Morrisett et al. [24] have suggested Typed Assembly Language (TAL) as language technologies 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 machine code can be heavily optimized, by hand or by compiler, 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 compilers have little or no control: memory management. In particular, 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 diminished. The goal of this work is to provide a high degree of control over memory management for programmers and compilers, but as in the PCC and TAL frameworks, make verification 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 management. In later work, Tofte and others show how to infer region types and lifetimes and how to implement their theory [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 memory management are relatively simple constant-time operations and, in principle, could be formally verified. The Tofte-Talpin calculus uses a lexically scoped expression (letregion r in e end) to delimit the lifetime of a region r. Memory for the region is allocated when control enters the scope of the letregion construct and is deallocated when control leaves the scope. This mechanism results in a strictly LIFO (last-in, first-out) ordering of region lifetimes. Both Birkedal et al. [4] and Aiken et 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 region 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 object x?” One solution is to free x when 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 capability with 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 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 Capability 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 argument. The fact that there is only one means of transferring control in CPS—rather than the two means (call and return) 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 Figure 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. Overview of Contributions In the rest of this paper, we describe a strongly typed language called the Capability Calculus. Our language’s type system provides an efficient way to check the safety of explicit, 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 overhead. We have a purely syntactic argument, based on Subject Reduction and Progress lemmas in the style of Felleisen and Wright [40], that the type system of the Capability Calculus is sound. In contrast, Tofte and Talpin formulate the soundness of their system using a more complicated greatest fixed point argument [36], and the soundness of Aiken et al.’s optimizations [1] depend upon this argument. Part of the reason for the extra complexity is that Tofte and Talpin simultaneously show that region inference translates 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 TofteTalpin language into our calculus. We describe the translation 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 Calculus to the setting of Typed Assembly Language, providing for the first time the ability for “applets” to explicitly control their memory management without sacrificing memoryor type-safety. As the typing constructs at the intermediate 2.1 Preliminaries We specify the operational behavior of the Capability Calculus using an allocation semantics [22, 23, 24], which makes the allocation of data in memory explicit. The semantics is given by a deterministic rewriting system P 7−→ P 0 mapping machine states to new machine states. A machine state consists of a pair (M, e) of a memory and a term being executed. A memory is a finite mapping of region names (ν) to regions where 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 binds x to the handle (handle(ν)) for that region. Region names and handles are distinguished in order to maintain a phase distinction between compile-time and runtime 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 significance and may be erased from executable code. In contrast, 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 declaration freergnv, where v is the handle for the region to be freed. Objects h large enough to require heap allocation (i.e., functions and tuples), called heap values, are allocated by the declaration x = h at v, where v is the handle for the region in which h 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 declaration x = πi (v), which binds x to the data residing in the ith field of the object at address v. Each of 2 kinds constructor vars constructors types regions capabilities multiplicities κ α, ρ, c τ r C ϕ ::= Type | Rgn | Cap ::= ::= ::= ::= ::= α|τ |r|C α | int | r handle | ∀[∆].(C, τ1 , . . . , τn ) → 0 at r | hτ1 , . . . , τn i at r ρ|ν | ∅ | {rϕ } | C1 ⊕ C2 | C 1|+ constructor contexts value contexts region types memory types ∆ Γ Υ Ψ ::= ::= ::= ::= · | ∆, α:κ | ∆, ≤ C · | Γ, x:τ {`1 : τ1 , . . . , `n : τn } {ν1 : Υ1 , . . . , νn : Υn } word values heap values arithmetic ops declarations terms v h p d e ::= ::= ::= ::= ::= x | i | ν.` | handle(ν) | v[c] fix f [∆](C, x1 :τ1 , . . . , xn :τn ).e | hv1 , . . . , vn i +|−|× x = v | x = v1 p v2 | x = h at v | x = πi v | newrgn ρ, x | freergn v let d in e | if0 v then e2 else e3 | v(v1 , . . . , vn ) | halt v memory regions memories machine states R M P ::= {`1 7→ h1 , . . . , `n 7→ hn } ::= {ν1 → 7 R1 , . . . , νn 7→ Rn } ::= (M, e) Figure 1: Capability Syntax these operations may be performed only when the region in question has not already been freed. Enforcing this restriction 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 location. 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 allocated in memory and referred to indirectly by an address. Thus, when executing the declaration x = h at r (where r is handle(ν), the handle for region ν), h is allocated in region ν (say at `) and the address ν.` is substituted for x 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 and x = v1 p v2 (where p ranges over +, − and ×) for arithmetic. 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 capabilities are combined into a single syntactic class of “constructors” and are distinguished by kinds. Thus, a type is a constructor with kind Type, a region is a constructor with kind Rgn, and a capability is a constructor with kind Cap. We use the metavariable c to range over constructors, but use the metavariables τ , r and C when those constructors are types, regions and capabilities, respectively. We also use the metavariables ρ and for constructor variables of kind Rgn and Cap, and use the metavariable α for type variables and generic constructor variables. When ∆ is empty, we abbreviate the function type ∀[∆].(C, τ~) →0 atr by (C, τ~ ) →0 atr. For example, a polymorphic identity function that is allocated in region r, but whose continuation function may be in any region, may be given type ∀[α:Type, ρ:Rgn].(C, α, (C, α) → 0 at ρ) → 0 at r for some appropriate C. Let f be such a function, let v be its argument with type τ , and let g be its continuation with type (C, τ ) → 0 at r. Then f is called by f [τ ][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 region allocated in memory. 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. If r is a region, then r handle is the type of r’s region handle. The tuple type hτ1 , . . . , τn i at r contains the usual n field tuples, but also specifies that such tuples are allocated in region r, where r is either a region name ν or, more frequently, a region variable ρ. The function type ∀[ ].(C, τ1 , . . . , τn ) → 0 at r contains functions taking n arguments (with types τ1 through τn ) that may be called when capability C is satisfied (see the next section). The 0 return type is intended to suggest the fact that CPS functions invoke their continuations rather than returning as a direct-style function does. The suffix “at r”, like the corresponding suffix for tuple types, indicates 2.2 Capabilities The central problem is how to ensure statically that no region 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 3 which states that (when memory has type Ψ, free constructor variables have kinds given by ∆ and free value variables have types given by Γ) it is legal to execute the term e, provided that the capability C is held. A related typing judgement is Ψ; ∆; Γ; C ` d ⇒ ∆0 ; Γ0 ; C 0 Often, we will extend the required capability for a function with a quantified capability variable (similar to a row variable). This variable may be instantiated with whatever capabilities are leftover after satisfying the required capability. Consequently, the function may be used in a variety of contexts. For example, functions with type ∀[:Cap].({r} ⊕ , . . .) → 0 at r which states that if the capability C is held, it is legal to execute the declaration d, which results in new constructor context ∆0 , new value context Γ0 and new capability C 0 . 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 collection 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 always idempotent; in Section 2.3 we will see examples where C ⊕ C is not equivalent to C. 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 access r, as in the rule: ∆ ` C = C 0 ⊕ {r} : Cap Ψ; ∆; Γ ` v : hτ1 , . . . , τn i at r Ψ; ∆; Γ; C ` x = πi (v) ⇒ ∆; Γ{x:τi}; C 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 region. Naive typing rules could be: Ψ; ∆; Γ; C ` newrgn ρ, x ⇒ ∆{ρ:Rgn}; Γ{x:ρ handle}; C ⊕ {ρ} Ψ; ∆; Γ ` v : r handle C 0 = C \ {r} (wrong) Ψ; ∆; Γ; C ` freergn v ⇒ ∆; Γ; C 0 We will be able to use something much like the first rule for allocation, but the naive rule for freeing regions is fundamentally flawed. For example, consider the following function: fix f [ρ1:Rgn, ρ2 :Rgn]({ρ1, ρ2 }, x:ρ1 handle, y:hinti at ρ2 ). let freergn x in let z = π0 y in · · · (x 6∈ 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 = C 0 ⊕ {r} : Cap Ψ; ∆; Γ ` h at r : τ Ψ; ∆; Γ ` v : r handle Ψ; ∆; Γ; C ` x = h at v ⇒ ∆; Γ{x:τ }; C (wrong) This function is well-formed according to the naive typing rule: The function begins with the capability {ρ1 , ρ2 } and ρ1 is removed by the freergn declaration, leaving {ρ2 }. The tuple y is allocated in ρ2 , so the projection is legal. However, this code is operationally incorrect if ρ1 and ρ2 are instantiated by the same region r. In that case, the first declaration frees r and the second attempts to read from r. 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. (x 6∈ Dom(Γ)) Functions Functions are defined by the form fix f [∆](C, x1 :τ1 , . . . , xn :τn).e, where f stands for the function itself and may appear free in the body, ∆ specifies 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 the fix form by λ(C, x1 :τ1 , . . . , xn :τn ).e. In order to call a function residing in region r, it is again necessary to hold the capability to access r, and also to hold a capability equivalent to the function’s capability precondition: 2.3 Alias Control We desire a system for alias control that can easily be enforced 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 trivially 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, suppose f has type ∆ ` C = C 00 ⊕ {r} : Cap ∆ ` C = C 0 : Cap Ψ; ∆; Γ ` v : (C 0 , τ1 , . . . , τn ) → 0 at r Ψ; ∆; Γ ` vi : τi ∀[ρ1 :Rgn, ρ2 :Rgn]. ({ρ1 , ρ2 }, hinti at ρ1 , hinti at ρ2 , . . .) → 0 at r0 Ψ; ∆; Γ; C ` v(v1 , . . . , vn ) and v1 and v2 are integer tuples allocated in the same region r. Then f could not be called with v1 and v2 as arguments, because that would require instantiating ρ1 and ρ2 with the same region. More generally, one could not type any function 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. The body of a function may then assume the function’s capability precondition is satisfied, as indicated by the capability C in the premise of the rule:1 Ψ; ∆; Γ{x1 :τ1 , . . . , xn :τn}; C ` e (xi 6∈ Dom(Γ)) Ψ; ∆; Γ ` λ(C, x1 :τ1 , . . . , xn :τn ).e 1 This rule specializes the full rule for fix to the case where the function is neither polymorphic nor recursive. 4 Uniqueness Our approach, instead of trying to prevent aliasing, is to use the type system to track aliasing. More precisely, we track non-aliasing, that is, uniqueness. We do this by tagging regions with one of two multiplicities when forming a capability. The first form, {r+ }, is the capability to access region r as it has been understood heretofore. The second form, {r1 }, also permits accessing region r, but adds the additional information that r is unique; that is, r represents a different region from any other region appearing in a capability formed using {r1 }. For example, the capability {r1+ , r21 } not only indicates that it is permissible to access r1 and r2 , but also indicates that r1 and r2 represent distinct regions. Since {r1 } guarantees that r does not appear anywhere else in a capability formed using it, it is the capability, not just to access r, but also to free r. Thus we may type region deallocation with the rule: The subcapability relation accounts only for the forgetting of uniqueness information. Intuitively there could be a second source of subcapabilities, those generated by forgetting an entire capability. For example, {r1+ , r2+ } seems to provide all the privileges of {r1+ }, so it is reasonable to suppose {r1+ , r2+ } 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 variable, as discussed in Section 2.2. By omitting the axiom C1 ⊕ 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: ∆; Γ ` v : r handle ∆ ` C = C 0 ⊕ {r1 } : Cap ∀[ρ:Rgn, :Cap]. ({ρ1 } ⊕ , ρ handle, () → 0 at r0 ) → 0 at r Ψ; ∆; Γ; C ` freergn v ⇒ ∆; Γ; C 0 Allocation of a region accordingly adds the new capability as unique: In our system ⊕ {ρ1 } 6≤ . Consequently, before any function 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”: programs must return all memory resources to the operating system before they terminate (Theorem 2.4). The operating system does not have to clean up after a program halts. The typing rule for halt states that no capabilities may be held, and since capabilities may not be forgotten, this means that all regions must have been freed. (ρ 6∈ Dom(∆), x 6∈ 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 that r is distinct from itself and consequently that latter capability can never be satisfied. The rules for capability equivalence appear in Appendix A. When C is equivalent to C ⊕C, we say that C is duplicatable. Note that capability variables are unduplicatable, since they can stand for any capability, including unduplicatable ones. Occasionally this prevents the typing of desired programs, so we provide a stripping operator C that replaces all 1 multiplicities in C with + multiplicities. For example, {r11 , r2+ } = {r1+ , r2+ }. For any capability C, the capability C is duplicatable. When programs need an unknown but duplicatable capability, they may use a stripped variable . Ψ; ∆; Γ ` v : int ∆ ` C = ∅ : Cap Ψ; ∆; Γ; C ` halt v 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 } and f has type: ∀[ρ1 :Rgn, ρ2 :Rgn]. + + + 0 00 ({ρ+ 1 , ρ2 }, . . . , ({ρ1 , ρ2 }, . . .) → 0 at r ) → 0 at r 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: 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. Unfortunately, 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 continuation to (for example) {ρ11 }, because then f may not call it. We may recover uniqueness information by quantifying a capability variable. Suppose we again hold capability {r1 } and g has type: Ψ; ∆; Γ ` v : (C 0 , τ1 , . . . , τn ) → 0 at r ∆ ` C ≤ C 00 ⊕ {r+ } ∆ ` C ≤ C 0 Ψ; ∆; Γ ` vi : τi Ψ; ∆; Γ; C ` v(v1 , . . . , vn ) ∀[ρ1 :Rgn, ρ2 :Rgn, :Cap].(, . . . , (, . . .) → 0 at r0 ) → 0 at r00 + 0 Suppose f has type ∀[ρ1 :Rgn, ρ2 :Rgn].({ρ+ 1 , ρ2 }, . . .)→0atr . + If we hold capability {r }, we may call f by instantiating ρ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+ }. We may instantiate with {r1 } and then the continuation will possess that same capability, allowing it to free r. Unfortunately, the body of function g no longer has the capability to access ρ1 and ρ2 , since its type draws no connection between them and . 5 We solve this problem by using bounded quantification to relate ρ1 , ρ2 and . Suppose h has type: 3 Our work provides a type system in which to check regionannotated 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 composing 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 behavior the integers involved in the count are boxed, and hence are allocated in a region. + ∀[ρ1 :Rgn, ρ2 :Rgn, ≤ {ρ+ 1 , ρ2 }]. (, . . . , (, . . .) → 0 at r0 ) → 0 at r00 If we hold capability {r1 }, we may call h by instantiating ρ1 and ρ2 with r and instantiating with {r1 }. This instantiation is permissible because {r1 } ≤ {r+ , r+ }. As with g, the continuation will possess the capability {r1 }, allowing it to free r, but the body of h (like that of f ) will have the + capability to access ρ1 and ρ2 , since ≤ {ρ+ 1 , ρ2 }. Bounded quantification solves the problem by revealing some information about a capability , while still requiring the function to be parametric over . Hence, when the function calls its continuation we regain the stronger capability (to free r), 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 amplification of capabilities. 2.4 Expressiveness %%% count in a Tofte-Talpin calculus variant letregion ρ1 , xρ1 in letregion ρ2 , xρ2 in letrec count [ρ] (xρ : ρ handle, x : hinti at ρ) at xρ1 = % count : {access(ρ1 ),access(ρ)} % ∀[ρ]. (ρ handle, hinti at ρ) −→ unit let n = π0 (x) in % (1) if0 n then () else count [ρ] (xρ, hn − 1i at xρ) % (2) end in count [ρ2 ] (xρ2 , h10i at xρ2 ) end % letrec end % region ρ2 scope and deallocate end % region ρ1 scope and deallocate 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 a stuck state during the execution of a well-typed program. A state (M, e) is stuck if there does not exist (M 0 , e0 ) such that (M, e) 7−→ (M 0 , 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. The count function is stored in region ρ1 and takes two arguments, a handle for region ρ and a boxed integer x allocated in region ρ. If x is 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 function count the effect {access(ρ1 ), access(ρ)}. Operationally, the letregion command 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 ), stores count in ρ1 , stores a boxed integer in ρ2 , calls count, and then deallocates ρ1 and ρ2 . The translation of this program into the Capability Calculus rewrites it in continuation-passing style, and converts effects information into capability requirements. One of the main tasks of the translation is the compilation of letregion blocks into newrgn and freergn declarations. The resulting program appears below. In the interest of clarity, we have simplified the actual output of the formal translation in a few ways. Theorem 1 (Type Soundness) If ` (M, e) and (M, e) 7−→∗ (M 0 , e0 ) then (M 0 , 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 welltypedness. Lemma 2 (Subject Reduction) If ` (M, e) and (M, e) − 7 → (M 0 , e0 ) then ` (M 0 , e0 ) Lemma 3 (Progress) If ` (M, e) then either: 1. There exists (M 0 , e0 ) such that (M, e) 7−→ (M 0 , e0 ), or 2. e = halt i The Complete Collection property guarantees that welltyped terminating programs return all of their memory resources to the system before they halt. Theorem 4 (Complete Collection) If ` (M, e) then either (M, e) diverges or (M, e) 7−→∗ ({ }, halt i). By Subject Reduction and Progress, terminating programs end in well-formed machine states (M, halt i). The typing rule for the halt expression requires that the capability C be empty. Using this fact, we can infer that the memory M contains no regions. 6 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 region each time around the loop, as shown below. Note that this optimization is not possible when region lifetimes must be lexically scoped. %%% 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:hinti at ρ, 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 = hn0 i at xρ 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) %%% 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:hinti at ρ, k:∀() → 0 at ρcont ) . % capability held is ⊕ {ρ1 } let n = π0 (x) in % let freergn xρ in % % capability held is if0 n then k() % else let n0 = n − 1 in let newrgn ρ0 , xρ0 in % capability held is ⊕ {ρ01 } let x0 = hn0 i at xρ0 in % count [ρ0 , ρcont , ] (xρ0 , x0 , k) % ) 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 % let freergn xρ3 in % halt 0 ) at xρ3 in count [ρ2 , ρ3 , {ρ11 , ρ13 }] (xρ2 , ten, cont) The translated program begins by allocating regions ρ1 and ρ2 , and also allocates a third region ρ3 to hold count’s continuation. The count function requires a capability at + + least as good as the capability {ρ+ 1 , ρ , ρcont } needed to access itself, its argument, and its continuation; and it passes on that same capability to its continuation. The continuation requires the capability {ρ11 , ρ12 , ρ13 } in order to free the three regions. Hence is 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 example could be rewritten to have ten and cont share a region, without changing the code of count in any way: ρ ok ρ unique ρcont ok ρ0 ok ρ1 ok ρ1 unique ρ3 unique In order to deallocate its argument, the revised count requires a unique capability for its argument’s region ρ. Note that if the program were again rewritten so that ten and cont shared a region (which would lead to a run-time error, since ten is deallocated early), the program would no + 1 longer typecheck, since {ρ11 , ρ12 } 6≤ {ρ+ 1 , ρ2 , ρ2 }. However, the program rewritten so that count and cont share a region does not fail at run time, and does typecheck, since + 1 {ρ11 , ρ12 } ≤ {ρ+ 1 , ρ1 , ρ2 }. %%% 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) 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. 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 + + + + + 1 1 {ρ1 , ρ2 } ≤ {ρ1 , ρ2 } = {ρ1 , ρ2 , ρ2 }). In the examples above, even though count is tailrecursive, we allocate a new cell each time around the loop and we do not deallocate any of the cells until the count is 4.1 Language Extensions The primary goal of this work was the development of a low-level, type-safe language that gives compilers and programmers control over the allocation and deallocation of data. The language that we have described so far is relatively high-level as it includes abstract closures and high- 7 an ex 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 satisfied 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 computation, our typing rules thread a capability (which summarizes aspects of the store) along the execution path of a program. The experience of Birkedal et al. [4] with the ML Kit region 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 duplication and yet ensure efficient space usage, they check at run time to find out which situation has occurred. In the former 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 system 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 finergrained aliasing specifications that declare a region ρ does not alias some particular region ρ0 but may alias other regions. 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 distinction, but it is straightforward to add one. With such a mechanism in place, we conjecture, based on the soundness proof for Aiken et 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 implementation that gives programmers control over region allocation 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 ensure safety and it would be interesting to investigate hybrid systems that combine features of our static type system with more dynamic systems. level operations such as the atomic allocation and initialization of data. In the companion technical report [5], we show that the capability constructs interact benignly with the process 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 Capability Calculus to implement safe deallocation of memory, but with a few changes, we believe our capability apparatus may be used in a variety of other settings as well. One potential application involves reducing the overhead of communication 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 kernel 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 exclusive access to shared mutable data in a multi-threaded environment, 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 capability 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 inference, 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 systems are usually formulated as a bottom-up synthesis of effects. Our work may viewed as producing verifiable evidence of the correctness of an inference. Hence, while effect systems typically work bottom-up, specifying the effects that might occur, we take a top-down approach, specifying by capabilities the effects that are permitted 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 relationships: 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 researchers [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 finergrained 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 functions that return state transformers. This might be called 5 Conclusions We have presented a new strongly typed language that admits 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 attenuation, amplification, and revocation is an effective new tool for language designers. 8 6 [12] Jean-Yves Girard. Linear logic. Theoretical Computer Science, 50:1–102, 1987. 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. [13] Robert Harper and Greg Morrisett. Compiling polymorphism using intensional type analysis. In TwentySecond ACM Symposium on Principles of Programming Languages, pages 130–141, San Francisco, January 1995. References [14] Chris Hawblitzel, Chi-Chao Chang, Grzegorz Czajkowski, Deyu Hu, and Thorsten von Eicken. Implementing multiple protection domains in Java. In 1998 USENIX Annual Technical Conference, New Orleans, June 1998. [1] Alexander Aiken, Manuel Fähndrich, and Raph Levien. Better static memory management: Improving regionbased analysis of higher-order languages. In ACM SIGPLAN Conference on Programming Language Design and Implementation, pages 174–185, La Jolla, California, 1995. [15] Chris Hawblitzel and Thorsten von Eicken. Sharing and revocation in a safe language. Unpublished manuscript., 1998. [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 Fifteenth ACM Symposium on Operating Systems Principles, pages 267–284, Copper Mountain, December 1995. [16] Pierre Jouvelot and D. K. Gifford. Algebraic reconstruction of types and effects. In Eighteenth ACM Symposium on Principles of Programming Languages, pages 303–310, January 1991. [3] Lars Birkedal, Nick Rothwell, Mads Tofte, and David N. Turner. The ML Kit (version 1). Technical Report 93/14, Department of Computer Science, University of Copenhagen, 1993. [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. [4] Lars Birkedal, Mads Tofte, and Magnus Vejlstrup. From region inference to von Neumann machines via region representation inference. In Twenty-Third ACM Symposium on Principles of Programming Languages, pages 171–183, St. Petersburg, January 1996. [19] Tim Lindholm and Frank Yellin. The Java Virtual Machine Specification. Addison-Wesley, 1996. [20] John M. Lucassen. Types and Effects—Towards the Integration of Functional and Imperative Programming. PhD thesis, MIT Laboratory for Computer Science, 1987. [5] Karl Crary, David Walker, and Greg Morrisett. Typed memory management in a calculus of capabilities. Technical report, Cornell University, 1999. [21] Eugenio Moggi. Notions of computation and monads. Information and Computation, 93:55–92, 1991. [6] Karl Crary, Stephanie Weirich, and Greg Morrisett. Intensional polymorphism in type-erasure semantics. In ACM SIGPLAN International Conference on Functional Programming, pages 301–312, Baltimore, September 1998. [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. [7] Alain Deutsch. Interprocedural may-alias analysis for pointers: Beyond k-limiting. In ACM SIGPLAN Conference on Programming Language Design and Implementation, pages 230–241, Orlando, June 1994. [23] Greg Morrisett and Robert Harper. Semantics of memory management for polymorphic languages. In A.D. Gordon and A.M. Pitts, editors, Higher Order Operational Techniques in Semantics, Publications of the Newton Institute. Cambridge University Press, 1997. [8] Andrzej Filinski. Controlling Effects. PhD thesis, Carnegie Mellon University, School of Computer Science, Pittsburgh, Pennsylvania, May 1996. [24] Greg Morrisett, David Walker, Karl Crary, and Neal Glew. From System F to Typed Assembly Language. In Twenty-Fifth ACM Symposium on Principles of Programming Languages, San Diego, January 1998. [9] David Gay and Alex Aiken. Memory management with explicit regions. In ACM SIGPLAN Conference on Programming Language Design and Implementation, pages 313 – 323, Montreal, June 1998. [25] George Necula. Proof-carrying code. In Twenty-Fourth ACM Symposium on Principles of Programming Languages, pages 106–119, Paris, 1997. [10] Rakesh Ghiya and Laurie J. Hendren. Is it a tree, a DAG, or a cyclic graph? A shape analysis for heapdirected pointers in C. In Twenty-Third ACM Symposium on Principles of Programming Languages, pages 1–15, St. Petersburg Beach, Florida, January 1996. [26] George Necula and Peter Lee. Safe kernel extensions without run-time checking. In Proceedings of Operating System Design and Implementation, pages 229–243, Seattle, October 1996. [11] D. K. Gifford and J. M. Lucassen. Integrating functional and imperative programming. In ACM Conference on Lisp and Functional Programming, Cambridge, Massachusetts, August 1986. 9 A [27] George Necula and Peter Lee. The design and implementation of a certifying compiler. In ACM SIGPLAN Conference on Programming Language Design and Implementation, pages 333 – 344, Montreal, June 1998. 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[E 0 /X] denotes the capture-avoiding substitution of E 0 for X in E. - Updates of finite maps M are denoted by M {X7→E} or M {X:E}. - Juxtaposition of two maps M and M N as in M N denotes an update of the first with the elements of the second. - The notation M \X excludes X from the domain of map M . - We abbreviate M (ν)(`) by M (ν.`). - We abbreviate M {ν 7→ M (ν){` 7→ E}} by M {ν.` 7→ E}. [28] Simon L. Peyton Jones and Philip Wadler. Imperative functional programming. In Twentieth ACM Symposium on Principles of Programming Languages, Charleston, South Carolina, January 1993. [29] John C. Reynolds. Definitional interpreters for higherorder 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. In Sixteenth International Colloquium on Automata, Languages, and Programming, July 1989. [32] M. Sagiv, T. Reps, and R. Wilhelm. Solving shapeanalysis problems in languages with destructive updating. 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 Programming, 2(3):245–271, July 1992. [34] Mads Tofte and Lars Birkedal. A region inference algorithm. 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 regions. In Twenty-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. In Mathematical 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. In Fourteenth 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 Computation, 115(1):38–94, 1994. [41] W. A. Wulf, R. Levin, and S. P. Harbison. Hydra/C.mmp: An Experimental Computer System. McGraw-Hill, New York, NY, 1981. 10 If e = let x = v in e0 let x = i p j in e0 let x = h at (handle(ν)) in e0 and ν ∈ Dom(M ) let x = πi (ν.`) in e0 and ν ∈ Dom(M ) and ` ∈ Dom(M (ν)) let newrgn ρ, x in e0 let freergn (handle(ν)) in e0 and ν ∈ Dom(M ) if0 0 then e2 else e3 if0 i then e2 else e3 and i 6= 0 v(v1, . . . , vn ) (M, e) 7−→ P then P = (M, e0 [v/x]) (M, e0 [(i p j)/x]) (M {ν.` 7→ h}, e0 [ν.`/x]) where ` 6∈ Dom(M (ν)) (M, e0 [vi /x]) where M (ν.`) = hv0 , . . . , vn−1 i (0 ≤ i < n) (M {ν → 7 {}}, e0 [ν, handle(ν)/ρ, x]) where ν 6∈ M and ν 6∈ e0 (M \ν, e0 ) (M, e2 ) (M, e3 ) (M, e[c1 , . . . , cm , ν.`, v1 , . . . , vn /α1 , . . . , αm , f, x1 , . . . , xn ]) where v = ν.`[c1 , . . . , cm ] and M (ν.`) = fix f [∆](C, x1 :τ1 , . . . , xn :τn ).e and Dom(∆) = α1 , . . . , αm Figure 2: Capability Operational Semantics Judgement ∆ ` ∆0 ∆`c:κ ∆ ` ∆1 = ∆ 2 ∆ ` c1 = c2 : κ ∆ ` C1 ≤ C2 `Ψ `Υ Ψ; ∆; Γ ` v : τ Ψ; ∆; Γ ` h at r : τ Ψ; ∆; Γ; C ` d ⇒ ∆0 ; Γ0 ; C 0 Ψ; ∆; Γ; C ` e Ψ ` C sat Ψ ` R at ν : Υ `M :Ψ `P Meaning Constructor context ∆0 is well-formed. Constructor c has kind κ. Constructor contexts ∆1 and ∆2 are equal. Constructors c1 and c2 are equal in kind κ. Capability C1 is a subcapability of C2 . Memory type Ψ is well-formed. Region type Υ is well-formed. Word value v has type τ . Heap value h (residing in region r) has type τ . Declaration d is well-formed and produces constructor context ∆0 , value context Γ0 and capability C 0 . Expression e is well-formed. Memories with type Ψ satisfy the capability C. That is, capability C contains exactly the regions in the domain of Ψ, and unique capabilities appear exactly once in C. Region R (named ν) has region type Υ. Memory M has memory type Ψ. Machine state P is well-formed. Figure 3: Capability Static Semantics: Judgements 11 ∆ ` ∆0 ∆`· ∆ ` ∆0 ∆∆0 ` C : Cap ( 6∈ Dom(∆∆0 )) ∆ ` ∆0 , ≤ C ∆ ` ∆0 (α 6∈ Dom(∆∆0 )) ∆ ` ∆0 , α:κ ∆`c:κ ∆`α:κ (∆(α) = κ) ∆ ` : Cap (( ≤ C) ∈ ∆) ∆ ` ∆0 ∆ ` τi : Type (for 1 ≤ i ≤ n) ∆ ` r : Rgn ∆ ` hτ1 , . . . , τn i at r : Type ∆ ` ν : Rgn `Ψ `Υ ∆ ` ∅ : Cap ∆ ` r : Rgn ∆ ` r handle : Type ∆ ` int : Type ∆∆0 ` τi : Type (for 1 ≤ i ≤ n) ∆∆0 ` C : Cap ∆ ` r : Rgn ∆ ` ∀[∆0 ].(C, τ1 , . . . , τn ) → 0 at r : Type ∆ ` r : Rgn ∆ ` {rϕ } : Cap ` Υi (for 1 ≤ i ≤ n) ` {ν1 : Υ1 , . . . , νn : Υn } ∆ ` C1 : Cap ∆ ` C2 : Cap ∆ ` C1 ⊕ C2 : Cap ∆ ` C : Cap ∆ ` C : Cap · ` τi : Type (for 1 ≤ i ≤ n) ` {`1 : τ1 , . . . , `n : τn } ∆ ` ∆1 = ∆ 2 ∆`·=· ∆ ` c1 = c2 : κ ∆ ` ∆1 = ∆2 ∆∆1 ` C1 = C2 : Cap ( 6∈ Dom(∆∆1 )) ∆ ` ∆1 , ≤ C1 = ∆2 , ≤ C2 ∆ ` ∆1 = ∆2 (α 6∈ Dom(∆∆1 )) ∆ ` ∆1 , α:κ = ∆2 , α:κ (except congruence rules) ∆`c:κ ∆`c=c:κ ∆ ` c1 = c2 : κ ∆ ` c2 = c3 : κ ∆ ` c1 = c3 : κ ∆ ` c2 = c1 : κ ∆ ` c1 = c2 : κ ∆ ` C : Cap ∆ ` ∅ ⊕ C = C : Cap ∆ ` C1 : Cap ∆ ` C2 : Cap ∆ ` C1 ⊕ C2 = C2 ⊕ C1 : Cap ∆ ` C : Cap ∆ ` Ci : Cap (for 1 ≤ i ≤ 3) ∆ ` (C1 ⊕ C2 ) ⊕ C3 = C1 ⊕ (C2 ⊕ C3 ) : Cap ∆ ` C = C ⊕ C : Cap ∆ ` r : Rgn ∆ ` ∅ = ∅ : Cap ∆` {r1 } + = {r } : Cap ∆ ` C : Cap ∆ ` C = C : Cap ∆ ` C1 : Cap ∆ ` r : Rgn ∆ ` {r+ } = {r+ } : Cap ∆ ` C2 : Cap ∆ ` C1 ⊕ C2 = C1 ⊕ C2 : Cap ∆ ` C1 ≤ C2 ∆ ` C1 = C2 : Cap ∆ ` C1 ≤ C2 ∆ ` C1 ≤ C2 ∆ ` C2 ≤ C3 ∆ ` C1 ≤ C3 ∆ ` C1 ≤ C10 ∆ ` C2 ≤ C20 ∆ ` C1 ⊕ C2 ≤ C10 ⊕ C20 ∆`≤C (( ≤ C) ∈ ∆) ∆ ` C ≤ C0 ∆ ` C : Cap ∆ ` C ≤ C0 ∆`C ≤C Figure 4: Capability Static Semantics: Type and Context Formation 12 Ψ; ∆; Γ ` h at r : τ ∆ ` ∆0 ∆∆0 ` C : Cap ∆∆0 ` τi : Type (for 1 ≤ i ≤ n) ∆ ` r : Rgn Ψ; ∆∆0 ; Γ{f :τf , x1 :τ1 , . . . , xn :τn}; C ` e Ψ; ∆; Γ ` fix f [∆0 ](C, x1 :τ1 , . . . , xn :τn ).e at r : τf τf = ∀[∆0 ].(C, τ1 , . . . , τn ) → 0 at r f, x1 , . . . , xn 6∈ Dom(Γ) ∆ ` τ 0 = τ : Type Ψ; ∆; Γ ` h at r : τ 0 Ψ; ∆; Γ ` h at r : τ Ψ; ∆; Γ ` vi : τi (for 1 ≤ i ≤ n) ∆ ` r : Rgn Ψ; ∆; Γ ` hv1 , . . . , vn i at r : hτ1 , . . . , τn i at r Ψ; ∆; Γ ` v : τ Ψ; ∆; Γ ` x : τ (Γ(x) = τ ) ∆ ` ∀[∆0 ].(C, τ1 , . . . , τn ) → 0 at ν : Type ∆ ` hτ1 , . . . , τn i at ν : Type (ν 6∈ Dom(Ψ)) Ψ; ∆; Γ ` ν.` : hτ1 , . . . , τn i at ν Ψ; ∆; Γ ` ν.` : τ Ψ; ∆; Γ ` i : int Ψ; ∆; Γ ` ν.` : ∀[∆0 ].(C, τ1 , . . . , τn ) → 0 at ν (Ψ(ν.`) = τ ) Ψ; ∆; Γ ` handle(ν) : ν handle Ψ; ∆; Γ ` v : ∀[α:κ, ∆0 ].(C, τ1 , . . . , τn ) → 0 at r ∆`c:κ Ψ; ∆; Γ ` v[c] : (∀[∆0 ].(C, τ1 , . . . , τn ) → 0)[c/α] at r Ψ; ∆; Γ ` v : ∀[ ≤ C 00 , ∆0 ].(C 0, τ1 , . . . , τn ) → 0 at r 0 ∆ ` C ≤ C 00 0 Ψ; ∆; Γ ` v[C] : (∀[∆ ].(C , τ1 , . . . , τn ) → 0)[C/] at r Ψ; ∆; Γ ` v : τ 0 ∆ ` τ 0 = τ : Type Ψ; ∆; Γ ` v : τ Figure 5: Capability Static Semantics: Heap and Word Values 13 (ν 6∈ Dom(Ψ)) Ψ; ∆; Γ; C ` d ⇒ ∆0 ; Γ0 ; C 0 Ψ; ∆; Γ ` v : τ (x 6∈ Dom(Γ)) Ψ; ∆; Γ; C ` x = v ⇒ ∆; Γ{x:τ }; C Ψ; ∆; Γ ` v1 : int Ψ; ∆; Γ ` v2 : int (x 6∈ Dom(Γ)) Ψ; ∆; Γ; C ` x = v1 p v2 ⇒ ∆; Γ{x:int}; C Ψ; ∆; Γ ` v : r handle Ψ; ∆; Γ ` h at r : τ ∆ ` C ≤ C 0 ⊕ {r+ } (x 6∈ Dom(Γ)) Ψ; ∆; Γ; C ` x = h at v ⇒ ∆; Γ{x:τ }; C Ψ; ∆; Γ ` v : hτ0 , . . . , τn−1 i at r ∆ ` C ≤ C 0 ⊕ {r+ } (x 6∈ Dom(Γ) ∧ 0 ≤ i < n) Ψ; ∆; Γ; C ` x = πi v ⇒ ∆; Γ{x:τi }; C Ψ; ∆; Γ; C ` newrgn ρ, x ⇒ ∆{ρ:Rgn}; Γ{x:ρ handle}; C ⊕ {ρ1 } (ρ 6∈ Dom(∆), x 6∈ Dom(Γ)) Ψ; ∆; Γ ` v : r handle ∆ ` C = C 0 ⊕ {r1 } : Cap Ψ; ∆; Γ; C ` freergn v ⇒ ∆; Γ; C 0 Ψ; ∆; Γ; C ` e Ψ; ∆; Γ; C ` d ⇒ ∆0 ; Γ0 ; C 0 Ψ; ∆0 ; Γ0 ; C 0 ` e Ψ; ∆; Γ; C ` let d in e Ψ; ∆; Γ ` v : int Ψ; ∆; Γ; C ` e2 Ψ; ∆; Γ; C ` e3 Ψ; ∆; Γ; C ` if0 v then e2 else e3 Ψ; ∆; Γ ` v : ∀[ ].(C 0 , τ1 , . . . , τn ) → 0 at r Ψ; ∆; Γ ` vi : τi (for 1 ≤ i ≤ n) ∆ ` C ≤ C 00 ⊕ {r+ } ∆ ` C ≤ C0 Ψ; ∆; Γ; C ` v(v1 , . . . , vn ) Ψ; ∆; Γ ` v : int ∆ ` C = ∅ : Cap Ψ; ∆; Γ; C ` halt v Figure 6: Capability Static Semantics: Declarations and Expressions Ψ ` C sat Ψ ` R at ν : Υ `M :Ψ `P · ` C = {ν1ϕ1 , . . . , νnϕn } : Cap (νi 6= νj for 1 ≤ i, j ≤ n and i 6= j) {ν1 : Υ1 , . . . , νn : Υn } ` C sat `Ψ Ψ ` Ri at νi : Υi (for 1 ≤ i ≤ n) (Ψ = {ν1 : Υ1 , . . . , νn : Υn }) ` {ν1 7→ R1 , . . . , νn 7→ Rn } : Ψ Ψ; ·; · ` hi at ν : τi (for 1 ≤ i ≤ n) Ψ ` {`1 7→ h1 , . . . , `n 7→ hn } at ν : {`1 : τ1 , . . . , `n : τn } `M :Ψ Ψ ` C sat ` (M, e) Ψ; ·; ·; C ` e Figure 7: Capability Static Semantics: Memory 14

* Your assessment is very important for improving the work of artificial intelligence, which forms the content of this project

Download PDF

advertising