Typed Memory Management in a Calculus of Capabilities Karl Crary David Walker

Typed Memory Management in a Calculus of Capabilities Karl Crary David Walker
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
Was this manual useful for you? yes no
Thank you for your participation!

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

Download PDF

advertising