Uniqueness and Reference Immutability for Safe Parallelism

20
Uniqueness and Reference Immutability for Safe Parallelism Colin S. Gordon , Matthew J. Parkinson , Jared Parsons , Aleks Bromfield , Joe Duffy University of Washington, Microsoft Research Cambridge, Microsoft Corporation [email protected], {mattpark,jaredpar,albromfi,joedu}@microsoft.com Abstract A key challenge for concurrent programming is that side- effects (memory operations) in one thread can affect the be- havior of another thread. In this paper, we present a type sys- tem to restrict the updates to memory to prevent these unin- tended side-effects. We provide a novel combination of im- mutable and unique (isolated) types that ensures safe paral- lelism (race freedom and deterministic execution). The type system includes support for polymorphism over type quali- fiers, and can easily create cycles of immutable objects. Key to the system’s flexibility is the ability to recover immutable or externally unique references after violating uniqueness without any explicit alias tracking. Our type system models a prototype extension to C# that is in active use by a Mi- crosoft team. We describe their experiences building large systems with this extension. We prove the soundness of the type system by an embedding into a program logic. Categories and Subject Descriptors D.3.3 [Language Constructs and Features]: Concurrent programming struc- tures; F.3.2 [Semantics of Programming Languages]: Pro- gram Analysis General Terms Languages, Verification Keywords reference immutability, type systems, concur- rency, views 1. Introduction In concurrent programs, side-effects in one thread can affect the behavior of another thread. This makes programs hard to understand as programmers must consider the context in which their thread executes. In a relaxed memory setting even understanding the possible interactions is non-trivial. We wish to restrict, or tame, side-effects to make pro- grams easier to maintain and understand. To do so, we build Permission to make digital or hard copies of all or part of this work for personal or classroom use is granted without fee provided that copies are not made or distributed for profit or commercial advantage and that copies bear this notice and the full citation on the first page. To copy otherwise, to republish, to post on servers or to redistribute to lists, requires prior specific permission and/or a fee. OOPSLA’12, October 19–26, 2012, Tucson, Arizona, USA. Copyright c 2012 ACM 978-1-4503-1561-6/12/10. . . $10.00 on reference immutability [37, 39, 40], which uses permis- sion type qualifiers to control object mutation. Typically there are notions of writable references (normal access), read-only references (objects may not be mutated via a read- only reference, and field reads from read-only references produce read-only references), and immutable references (whose referents may never be changed through any alias). There are many applications of this approach to controlling side effects, ranging from improving code understanding to test generation to compiler optimization. We add to reference immutability a notion of isolation in the form of an extension to external uniqueness [23]. We support the natural use of isolation for object immutabil- ity (making objects permanently immutable through all ref- erences). But we also show a new use: to recover isola- tion or strengthen immutability assumptions without any alias tracking. To achieve this we give two novel typing rules, which allow recovering isolated or immutable refer- ences from arbitrary code checked in environments contain- ing only isolated or immutable inputs. We provide two forms of parallelism: Symmetric Assuming that at most one thread may hold writable references to an object at a given point in time, then while all writable references in a context are tem- porarily forgotten (framed away, in the separation logic sense [29, 33]), it becomes safe to share all read-only or immutable references among multiple threads, in ad- dition to partitioning externally-unique clusters between threads. Asymmetric If all data accessible to a new thread is im- mutable or from externally-unique clusters which are made inaccessible to the spawning thread, then the new and old threads may run in parallel without interference. We provide an extended version of the type system with polymorphism over reference immutability qualifiers. This maintains precision for instantiated uses even through rich patterns like iterators, which was not possible in previous work [39]. There are several aspects of this work which we are the first to do. We are the first to give a denotational meaning to reference immutability qualifiers. We are the first to formal- ize the use of reference immutability for safe parallelism. We

Transcript of Uniqueness and Reference Immutability for Safe Parallelism

Page 1: Uniqueness and Reference Immutability for Safe Parallelism

Uniqueness and Reference Immutability for Safe Parallelism

Colin S. Gordon†, Matthew J. Parkinson‡, Jared Parsons�, Aleks Bromfield�, Joe Duffy�†University of Washington, ‡Microsoft Research Cambridge, �Microsoft [email protected], {mattpark,jaredpar,albromfi,joedu}@microsoft.com

AbstractA key challenge for concurrent programming is that side-effects (memory operations) in one thread can affect the be-havior of another thread. In this paper, we present a type sys-tem to restrict the updates to memory to prevent these unin-tended side-effects. We provide a novel combination of im-mutable and unique (isolated) types that ensures safe paral-lelism (race freedom and deterministic execution). The typesystem includes support for polymorphism over type quali-fiers, and can easily create cycles of immutable objects. Keyto the system’s flexibility is the ability to recover immutableor externally unique references after violating uniquenesswithout any explicit alias tracking. Our type system modelsa prototype extension to C# that is in active use by a Mi-crosoft team. We describe their experiences building largesystems with this extension. We prove the soundness of thetype system by an embedding into a program logic.

Categories and Subject Descriptors D.3.3 [LanguageConstructs and Features]: Concurrent programming struc-tures; F.3.2 [Semantics of Programming Languages]: Pro-gram Analysis

General Terms Languages, Verification

Keywords reference immutability, type systems, concur-rency, views

1. IntroductionIn concurrent programs, side-effects in one thread can affectthe behavior of another thread. This makes programs hardto understand as programmers must consider the context inwhich their thread executes. In a relaxed memory settingeven understanding the possible interactions is non-trivial.

We wish to restrict, or tame, side-effects to make pro-grams easier to maintain and understand. To do so, we build

Permission to make digital or hard copies of all or part of this work for personal orclassroom use is granted without fee provided that copies are not made or distributedfor profit or commercial advantage and that copies bear this notice and the full citationon the first page. To copy otherwise, to republish, to post on servers or to redistributeto lists, requires prior specific permission and/or a fee.OOPSLA’12, October 19–26, 2012, Tucson, Arizona, USA.Copyright c© 2012 ACM 978-1-4503-1561-6/12/10. . . $10.00

on reference immutability [37, 39, 40], which uses permis-sion type qualifiers to control object mutation. Typicallythere are notions of writable references (normal access),read-only references (objects may not be mutated via a read-only reference, and field reads from read-only referencesproduce read-only references), and immutable references(whose referents may never be changed through any alias).There are many applications of this approach to controllingside effects, ranging from improving code understanding totest generation to compiler optimization.

We add to reference immutability a notion of isolation inthe form of an extension to external uniqueness [23]. Wesupport the natural use of isolation for object immutabil-ity (making objects permanently immutable through all ref-erences). But we also show a new use: to recover isola-tion or strengthen immutability assumptions without anyalias tracking. To achieve this we give two novel typingrules, which allow recovering isolated or immutable refer-ences from arbitrary code checked in environments contain-ing only isolated or immutable inputs.

We provide two forms of parallelism:

Symmetric Assuming that at most one thread may holdwritable references to an object at a given point in time,then while all writable references in a context are tem-porarily forgotten (framed away, in the separation logicsense [29, 33]), it becomes safe to share all read-onlyor immutable references among multiple threads, in ad-dition to partitioning externally-unique clusters betweenthreads.

Asymmetric If all data accessible to a new thread is im-mutable or from externally-unique clusters which aremade inaccessible to the spawning thread, then the newand old threads may run in parallel without interference.

We provide an extended version of the type system withpolymorphism over reference immutability qualifiers. Thismaintains precision for instantiated uses even through richpatterns like iterators, which was not possible in previouswork [39].

There are several aspects of this work which we are thefirst to do. We are the first to give a denotational meaning toreference immutability qualifiers. We are the first to formal-ize the use of reference immutability for safe parallelism. We

Page 2: Uniqueness and Reference Immutability for Safe Parallelism

Figure 1. External uniqueness with immutable out-references.

are the first to describe industry experience with a referenceimmutability type system.

2. Reference Immutability, Uniqueness, andParallelism

Reference immutability is based on a set of permission-qualified types. Our system has four qualifiers:

writable: An “ordinary” object reference, which allows mu-tation of its referent.

readable: A read-only reference, which allows no mu-tation of its referent. Furthermore, no heap traversalthrough a read-only reference produces a writable ref-erence (writable references to the same objects may existand be reachable elsewhere, just not through a readablereference). A readable reference may also refer to an im-mutable object.

immutable: A read-only reference which additionally notesthat its referent can never be mutated through any refer-ence. Immutable references may be aliased by read-onlyor immutable references, but no other kind of reference.All objects reachable from an immutable reference arealso immutable.

isolated: An external reference to an externally-uniqueobject cluster. External uniqueness naturally capturesthread locality of data. An externally-unique aggregateis a cluster of objects that freely reference each other,but for which only one external reference into the ag-gregate exists. We define isolation slightly differentlyfrom most work on external uniqueness because we alsohave immutable objects: all paths to non-immutable ob-jects reachable from the isolated reference pass throughthe isolated reference. We allow references out of theexternally-unique aggregate to immutable data because itadds flexibility without compromising our uses for iso-lation: converting clusters to immutable, and support-ing non-interference among threads (see Figure 1). Thischange in definition does limit some traditional uses ofexternally-unique references that are not our focus, suchas resource management tasks.

The most obvious use for reference immutability is to con-trol where heap modification may occur in a program, simi-

readable

↗ ↖writable immutable

↖ ↗isolated

Figure 2. Qualifier conversion/subtyping lattice.

lar to the owner-as-modifier discipline in ownership and uni-verse type systems [14]. For example, a developer can besure that a library call to a static method with the type signa-ture

int countElements(readable ElementList lst);

will not modify the list or its elements (through the lst

reference). Accessing any field of the argument lst throughthe readable reference passed will produce other readable(or immutable) results. For example, a developer could notimplement countElements like so:

int countElements(readable ElementList lst)

{ lst.head = null; return 0; }

because the compiler would issue a type error. In fact, anyattempt within countElements() to modify the list wouldresult in a type error, because lst is deeply (transitively)read-only, and writes through read-only references are pro-hibited.

2.1 Conversion from IsolatedThe isolated qualifier is atypical in reference immutabilitywork, and is not truly a permission for (im)mutability in thepurest sense. In fact, we require that isolated referencesbe converted through subtyping to another permission beforeuse, according to the type qualifier hierarchy in Figure 2.

isolated references are particularly important in oursystem for two reasons. First, they naturally support safeparallelism by partitioning mutable data amongst threads.The threads1 in the following example cannot interfere witheach other, because the object graphs they operate on andcan mutate are disjoint:

isolated IntList l1 = ...;

isolated IntList l2 = ...;

{ l1.map(new Incrementor()); }

|| { l2.map(new Incrementor()); }

Second, the control of aliasing allows conversion of wholeexternally-unique object clusters. If there are no external ref-erences besides the isolated reference, then the whole ob-ject graph (up to immutable objects) can be converted atonce. An isolated reference (and object graph) can triv-ially be converted to writable, by essentially surrenderingthe aliasing information:

1 We use ‖ for structured parallelism, and the formal system does not havedynamic thread creation.

Page 3: Uniqueness and Reference Immutability for Safe Parallelism

isolated IntList l = ...;

// implicitly update l’s permission to writable

l.head = ...;

Or an isolated graph can be converted to immutable; aswith any form of strong update, the decision to treat thewhole object graph as immutable is localized:

isolated IntList l = ...;

// implicitly update l’s permission to immutable

immutable IntList l2 = l;

l.head = ...; // Type Error!

The type system is flow sensitive, so although l was initiallyisolated after the assignment to l2 it has been coerced toimmutable and thus cannot be written to.

2.2 Recovering IsolationA key insight of our approach is that converting an isolatedreference to writable does not require permanently sur-rendering the aliasing information. In particular, if the inputtype context for an expression contains only isolated and im-mutable objects, then if the output context contains a singlewritable reference, we can convert that reference back toisolated. Consider the following method:

isolated IntBox increment(isolated IntBox b) {

// implicitly convert b to writable

b.value++;

// convert b *back* to isolated

return b;

}

The first conversion from isolated to writable occursnaturally by losing aliasing information. The second conver-sion is safe because if one writable reference is left whenthe initial context contained only isolated and immutablereferences, that reference must either refer to an object thatwas not referenced from elsewhere on entry, or was freshlyallocated (our core language and prototype do not allow mu-table global variables).

This flexibility is especially useful for algorithms that re-peatedly map destructive operations over data in parallel. Bykeeping data elements as isolated, the map operations nat-urally parallelize, but each task thread can internally violateuniqueness, apply the updates, and recover an isolated

reference for the spawning context for later parallelization(Section 2.5).

Recovering isolation is reminiscent of borrowing — al-lowing temporary aliases of a unique reference, often in ascope-delimited region of program text. The main advantageof recovery is that unlike all borrowing designs we are awareof, recovery requires no tracking or invalidation of specificreferences or capabilities as in other work [10, 23]. Of coursethis is a result of adding reference immutability, so recoveryis not a stand-alone replacement for traditional borrowing; itis an additional benefit of reference immutability.

We also see two slight advantages to our recovery ap-proach. First, a single use of recovery may subsume multiple

uses of a scoped approach to borrowing [30], where exter-nal uniqueness is preserved by permitting access to only theinterior of a particular aggregate within a lexically scopedregion of code. Of course, scopeless approaches to borrow-ing exist with more complex tracking [10, 23]. Second, nospecial source construct is necessary beyond the referenceimmutability qualifiers already present for parallelism.

2.3 Recovering Immutability, and Cycles ofImmutable Objects

Another advantage of using isolated references is that thedecision to make data immutable can be deferred (arbitrar-ily). This makes constructing cycles of immutable objectseasy and natural to support. The mechanism for convertingan isolated reference to immutable is similar to recover-ing isolation, with the natural direct conversion being a spe-cial case. If the input context when checking an expressioncontains only isolated and immutable references, and theoutput context contains one readable reference (or in gen-eral, multiple readable references), then the readable refer-ent must be either an already-immutable object or an objectnot aliased elsewhere that it is safe to now call immutable.The simplest case of this (equivalent to direct conversion) isto frame away all references but one, convert to readable,and then recover immutability:

immutable IntBox freeze(isolated IntBox b) {

// implicitly convert b to readable

// implicitly recover immutability;

// the input context was all isolated

return b;

}

Creating cycles of immutable objects is then simplya matter of restricting the input to a conversion to onlyisolated and immutable data, then recovering. This caneven include recovering immutability from regular code:

// The default permission is writable

CircularListNode make2NodeList() {

CircularListNode n1 = new CircularListNode();

CircularListNode n2 = new CircularListNode();

n1.next = n2; n1.prev = n2;

n2.next = n1; n2.prev = n1;

return n1;

}

...

immutable l = make2NodeList();

Here the method has no inputs and it returns a writablevalue, so at the call site anything it returns can be consid-ered readable, then recovered to immutable (or directlyrecovered to isolated).

Prior reference immutability systems [39] required build-ing immutable cyclic data structures in the constructor of oneobject, using extensions to pass a partially-initialized objectduring construction as (effectively) immutable to the con-structor of another object. Our use of isolated with recov-

Page 4: Uniqueness and Reference Immutability for Safe Parallelism

ery means we do not need to explicitly model the initializa-tion period of immutable structures.

While we have been using closed static method defini-tions to illustrate the recovery rules, our system includes aframe rule [29, 33], so these conversions may occur in local-ized sections of code in a larger context.

2.4 Safe Symmetric ParallelismFork-join concurrency is deterministic when neither forkedthread interferes with the other by writing to shared memory.Intuitively, proving its safe use requires separating read andwrite effects, as in Deterministic Parallel Java (DPJ) [4].With reference immutability, a simpler approach is availablethat does not require explicit region management, allowingmuch of the same expressiveness with simpler annotation(see Section 7).

If neither forked thread requires any writable referenceinputs to type check, then it is safe to parallelize, even ifthe threads share a readable reference to an object thatmay be mutated later, and even if threads receive isolatedreferences.

x = new Integer(); x.val = 3; y = x; z = x;

// y and z are readable aliases of x

a = new Integer(); b = new Integer();

// a and b are isolated

// frame away writable references (x)

a.val = y.val; || b.val = z.val;

// get back writable references (x)

x.val = 4;

After joining, x may be “unframed” and the code regainswritable access to it. Safety for this style of parallelism is anatural result of reference immutability, but proving it sound(race free) requires careful handling of coexisting writablereferences to the temporarily-shared objects.

We require that each thread in the parallel composition re-ceives disjoint portions of the stack, though richer treatmentsof variable sharing across threads exist [31, 32].

2.5 Safe Asymmetric ParallelismC# has an async construct that may execute a block ofcode asynchronously via an interleaving state machine oron a new thread [3], and returns a handle for the block’sresult in the style of promises or futures. A common usecase is asynchronously computing on separated state whilethe main computation continues. Our formal system modelsthe asymmetric data sharing of this style of use on top ofstructured parallelism. The formal system (Section 3) doesnot model the first-class join; in future work we intend toextend this rule to properly isolate async expressions.

A natural use for this style of parallelism is to have theasynchronous block process a limited data set in parallelwith a “main” thread’s execution. One definition of “limited”is to restrict the “worker” thread to isolated and immutabledata, allowing the “main” thread to proceed in parallel whileretaining writable references it may have.

writable Integer x = ...;

// construct isolated list of isolated integers

y = new IsolatedIntegerList();

... // populate list

f = new SortFunc();

// Sort in parallel with other work

y.map(f); || x.val = 3;

This code also demonstrates the flexibility of combiningthe rules for recovering isolated or immutable referenceswith parallelism. In the left thread, f and y are both isolatedon entry, and the rule for recovering an isolated referencecan be applied to y at that thread’s finish. Thus, when thethreads join, y is again isolated, and suitable for furtherparallelization or full or partial conversion to immutable.

3. Types for Reference Immutability andParallelism

We describe a simple core imperative, object-oriented lan-guage in Figure 3. Commands (statements) include standardfield and variable assignments and reads, sequencing, loops,non-deterministic choice (to model conditional statements)and fork-join style parallelism. Our language also includes adestructive read, x = consume(y.f), which reads the field,y.f , stores its value in x, and then updates the field to null.Our types include primitive types and permission-qualifiedclass types. We include the four permissions from Section2: readable, writable, isolated, and immutable. Thissection focuses on the language without methods, which areadded in Section 3.3. Polymorphism, over both class typesand permissions, is described in Section 5.

One of our primary goals for this core system is to under-stand the design space for source languages with referenceimmutability and concurrency in terms of an intermediate-level target language. This approach permits understandingsource-level proposals for typing higher level language fea-tures (such as closures) in terms of translation to a well-typed intermediate form (such as the function objects C#closures compile into), rather than independently reasoningabout their source level behavior.

The heart of reference immutability is that a reference’spermission applies transitively. Any new references acquiredthrough a reference with a given permission cannot allowmodifications that the root reference disallows. We modelthis through a permission combining relation B, borrowingintuition and notation from universe types’ “viewpoint adap-tation” [14]. We define B and lift it to combining with typesin Figure 3.

Generally speaking, this relation propagates the weak-est, or least permissive, permission. Notice that there areno permission-combining rules for isolated receivers andnon-immutable fields; this reflects the requirement that ac-cessing an isolated object graph generally requires upcast-ing variables first and accessing isolated fields requiresdestructive reads. Also notice that any combination involv-

Page 5: Uniqueness and Reference Immutability for Safe Parallelism

Metavariables

a atomsC command (statement)w, x, y, z variablest, u typesT,U class typeTD class type declarationcn class namep permissionfld field declarationmeth method declarationf, g field namesm method namesn, i, j nat (indices)

Syntax

a ::=| x = y| x.f = y| x = y.f| x = consume(y.f)| x = y.m(z1, ..., zn)| x = new t()| return x

C ::= a | skip | C;C | C + C | C‖C | C∗p ::= readable | writable | immutable | isolated

T ::= cnTD ::= class cn [<: T2] {field ∗ meth∗ }fld ::= t fnmeth ::= t m(t1 x1, ..., tn xn)p{ C; return x ; }t ::= int | bool | p TΓ ::= ε | Γ, x : t

B : Permission→ Permission→ PermissionimmutableB = immutable

B immutable = immutable

readableB writable = readable

readableB readable = readable

writableB readable = readable

writableB writable = writable

pB int = int

pB bool = bool

pB (p′ T ) = (pB p′) T

Figure 3. Core language syntax.

ing immutable permissions produces an immutable permis-sion; any object reachable from an immutable object is alsoimmutable, regardless of a field’s declared permission.

We use type environments Γ, and define subtyping on en-vironments (` Γ ≺ Γ) in terms of subtyping for permissions(` p ≺ p), class types (` T ≺ T ), and permission-qualifiedtypes (` t ≺ t) in Figure 4.

Figure 5 gives the core typing rules. These are mostlystandard aside from the treatment of unique references. Adestructive field read (T-FIELDCONSUME) is fairly stan-dard, and corresponds dynamically to a basic destructiveread: as the command assigns null to the field, it is soundto return an isolated reference. Writes to isolated fields(T-FIELDWRITE) and method calls with unique arguments(T-CALL) treat the isolated input references as affine re-sources, consumed by the operation. We use a metafunctionRemIso() to drop “used” isolated references:

` p ≺ p′ ` p ≺ p ` p ≺ readable ` isolated ≺ p

` T ≺ T ′class c <: d {fld meth } ∈ P

` c ≺ d S-DECL

` t1 ≺ t2` p ≺ p′

` p T ≺ p′ TS-PERM

` T ≺ T ′

` p T ≺ p T ′S-TYPE

` t ≺ t S-REFLEXIVE` t1 ≺ t2 ` t2 ≺ t3

` t1 ≺ t3S-TRANS

` Γ ≺ Γ′ ε ≺ ε S-EMPTY` Γ ≺ Γ′ ` t ≺ t′

` Γ, x : t ≺ Γ′, x : t′S-CONS

` Γ ≺ Γ′

` Γ, x : t ≺ Γ′S-DROP

Figure 4. Subtyping rules

RemIso() : Γ→ ΓRemIso(Γ) = filter (λx. x 6= isolated ) Γ

This is a slight inconvenience in the core language, butthe implementation supports consume as a first class effect-ful expression. The method rule is otherwise straightforwardaside from calls on isolated receivers (Section 3.3). Wealso provide structural rules to allow these rules to be used inmore general contexts (last two rows of Figure 5). The def-inition of well-formed programs is mostly standard (shownin our technical report [20]), aside from requiring covariantmethod permissions for method overrides (Figure 6).

3.1 Recovery RulesFigure 7 gives the two promotion rules from Sections 2.2and 2.3 that are key to our system’s flexibility: the rulesfor recovering isolated or immutable references, used forboth precision and conversion. These rules restrict their in-put contexts to primitives, externally unique references, andimmutable references. The T-RECOVISO, checks the vari-able in the premise x must either be null, or point into afreshly-allocated or previously present (in Γ) object aggre-gate with no other references, and thus it is valid to considerit isolated. Similarly T-RECOVIMM checks sufficient prop-erties to establish that it is safe to consider it immutable. Inpractice, using these relies on the frame rule (Figure 5).

Without reference immutability, such simple rules for re-covery (sometimes called borrowing) would not be possi-ble. In some sense, the information about permissions inthe rules’ input contexts gives us “permissions for free.”We may essentially ignore particular permissions (isolation)for a block of commands, because knowledge of the inputcontext ensures the writable or readable output in eachpremise is sufficiently separated to convert if necessary (tak-ing advantage of our slight weakening of external unique-ness to admit references to shared immutable objects). Sec-tion 4.2 elaborates on the details of why we can prove this

Page 6: Uniqueness and Reference Immutability for Safe Parallelism

Γ1 ` C a Γ2

t 6= isolated

x : , y : t ` x = y a y : t, x : tT-ASSIGNVAR ` x = new T () a x : isolated T

T-NEW

t′ f ∈ T p 6= isolated ∨ t′ = immutable t′ 6= isolated ∨ p = immutable

x : , y : p T ` x = y.f a y : p T, x : pB t′T-FIELDREAD

t f ∈ Ty : writable T, x : t ` y.f = x a y : writable T,RemIso(x : t)

T-FIELDWRITE

isolated Tf f ∈ Ty : writable T ` x = consume(y.f) a y : writable T, x : isolated Tf

T-FIELDCONSUME

x : ` x = n a x : intT-INT

x : ` x = b a x : boolT-BOOL

x : ` x = null a x : p TT-NULL

t′ m(u′ z′) p′ ∈ T ` p ≺ p′ ` u ≺ u′p = isolated =⇒ t 6= readable ∧ t 6= writable ∧ IsoOrImm(z : t) ∧ p′ 6= immutable

y : p T, z : u ` x = y.m(z) a y : p T,RemIso(z : t), x : t′T-CALL

Γ1 ≺ Γ′1 Γ′1 ` C a Γ′2 Γ′2 ≺ Γ2

Γ1 ` C a Γ2T-SUBENV

Γ1 ` C a Γ2

Γ,Γ1 ` C a Γ,Γ2T-FRAME

Γ ` C a ΓΓ ` C∗ a Γ

T-LOOP

Γ1 ` C1 a Γ2 Γ2 ` C2 a Γ3

Γ1 ` C1;C2 a Γ3T-SEQ

Γ1 ` C1 a Γ2 Γ1 ` C2 a Γ2

Γ1 ` C1 + C2 a Γ2T-BRANCH

Γ, y : t′, x : t,Γ′ ` C a Γ′′

Γ, x : t, y : t′,Γ′ ` C a Γ′′T-SHUFFLE

Figure 5. Core typing rules.

TD = class cn [<: T2] {fld meth } t′ m(t′ x′) p′ ∈ T2 P ` t ≺ t′ P ` p′ ≺ p P ` t′ ≺ tp 6= isolated ∀i ∈ [1 . . . n]. P ` ti P ` t this : p cn, t x ` C; return x a result : t

P ;TD ` t m(t x) p { C; return x ; }T-METHOD2

Figure 6. Method override definition typing

IsoOrImm(Γ) IsoOrImm(Γ′)Γ ` C a Γ′, x : writable T

Γ ` C a Γ′, x : isolated TT-RECOVISO

IsoOrImm(Γ) IsoOrImm(Γ′)Γ ` C a Γ′, x : readable T

Γ ` C a Γ′, x : immutable TT-RECOVIMM

where IsoOrImm(Γ)def= ∀(x : p T ) ∈ Γ. ` p ≺ immutable

Figure 7. Recovery rules

is sound. Additionally, the permission qualifications spec-ify which references may safely interact with an externally-unique aggregate, and which must be prevented from inter-acting via the frame rule (readable and writable refer-ences). This distinction normally requires precise reasoningabout aliases.

3.2 Safe ParallelismFigure 8 gives the rules for safe parallelism. They ensuredata race freedom, and therefore (for the concurrency prim-itives we provide) deterministic execution. T-PAR corre-sponds to safe symmetric parallelism, when all writablereferences are framed out. The second rule T-ASYNC cor-

NoWrit(Γ1) NoWrit(Γ2) Γ1 ` C1 a Γ′1 Γ2 ` C2 a Γ′2

Γ1,Γ2 ` C1||C2 a Γ′1,Γ′2

T-PAR

IsoOrImm(Γ1) Γ1 ` C1 a Γ′1 Γ2 ` C2 a Γ′2

Γ1,Γ2 ` C1||C2 a Γ′1,Γ′2

T-ASYNC

where NoWrit(Γ)def= ∀(x : p T ) ∈ Γ. p 6= writable

Figure 8. Type rules for safe parallelism. IsoOrImm is de-fined in Figure 7

responds to the safety criteria for asymmetric parallelism(named for C#’s async block). This rule obviously producesstructured parallelism, not the unstructured task-based con-currency present in C#. But it models the state separationrequired for safe task parallelism: all input to a task mustbe isolated or immutable. The implementation provides safetask parallelism of this form, as described in Section 6.1, aswell as structured parallelism.

3.3 MethodsThe type rule for a method call (T-CALL) is shown in Fig-ure 5. It is mostly standard (the method exists in the receivertype, actual arguments are subtypes of formal arguments),

Page 7: Uniqueness and Reference Immutability for Safe Parallelism

with a couple of complications. First, isolated actual ar-guments are forgotten by the typing context, in lieu of ex-tending the method syntax for destructive reads.

Second, methods have required calling permissions, whichrestrict the side effects a method may have on the receiver.The permission on the receiver at the call site must be at leastas permissive as the required permission (e.g., a programcannot call a writable method on a readable receiver).This is standard for reference immutability [37, 39, 40].

Finally, additional restrictions apply when the receiver isisolated. Intuitively, no isolated method may return an aliasto an object inside its isolation bubble; alternatively, the re-strictions ensure that an inlined method body is suitable forupcasting the isolated receiver’s permission, executing,and finally applying T-RecovIso. Because no method istype checked with this : isolated T (by T-METHOD*in Figure 6 and our technical report [20], no method mayrequire an isolated receiver permission), no method mayleverage recovery rules (Figure 7) to recover an isolated

or immutable reference to its receiver. Thus any meth-ods returning primitives (int and bool) or isolated orimmutable references is returning a value that does not vi-olate external uniqueness for the receiver’s bubble.

3.4 ExamplesFor brevity, because the type environment is flow sensitive,we write typing derivations in the same style as a proof in aprogram logic, with pre- and post-conditions of a statementin braces before and afterwards. Unmarked adjacent asser-tions represent use of the rule of implication (subtyping).Uses of other transformation rules are labeled. In Section 4,it will become clear how this style directly models the cor-responding proof of type checking in the program logic.

3.4.1 Assigning an Isolated VariableAssigning an isolated variable consists of framing awayouter context, upcasting the isolated reference to writable,assigning normally, weakening to drop the source variable,and an application of T-RECOVISO to recover the isolationproperty on the destination variable. It is possible to add anadmissible rule for the direct consumption. It is also possibleto preserve access to the source variable by also overwritingit with a primitive value such as null, which is equivalent toan encoding of a traditional destructive read on a variable.

{Γ, x : bool, y : isolated T}{x : bool, y : isolated T}{x : bool, y : isolated T}{x : bool, y : writable T}x = y{x : writable T, y : writable T}{x : writable T}

T-S

UB

EN

V

{x : isolated T}

T-R

EC

OV

ISO

{Γ, x : isolated T}

T-F

RA

ME

3.4.2 Temporarily Violating IsolationFigure 9 shows the type derivation for a simple use of theT-RECOVISO rule, adding a node to an isolated list. Theinner portion of the derivation is not notable, simply naturaluse of sequencing, allocation, and field write rules. But thatinner portion is wrapped by a use of subtyping, followedby recovering an isolated reference. Using T-RECOVIMMto recover an immutable reference would be similar, usinga readable reference to the list after the updates.

4. Type SoundnessTo prove soundness, we must define the dynamic languagesemantics and relate the typing rules. The dynamic seman-tics for commands C are standard small step operational se-mantics over the states we define below, so we omit themhere. The operational rule for reducing an atom a appealsto a denotational semantics of atoms, which is defined inan entirely standard way and therefore also omitted (methodcalls have some subtlety, but conform to standard intuition ofevaluating method calls by inlining method bodies, fully de-tailed in our technical report [20]). We relate the type rules tothe semantics by defining a denotation of type environmentsin terms of an extended machine state.

We define abstract machine states as:

S def= Stack× Heap× TypeMap

where Stackdef= Var ⇀ Val and is ranged over by s, Heap

def=

OID×Field ⇀ Val and is ranged over by h, and TypeMapdef=

OID ⇀ Class and is ranged over by t.We only consider well-typed states. To define well-typed

states, we assume a function that gives the type and permis-sion for each field of each class, reflects inheritance of fields,and in Section 5 handles instantiating field types of polymor-phic types:

FType : Class× Field→ Type

We can describe a state (s, h, t) as well-typed iff

WellTyped(s, h, t) = ∀o, f.(∃v. h(o, f) = v)

⇐⇒ (∃ft .FType(t(o), f) = ft)∧ ∀ft .FType(t(o), f) = ft =⇒ft = p c′ ∧ h(o, f) 6= null =⇒ ` t(h(o, f)) ≺ c′

∧ ft = bool =⇒ ∃b. h(o, f) = b∧ ft = int =⇒ ∃n. h(o, f) = n

The first conjunct requires that the type map contains a typefor every object in the heap, and vice-versa; it limits thetype map to the heap contents. The second conjunct simplyenforces that each field holds well-typed contents.

Reasoning about which objects are immutable and thepermissions of various references is somewhat difficult forsuch a basic state space, so we define an instrumented statewith additional metadata: a partitioning of objects among

Page 8: Uniqueness and Reference Immutability for Safe Parallelism

{x : isolated Node, y : int, z : bool}{x : isolated Node, y : int, z : bool}{x : writable Node, y : int, z : bool}{x : writable Node, y : int, z : bool}y=new Node();{x : writable Node, y : writable Node, z : bool}

T-A

LL

OC

z=x.next;{x : writable Node, y : writable Node, z : writable Node}y.next=z;{x : writable Node, y : writable Node, z : writable Node}z.prev=y;{x : writable Node, y : writable Node, z : writable Node}y.prev=x;{x : writable Node, y : writable Node, z : writable Node}x.next=y;{x : writable Node, y : writable Node, z : writable Node}{x : writable Node, y : writable Node, z : writable Node}

T-S

EQ∗

acro

ssT-

FIE

LD

RE

AD

and

T-F

IEL

DW

RIT

E

{x : writable Node}

T-S

UB

EN

V

{x : isolated Node}

T-R

EC

OV

ISO

Figure 9. Typing derivation for adding a node to an isolated doubly-linked list.

regions, and permission to each region (important for safeparallelism).

We map each object to a region

r : RegionMap = OID ⇀ Region

We have three forms of region:

• Root(ρ) is a root region with abstract root ρ• Field(o, f) means the region is only accessible through

the isolated field f of object o.• Immutable means immutable

We associate two permissions with each root region:

π : RegionPerm = Root ⇀ Update[0, 1]× Reference(0, 1]

where

• Update: Is used to indicate if objects in the region canbe modified. Full (1) means this is the case. An updatepermission will be split for the period of a parallel com-position, as a fractional permission [9].• Reference: Is used to indicate whether there is a framed-

out reference to this region (< 1). This prevents the con-version of a region to isolated or immutable when thereare framed-out readable or writable references to it.Note that 0 reference permission is not allowed; stateswith no permission at all to a region do not have that per-mission in their permission map.

For both permissions, the total permission available to theprogram for any given region is 1. These two permissiontypes capture two interference concepts. You can interferewith yourself; and you cannot interfere with other threads.Interference with other threads is prevented by the update

permission, only one thread can ever have an update permis-sion to a region.

These states also satisfy a well-formedness predicates.We require instrumented states to be well-regioned: e.g.an immutable reference points to an object in region Im-mutable, no readable or writable reference outside agiven externally unique aggregate points to an object in anisolated region, etc. We define well-regioned given in Fig-ure 10. The first conjunct ensures full region information forthe heap’s objects. The second, largest conjunct enforces re-strictions on references between regions. Intra-region point-ers must be either within the Immutable region, or followingreadable or writable fields. Cross-region pointers mustnot be pointing out of Immutable (which is closed underfield dereference), and must either pointing into Immutablefrom fields with appropriate permissions, an isolated fieldpointing into an appropriate Field region, or a readable

reference between root regions. The next conjunct requirespermissions on any root region, and the final conjunct lim-its the region map’s Fields to those whose entry points arepresent in the heap.

We can thus define an instrumented state as:

M =

{m ∈ S × RegionMap× RegionPerm|WellRegioned(m) ∧WellTyped(bmc)

}where we define an erasure b·c : M → S that projects in-strumented states to the common components with S . Weuse m.s, m.h, m.t, m.r, and m.π to access the stack, heap,type map, region map and region permission map, respec-tively.

We view type environments denotationally, in terms ofthe set of instrumented states permitted by a given envi-

Page 9: Uniqueness and Reference Immutability for Safe Parallelism

WellRegioned(s, h, t, r, π) =CompleteRegionInfo(s, h, t, r, π)∧∀o, f, v, p. h(o, f) = v ∧ v ∈ OID ∧ FType(t(o), f) = p=⇒(r(o) = r(v) =⇒

r(o) = Immutable ∨ p ∈ {readable, writable})∧ (r(o) 6= r(v) =⇒ ValidXRegionRef(r, o, f, p, v))

∧ (∀ρ.Root ρ ∈ Img(r) =⇒ π(ρ)(≥, >)(0, 0))∧ (∀o, f.Field(o, f) ∈ Img(r) =⇒ (o, f) ∈ dom(h))

where

ValidXRegionRef(r, o, f, p, v) =(r(o) 6= Immutable)∧ (r(v) = Immutable =⇒ p ∈ {immutable, readable})∧ (r(v) = Field(o, f) =⇒ p = isolated)∧ (r(v) = Root( ) =⇒ p = readable ∧ r(o) = Root( ))

CompleteRegionInfo(s, h, t, r, π) = ∀o, f. h(o, f) defined =⇒

t(o) defined ∧ r(o) defined ∧(∀ρ. r(o) = Root(ρ) =⇒ π(ρ) defined)

∧(∀o, r(o) defined =⇒ ∃f, h(o, f) defined)

Figure 10. Definition of Well-Regioned

ronment. Figure 11 defines the type environment denota-tion JΓKπ . Isolated and immutable denotations are mostlystraightforward, though they rely on a notion of partial sep-arating conjunction ∗ of instrumented states. To define thisseparation, we must first define composition of instrumentedstates •:

• = (•⇀, •∪, •∪, •∪, •π)

where

s ∈ (s1 •⇀ s2)def= dom(s1) ∩ dom(s2) = ∅ ∧ s = s1 ∪ s2

x ∈ (x1 •∪ x2)def= x = x1 ∪ x2

π ∈ (π1 •π π2)def= ∀ρ. π(ρ) = π1(ρ)(+,+)π2(ρ)

Partial separating conjunction then simply requires the exis-tence of two states that compose:

m ∈ P ∗Q def= ∃m′.∃m′′.m′ ∈ P ∧m′′ ∈ Q∧m ∈ m′ •m′′

This partial separation makes denotation of immutable orisolated references mostly independent of other state. For ex-ample, an isolated reference in the environment must bethe only reference to some root region, and it must be pos-sible to split that full permission away from the state de-scribed by the rest of the environment without invalidingother parts of the context. We cannot define the meaning ofreadable and writable individually, because we need anexternally visible bound on the regions involved in denot-ing a readable or writable reference when proving con-versions (T-RECOVISO and T-RECOVIMM) sound. We givethe meaning of a typing context with respect to some local

Jx : isolated T K =m ∈M m.π(m.r(m.s(x))) = (1, 1)

∧m.t(m.s(x)) = T ′∧ ` T ′ ≺ T∧RootClosed(m.r(m.s(x)),m)∨m.s(x) = null

Jx : immutable T K =

m ∈M m.r(m.s(x)) = Immutable∧m.t(m.s(x)) = T ′∧ ` T ′ ≺ T∨m.s(x) = null

Jx : readable T Kπ =

m ∈M m.s(x) = null∨((∃ρ.Up(π(ρ)) > 0 ∧ Up(m.π(ρ)) > 0∧m.r(m.s(x)) = Root ρ)

∨m.r(m.s(x)) = Immutable)∧m.t(m.s(x)) = T ′∧ ` T ′ ≺ T

Jx : writable T Kπ =

m ∈M m.s(x) = null∨(∃ρ. π(ρ) = (1, ) ∧m.π(ρ) = (1, )∧m.r(m.s(x)) = Root ρ)∧m.t(m.s(x)) = T ′∧ ` T ′ ≺ T

JΓ, x : isolated T Kπ = Jx : isolated T K ∗ JΓKπJΓ, x : immutable T Kπ = Jx : immutable T K ∗ JΓKπJΓ, x : readable T Kπ = Jx : readable T Kπ ∩ JΓKπJΓ, x : writable T Kπ = Jx : writable T Kπ ∩ JΓKπJεKπ =

m ∈M m.π ≥ π∧ (∀ρ ∈ dom(π).Up(π(ρ)) > 0)∧ ∀o, f, o′.m.r(o) ∈ dom(π) ∧m.h(o, f) = o′

=⇒

m.r(o′) ∈ dom(π) ∨m.r(o′) = Immutable ∨m.r(o′) = Field(o, f)

where RootClosed(ρ,m)

def= ∀o, f, o′.m.r(o) = Root(ρ) ∧m.h(o, f) = o′ =⇒

(m.r(o′) = m.r(o) ∨m.r(o′) = Immutable ∨m.r(o′) = Field(o, f))

.

Figure 11. Denoting types and type environments.

permission map π, which the denotations of readable andwritable references refer to, in addition to checking per-missions in the concrete state. Because this π bounds the setof regions supporting an environment, when π contains onlyfull permissions we can prove that certain region-changingoperations will not interfere with other threads. It also en-ables proving parallel composition is race free, as our proofof safe composition gives full update permission on a sharedregion to neither thread, meaning neither thread may denotea writable reference to a shared object (as in T-PAR).

Section 4.1 briefly describes specifics of how we inter-act with an existing program logic [15] to prove sound-ness. Even without reading Section 4.1, the actual sound-ness proof in Section 4.2 should be understandable enoughto build an intuition for soundness with only intuition for∗. The proofs are based around a relation v, which can beviewed as saying what changes to the π and r components of

Page 10: Uniqueness and Reference Immutability for Safe Parallelism

instrumented states are allowed, such that other threads canpreserve their view of the typing of the state.

4.1 Views FrameworkOur soundness proof builds upon a version of the ViewsFramework [15], which is in some sense a generalizationof the ideas behind separation logic and rely-guarantee rea-soning. The definitions we gave in the previous section, S,M and •, happen to coincide with definitions required bythis framework. Given a few operations and relations overM, the framework gives a natural structure to the soundnessproof as an embedding of type derivations into the view’sprogram logic. To do this we must define:

• An operation • : M → M → M that is commutativeand associative.• A preorder interference relation R ⊆ M × M that

defines permissible interference on an instrumented state.The relation must distribute over composition:

∀m1,m2,m. (m1 •m2)Rm =⇒∃m′1,m′2.m1Rm′1 ∧m2Rm′2 ∧m ∈ m′1 •m′2

• A (left and right) unit to • that is closed wrt to R (in ourcase, an instrumented state where all the components areempty maps).• A denotation of static assertions (in our case, types) in

terms of instrumented states: JΓKπ as in Figure 11.

Soundness follows from proving that the denotation of atyping derivation (JΓ ` C a ΓK) respects some lifting of theoperational semantics to instrumented states, by embeddingthe typing derivations into a program logic. The advantageof choosing this approach over a more traditional techniquelike syntactic type soundness is that after proving a fewlemmas about how type environment denotations behavewith respect to composition and interference, a number oftypically distinct concepts (including forking and joiningthreads, frame rules, and safety of environment reordering)become straightforward applications of simpler lemmas.

We define the interference permitted by a single action ofanother thread (heap modifications) R0 in Figure 4.1. Theinterference relation for individual actions allows relativelylittle to change. The stack and region permissions must re-main constant. The types and region map must remain con-stant, aside from objects initially in Field regions disappear-ing (as in another view performing a destructive read), newobjects appearing in any region the current view has no up-date permission for, and moving objects between root re-gions with no update permission (intuitively, another viewmerging two root region contents). The heap has similar re-strictions, though it additionally permits field changes in rootregions with 0 update permissions. Note that WellTyped andWellRegioned constrain the domains of the region and typemaps, and the object-only projection of the heap domain, to

(s, h, t, r, π)R0(s′, h′, t′, r′, π′)def=

s = s′ ∧ π = π′

∧(∀o, f. h(o, f) 6= h′(o, f) =⇒ π(r(o)) = (0, ))

let O = dom(t) inlet O′ = dom(t′) in(∀o. o ∈ O ∩O′ =⇒

(r(o) 6= r′(o) =⇒π(r(o)) = (0, ) ∧ π(r′(o)) = (0, )))

∧ t(o) = t′(o)∧ (∀o. o ∈ O \O′ =⇒ r(o) = Field( , ))∧ (∀o. o ∈ O′ \O ∧ r′(o) ∈ dom(π′)

=⇒ π′(r′(o)) = (0, ))

Figure 12. The thread interference relationR0.

all be equal. So if an object appears in the region map, it ap-pears in the type map and heap as well, and so on. We definethe final interference relation R as the reflexive transitiveclosure ofR0.

Given the specific definitions of composition and inter-ference, the Views Framework defines a number of usefulconcepts to help structure and simplify the soundness proof.First, it defines a view as the subset of instrumented statesthat are stable under interference:

Viewdef= {M ∈ P(M) | R(M) ⊆M}

The program logic is proven sound with respect to views [15],and our denotation of type environments is a valid view (sta-ble with respect to R). The framework also describes a use-ful concept called the view shift operator v, that describes away to reinterpret a set of instrumented states as a new set ofinstrumented states with the same erasures to S, accountingfor any requirement of other views. It requires that:

p v q def⇐⇒ ∀m ∈M. bp ∗ {m}c ⊆ bq ∗ R({m})c

This specifies how the region information can be changedsoundly. That is, we can only change the region informationsuch that all other possible threads can maintain compatibleviews. This corresponds to precisely what subtyping mustsatisfy in a concurrent setting and underlies the majority ofencoding the complex typing rules into the Views Frame-work.

4.2 Soundness ProofAs mentioned earlier, soundness of a type system in theViews Framework proceeds by embedding the types’ deno-tation into a sound program logic [15]. The logic itself con-tains judgments of the form {p}C{q} for views p and q andcommands C, and the logic’s soundness criteria, subject toour definitions of composition, interference, etc. satisfyingthe required properties, is

Page 11: Uniqueness and Reference Immutability for Safe Parallelism

Theorem 1 (Views Logic Soundness [15]). If {p}C{q} isderived in the logic, then for all s ∈ bpc, and s ∈ S, if(C, s) −→∗ (skip, s′) then s′ ∈ bqc.

Thus because our definitions of S,M, • andR satisfy therequired properties, if every type derivation denotes a validderivation in the Views Framework’s logic, then the typesystem is sound. We can define the denotation of a typingjudgment as:

JΓ1 ` C a Γ2Kdef=

∀π. (∃ρ. π(ρ) = (1, ))⇒ {JΓ1Kπ}C{JΓ2Kπ}

We map each judgement onto a collection of judgements inthe Views Framework. This allows us to encode the rules forrecovery, as the logic does not directly support them. Specif-ically, closing over π allows us to prove that permissions arepreserved. Thus, if a block of code is encoded with a set ofinitially full permissions, it will finish with full permissions,allowing conversion back to isolated if necessary.

We always require there to be at least one local regionthat is writable: (∃ρ. π(ρ) = (1, )). This is required toprove soundness for the subtyping rule, to allow us to castisolated to writable.

Here we describe the major lemmas supporting thesoundness proof, and omit natural but uninteresting lemmas,such as proving that the denotation of a type environment isstable under interference. We also omit methods here. Whennot otherwise discussed, lemmas are typically proven by in-duction on a type environment Γ. More details are availablein our extended technical report [20]. To prove soundnessfor method calls, we extended the Views Framework withsupport for method calls. The semantics are mostly intuitive(reducing a call statement to an inlined method body withfreshly bound locals), and both the semantics and proof ex-tensions are described in detail in our technical report [20].

The most important lemmas are those for recovering iso-lated or immutable references, which prove the soundness ofthe type rules T-RECOVISO and T-RECOVIMM:

Lemma 1 (Recovering Isolation).

IsoOrImm(Γ) =⇒ FullPermsOnly(π) =⇒JΓ, x : writableKπ v JΓ, x : isolatedK∅

Lemma 2 (Recovering Immutability).

IsoOrImm(Γ) =⇒ FullPermsOnly(π) =⇒JΓ, x : readableKπ v JΓ, x : immutableK∅

Both Lemmas 1 and 2 rely on the fact that readable andwritable references into root regions refer only to regionsin π. Without that restriction, and the fact that the denotationof type environments separates isolated and immutable

references from regions in π, recovering isolation or im-mutability would not be possible. Another important factorfor these lemmas is our slight weakening of external unique-ness, to allow references out of an aggregate into immutable

data; without this, recovering isolation would not be possiblewith immutable references in Γ.

Environment subtyping is given by the following lemma.

Lemma 3 (Subtyping Denotation).

(∃ρ. π(ρ) = (1, )) ∧ ` Γ1 ≺ Γ2 =⇒ JΓ1Kπ v JΓ2Kπ

The lemmas for framing (and unframing) parts of thetype environment require defining a weakened type deno-tation JΓKωπ , described in detail in our expanded technicalreport [20]. This denotation is mostly the same as the reg-ular denotation but requires only a non-zero update per-mission in π, with 0 update permission in the state, butchecking reference permission against a π matching the “un-framed” state. This makes the environment unusable for ex-ecuting commands but retaining enough information to re-store the environment later. We also use a transformationfunction on π to frame out a reference permission, prevent-ing the recovery rules from being applied in cases where areadable or writable reference to some region is framedout: frame out perm (u, r) := (u, r/2).

Lemma 4 (Type Framing).

JΓ,Γ′Kπ v JΓKω(map frame out perm π)∗JΓ′K(map frame out perm π)

Lemma 5 (Type Unframing).

JΓKω(map frame out perm π)∗JΓ′K(map frame out perm π) v JΓ,Γ′Kπ

Lemma 6 (Symmetric Decomposition).

NoWrit(Γ) =⇒JΓ,Γ′Kπ v JΓK(map halve perm π) ∗ JΓ′K(map halve perm π)

Lemma 7 (Join). JΓKπ ∗ JΓ′Kπ′ v JΓ,Γ′Kπ•π′

Lemma 8 (Asymmetric Decomposition).

IsoOrImm(Γ) =⇒ JΓ,Γ′Kπ v JΓK∅ ∗ JΓ′Kπ

Theorem 2 (Type Soundness).

Γ1 ` C a Γ2 =⇒ JΓ1 ` C a Γ2K

Proof. By induction on the derivation Γ1 ` C a Γ2.

5. PolymorphismAny practical application of this sort of system naturally re-quires support for polymorphism over type qualifiers. Oth-erwise code must be duplicated, for example, for each pos-sible permission of a collection and each possible permis-sion for the objects contained within a collection. Of course,polymorphism over unique and non-unique references withmutable state still lacks a clean solution due to the presenceof destructive reads (using a destructively-reading collectionfor non-unique elements would significantly alter semantics,though in the pure setting some clever techniques exist [26]).

Page 12: Uniqueness and Reference Immutability for Safe Parallelism

To that end we also develop a variant of the system withboth type and method polymorphism, over class types andpermissions. As in C#, we allow a sort of dependent kind-ing for type parameters, allowing type and permission pa-rameters to bound each other (without creating a cycle ofbounding). We separate permission parameters from classparameters for simplicity and expressivity. A source levelvariant may wish to take a single sort of parameter that quan-tifies over a permission-qualified type as in IGJ [39]. Thereis a straightforward embedding from those constraints to themore primitive constraints we use, and our separation makesour language suitable as an intermediate language that canbe targeted by variants of a source language that may changeover time.

Figure 13 gives the grammar extensions for the polymor-phic system. Our language permits bounding a polymorphicpermission by any other permission, including other permis-sion parameters, and by concrete permissions that produceparameters with only a single valid instantiation (such asimmutable). This allows for future extensions, for exampledistinguishing multiple immutable data sources. We add acontext ∆ containing type bounds to the typing and subtyp-ing judgements. We extend the previous typing and subtyp-ing rules in the obvious way. ∆ is invariant for the durationof checking a piece of code, and the main soundness the-orem, restated below, relies on an executing program hav-ing an empty ∆ (a program instantiates all type and permis-sion parameters to concrete classes and permissions). Con-cretely, type judgements and subtyping judgements now takethe forms:

∆ | Γ ` C a Γ ∆ ` t ≺ t

Figure 14 gives auxiliary judgements and metafunctionsused in the polymorphic system. Most of the extensions arestraightforward, leveraging bounds in ∆ to type check inter-actions with generic permissions and class types. We discussa couple of the most interesting rules here, presenting the fulltype system in our technical report [20].

One of the most interesting rules is the field read:

t′ f ∈ T p 6= isolated ∨ t′ = immutable

t′ 6= isolated ∨ p = immutable

∆ | x : , y : p T ` x = y.f a y : p T, x : pB∆ t′

It uses a variant of permission combining parameterized by∆, given in Figure 14, and lifted to types as before. Whenreading the definition of B∆, bear in mind that no permis-sion variable may ever be instantiated to isolated. The fi-nal case of B∆ produces a deferred permission combination(p p). The two cases previous to it that combine uninstan-tiated permission parameters leverage the bounding relationin ∆ to give a sound answer that might produce writable

or immutable results that can be used locally (though inthe case that P is instantiated to immutable, this can losesome precision compared to instantiated uses). In the unre-lated case, there is always an answer to give: readable. But

class cn〈X〉〈P 〉 where . . . {field t f field method} ∈ Pt[P/p,X/T ] f ∈ cn〈T 〉〈p〉

class cn〈X〉〈P 〉 where . . . {field method} ∈ P m ∈ methodm = t′ m〈Y 〉〈Q〉(u′ z′) p′ where Y <: V ,Q <: q . . .

m[P/p,X/T ] ∈ cn〈T 〉〈p〉

IsoOrImm∆(Γ)def= ∀(x : p T ) ∈ Γ. ∆ ` p ≺ immutable

B∆ : Permission→ Permission→ PermissionimmutableB∆ = immutable

B∆ immutable = immutable

readableB∆ writable = readable

readableB∆ readable = readable

writableB∆ readable = readable

writableB∆ writable = writable

readableB∆ Q = readable

P B∆ readable = readable

writableB∆ Q = QP B∆ writable = P

P B∆ Q =

Q ∆ ` P ≺ QP ∆ ` Q ≺ PP Q otherwise

Figure 14. Auxiliary judgements and metafunctions for thepolymorphic system.

this is too imprecise for uses such as container classes. Thereis always a more precise answer to give, but it cannot beknown until all parameters are instantiated. To this end, wealso change the type and permission substitution to reducep q to p B∆ q if p and q are both concrete permissionsafter substitution. Note that these deferred permissions areeffectively equivalent to readable in terms of what actionsgeneric code using them may perform. This deferred combi-nation plays a pivotal role in supporting highly polymorphiccollection classes, as Section 6.3 describes.

We also support covariant subtyping on readable andimmutable references, as in IGJ [39].

∆ ` p c〈T i−1, Ti, T

m−i〉〈p〉 ∆ ` p c〈T i−1, T ′i , T

m−i〉〈p〉p = readable ∨ p = immutable ∆ ` Ti ≺ T ′i

∆ ` p c〈T i−1, Ti, T

m−i〉〈p〉 ≺ p c〈T i−1, T ′i , T

m−i〉〈p〉

There is another rule for safe covariant permission subtypingas well.

Soundness for Generics At a high level, the soundnessproof for the polymorphic system is similar to the monomor-phic system, because we only need to embed fully-instantiatedprograms (the top level program expression is type checkedwith an empty type bound context). The definition for typemaps in the concrete machine states and views are redefinedto have a range of only fully-instantiated types, making typeenvironment denotations defined only over fully-instantiatedtypes.

Page 13: Uniqueness and Reference Immutability for Safe Parallelism

W,X, Y, Z type variablesP,Q,R permission variablesT,U, V ::= cn〈T 〉〈p〉 | X

∣∣∣∣∣∣∣∣TD ::= class cn〈X〉〈P 〉 [<: T2] whereX <: T , P <: p {field meth }meth ::= t m〈X〉〈P 〉(t1 x1, ..., tn xn) p whereX <: T , P <: p { C; return x ; }p, q ::= . . . | P | p p∆ ::= ε | ∆, X <: T | ∆, P <: p

Figure 13. Grammar extensions for the polymorphic system.

Several auxiliary lemmas are required such as that sub-stituting any valid permission or type instantiations into ageneric derivation yields a consistent derivation. Addition-ally, the denotation of writable and isolated referencesmust use a strengthened subtyping bound on their referents,to ensure they are viewed at a type that does not changeany field types (thus preventing the classic reference sub-typing problem while allowing covariant subtyping of read-only references). More details are given in our technical re-port [20].

6. EvaluationA source-level variant of this system, as an extension toC#, is in use by a large project at Microsoft, as their pri-mary programming language. The group has written sev-eral million lines of code, including: core libraries (includ-ing collections with polymorphism over element permis-sions and data-parallel operations when safe), a webserver,a high level optimizing compiler, and an MPEG decoder.These and other applications written in the source languageare performance-competitive with established implementa-tions on standard benchmarks; we mention this not becauseour language design is focused on performance, but merelyto point out that heavy use of reference immutability, includ-ing removing mutable static/global state, has not come at thecost of performance in the experience of the Microsoft team.In fact, the prototype compiler exploits reference immutabil-ity information for a number of otherwise-unavailable com-piler optimizations.

6.1 Differences from Formal SystemThe implementation differs from the formal system de-scribed earlier in a number of ways, mostly small. The mostimportant difference is that the implementation supportsproper task parallelism, with a first-class (unstructured) join.Task parallelism is supported via library calls that acceptisolated delegates (closures, which must therefore captureonly isolated or immutable state, in correspondence with T-ASYNC) and return isolated promises, thus interactingnicely with recovery and framing, since the asynchronoustask’s mutable memory is disjoint from the main compu-tation’s. async blocks are not currently checked accordingto T-ASYNC, mostly because we restrict async block taskexecution to single-threaded cooperative behavior, multi-

plexing async block tasks on a single CLR thread2, whichalready reduces concurrency errors from its use, so the teamhas not yet decided to undertake the maintenance task ofturning on such checking. This permits some uncheckedconcurrency, but single-threaded (avoiding at least memorymodel issues) and with only explicit points of interference(an await expression basically acts as a yield statement; es-sentially cooperative concurrency). The team plans to even-tually enable checking of async blocks as well. T-PAR is notused for asynchronous tasks because it is unsound: recovery(T-RECOVISO and T-RECOVIMM) is not valid if a sharedreadable reference to mutable data can live arbitrarily longafter the “recovery block” in an asynchronous task. ThusT-PAR is used only for applicable static constructs such asparallel for loops.

There are also a few source-level conveniences added ascompared to the system here. The most notable is immutableclasses. Immutable classes are simply classes whose con-structors are required to have a final type environment withthis : immutable rather than isolated. This allows theconstructor to internally treat the self pointer as writableor isolated, before the type system conceptually uses T-RECOVIMM. Thus, writable and readable constructorarguments are permitted, they simply cannot be stored di-rectly into the object. The disadvantage of this is that it isnot possible, without unsafe casts, to create a cycle of ob-jects of immutable classes (cycles of immutable objects ingeneral remain possible as in Section 2.3).

The source variation also includes an unstrict block,where permission checking is disabled. The eventual goalis for this to be used only in trusted code (whose .NET as-semblies are marked as such), for optimizations like lazy ini-tialization of immutable data when an accessor is called; thecore libraries offer a set of standard abstractions to encap-sulate these unsafe actions (Section 6.6). Finally, the sourcelanguage uses only a single type parameter list, where eachargument may be instantiated with a single permission orfull permission-qualified type.

C# also permits compound expressions, and expres-sions with side-effects, which our core language disallows.consume is an expression in the source language, whichperforms a destructive read on its l-value argument and re-turns the result. This makes using isolated method arguments

2 In C#’s implementation, async blocks may run on other threads [3], butthe team decided prior to adding reference immutability that such behaviorwas too error prone.

Page 14: Uniqueness and Reference Immutability for Safe Parallelism

more convenient than in our core language, which allowsstatement consumption of fields, but treats isolated variablesas affine resources when passed to methods.

A common focus for safe data-parallelism systems is han-dling of arrays. The implementation currently does not sup-port arrays directly, but via trusted library abstractions forsafe data parallelism. We are currently designing a notion ofa sub-array, using a combination of isolation and immutabil-ity to allow safe array partitioning for in-place updates, aswell as functional data-parallelism. This part of the design isstill evolving.

6.2 Differences from C#Beyond adding the obvious immutability-related extensionsand restricting async block task execution to a single-threaded model (augmented with a true task parallelism li-brary) as already discussed, the only additional differencefrom C# is that all static (global) state must be immutable.This is necessary for safe parallelism and for the recoveryrules to avoid capturing shared mutable state. This restric-tion does lead to some different coding patterns, and requiredintroducing several internally-unsafe but externally-safe li-brary abstractions for things like global caches, which wewill discuss shortly.

6.3 Source Level ExamplesGeneric Collections Collection libraries are a standardbenchmark for any form of generics. The source variant ofour system includes a full collections library, including sup-port for polymorphism over permissions of the collectionitself and elements of the collection. An illustrative exam-ple is the following simplified collections interface (using alifting of our notation to a source language with interfaces,retaining our separation of permission and class parameters):public interface ICollection<Elem><PElem> {

public void add(PElem Elem e) writable;public writable Enumerator<Elem><P,PElem> getEnumerator() P;

}public interface IEnumerator<Elem><PColl,PElem> {

public bool hasNext() readable;public PColl PElem Elem getNext() writable;

}

This collection interface is parameterized over a class typefor elements and a permission for the elements (which maynever be instantiated to isolated). The add() method isnatural, but the interesting case is getEnumerator(). Thismethod returns a writable enumerator, but the enumera-tor manages two permissions: the permission with whichgetEnumerator() is called (which governs the permissionthe enumerator will hold on the collection) and the permis-sion the collection has for the elements.

These separate permissions come into play in the typeof the enumerator’s getNext() method, which uses de-ferred permission composition (p p, Section 5) to returnelements with as precise a permission as possible. Simplyspecifying a single permission for the elements returnedrequires either specifying a different enumerator variant

public class LinkedList<Elem><PElem>implements ICollection<Elem><PElem> {protected writable Node<Elem><PElem> head;protected class Node<Elem><PElem> {

public PElem Elem item;public writable Node<Elem><PElem> next;

}protected class LLEnum<E><PColl,PE>implements IEnumerator<E><PColl,PE> {private PColl Node<Elem><PE> next;public LLEnum(PColl LinkedList<E><PE> coll) {

next = coll.head;}public bool hasNext() readable { return next == null; }public PColl PElem E getNext() writable {

if (next != null) {PColl PElem E nextElem = next.item;next = next.next;return nextElem;

}return null;

}}public LinkedList() { head = null; }public void add(PElem Elem e) writable {

writable Node<Elem><PElem> n = new Node<Elem><PElem>();n.item = e;n.next = head;head = n;

}public writable Enumerator<Elem><P,PElem> getEnumerator() P {

return new LLEnum<Elem><P,PElem>(this);}

}

Figure 15. A simplified collection with a polymorphic enu-merator.

for every possible permission on the collection, or los-ing precision. For example, given a writable collectionof immutable elements, it is reasonable to expect an it-erator to return elements with an immutable permission.This is straightforward with a getEnumerator() variantspecific to writable collections, but difficult using poly-morphism for code reuse. Returning (using the enumera-tor definition’s parameters) PElem elements is in generalnot possible with a generic PColl permission on the col-lection because we cannot predict at the method defini-tion site the result of combining the two permissions whenthe enumerator accesses the collection; it would be soundfor a writable collection of immutable objects, but notfor an immutable collection of writable objects sinceimmutableB∆ writable = immutable, not writable.It also preserves precision, as any element from enumeratingan immutable collection of readable references shouldideally return immutable elements rather than the soundbut less precise readable.

Consider a linked list as in Figure 15. The heart of the it-erator’s flexibility is in the type checking of the first assign-ment in LLEnum.getNext(). There the code has a PColl

permissioned reference next to a linked list node that con-tains a PElem permissioned reference field item to an ele-ment. Thus the result type of next.item is PColl PElem

PE by T-FIELDREAD and B∆. When the linked list type isinstantiated, and getEnumerator() is called with a certain

Page 15: Uniqueness and Reference Immutability for Safe Parallelism

permission, the enumerator type becomes fully instantiatedand the deferred combination is reduced to a concrete per-mission. For example:writable LinkedList<IntBox><writable> ll =

new LinkedList<IntBox><writable>();writable IEnumerator<IntBox><writable,writable> e =

ll.getEnumerator(); // P instantiated as writablewritable IntBox b = e.getNext();

writable LinkedList<IntBox><readable> llr =new LinkedList<IntBox><readable>();

writable IEnumerator<IntBox><writable,readable> e =llr.getEnumerator(); // P instantiated as readable

writable IntBox b = e.getNext(); // Type Error!// e.getNext() returns readable, since w r=rreadable IntBox b = e.getNext(); // OK

A slightly richer variant of this enumerator design underliesthe prototype’s foreach construct, and is used widely in theMicrosoft team’s code.

Data Parallelism Reference immutability gives our lan-guage the ability to offer unified specializations of datastructures for safe concurrency patterns. Other systems,such as the collections libraries for C# or Scala separateconcurrency-safe (immutable) collections from mutable col-lections by separate (but related) trees in the class hierarchy.3

A fully polymorphic version of a map() method for a col-lection can coexist with a parallelized version pmap() spe-cialized for immutable or readable collections. Considerthe types and extension methods [34] (intuitively similar tomixins on .NET/CLR, though the differences are non-trivial)in Figure 16, adding parallel map to a LinkedList class fora singly-linked list (assuming the list object itself acts as alist node for this example). Each maps a function4 across thelist, but if the function requires only readable permissionto its arguments, pmap may be used to do so in parallel. Notethat the parallelized version can still be used with writable

collections through subtyping and framing as long as themapped operation is pure; no duplication or creation of anadditional collection just for concurrency is needed. Withthe eventual addition of static method overloading by per-missions (as in Javari [37]), these methods could share thesame name, and the compiler could automatically select theparallelized version whenever possible.

6.4 OptimizationsReference immutability enables some new optimizations inthe compiler and runtime system. For example, the concur-rent GC can use weaker read barriers for immutable data.The compiler can perform more code motion and caching,and an MSIL-to-native pass can freeze immutable data intothe binary.

3 C# and Scala have practical reasons for this beyond simply being unableto check safety of parallelism: they lack the temporary immutability of oursystem due to the presence of unstructured parallelism.4 Func1 is the intermediate-language encoding of a higher-order procedure.C# has proper types for these, called delegate types [34], each compiledto an abstract class with an invoke method with the appropriate arity andtypes. We restrict our examples to the underlying object representation forclarity.

public abstract class Func1<In,Out><Pin,Pout,Prun> {public abstract Pout Out invoke(Pin In in) Prun;

}public static class LinkedListExtensions {// A parallel mappublic static readable LinkedList<readable><X>pmap<X>(this readable LinkedList<readable><X>,immutable Func1<X,X><readable,readable,readable> fun)

readable {readable LinkedList<readable><X> rest = null;isolated LinkedList<readable><X> head = null;

head = if (list.next != null)new LinkedList<readable><X>; rest =

head.elem = fun(list.elem); list.next.map<X>(fun);

head.next = rest;return head;

}// A polymorphic mappublic static writable LinkedList<PL PE><X>map<X><PE>(this PL LinkedList<PE><X> list,immutable Func1<X,X><PL PE,PL PE,readable> fun) PL {writable LinkedList<PL PE><X> result =

new LinkedList<PL PE><X>;result.elem = fun(list.elem);writable LinkedList<PL PE><X> newCurr = result;PL LinkedList<PE><X> oldCurr = list;while (oldCurr.next != null) {newCurr.next = new LinkedList<PL PE><X>;newCurr = newCurr.next;oldCurr = oldCurr.next;newCurr.elem = fun(oldCurr.elem);

}return result;

}}

Figure 16. Extension methods to add regular and parallelmap to a linked list.

A common concern with destructive reads is the addi-tional memory writes a naıve implementation (such as ours)might incur. These have not been an issue for us: many nullwrites are overwritten before flushing from the cache; thecompiler’s MSIL is later processed by one of two optimizingcompilers (.NET JIT or an ahead-of-time MSIL-to-nativecompiler) that often optimize away shadowed null writes;and in many cases the manual treatment of uniqueness wouldstill require storing null.

6.5 Evolving a Type SystemThis type system grew naturally from a series of effortsat safe parallelism. The initial plans included no new lan-guage features, only compiler plugins, and language exten-sions were added over time for better support. The earli-est version was simply copying Spec#’s [Pure] methodattribute [1], along with a set of carefully designed task-and data-parallelism libraries. To handle rough edges withthis approach and ease checking, readable and immutablewere added, followed by library abstractions for isolatedand immutable. After some time using unstrict blocks toimplement those abstractions, we gradually saw a way tointegrate them into the type system. With all four permis-sions, the team was much more eager to use reference im-

Page 16: Uniqueness and Reference Immutability for Safe Parallelism

mutability. After seeing some benefit, users eagerly addedreadable and immutable permissions.

Generics were the most difficult part of the design, butmany iterations on the design of generic collections pro-duced the design shown here. The one aspect we still strug-gle with is the occasional need for shallow permissions, suchas for a collection with immutable membership, but mutableelements. This is the source of some unstrict blocks.

The entire design process was guided by user feedbackabout what was difficult. Picking the right defaults had alarge impact on the users’ happiness and willingness to usethe language: writable is the default annotation, so anysingle-threaded C# that does not access global state alsocompiles with the prototype. This also made converting ex-isting code much easier.

The system remains a work in progress. We initiallyfeared the loss of traditional threads could be a great weak-ness, but the team placed slightly higher value on correctconcurrency than easy concurrency, leading to the designpoint illustrated here. To recover some flexibility, we arecurrently adding actor concurrency, using isolated andimmutable permissions for safe message passing [22, 24].

We continue to work on driving the number of unstrictblocks as low as possible without over-complicating the typesystem’s use or implementation. Part of this includes codify-ing additional patterns that currently require unstrict use. Weunderstand how to implement some of these (such as per-mission conversion for stateless objects like empty arrays)safely within the type system, but the engineering trade-offshave not yet been judged worth the effort to implement.

6.6 User ExperienceOverall, the Microsoft team has been satisfied with the addi-tional safety they gain from not only the general software en-gineering advantages of reference immutability [37, 39, 40]but particularly the safe parallelism.

Anecdotally, they claim that the further they push refer-ence immutability through their code base, the more bugsthey find from spurious mutations. The main classes of bugsfound are cases where a developer provided an object in-tended for read-only access, but a callee incorrectly mu-tated it; accidental mutations of structures that should beimmutable; and data races where data should have been im-mutable or thread local (i.e. isolated, and one thread keptand used a stale reference).

Annotation burden has been low. There is roughly 1annotation (permission or consume) per 63 lines of code.These are roughly 55% readable, 16.8% consume, 16.5%immutable, 4.7% writable, 4.1% isolated, and 2.8%generic permissions. This is partly due to the writable

default, as well as C#’s local type inference (e.g. var x

= ...;). Thus most annotations appear in method signa-tures. Note that because users added additional qualifiers forstricter behavior checking, this is not the minimal annotationburden to type check, but reflects heavy use of the system.

The type system does make some common tasks diffi-cult. We were initially concerned that immutable-only globalstate would be too restrictive, but has been mitigated by fea-tures of the platform the Microsoft team develops on top of.The platform includes pervasive support for capability-basedresource access for resources such as files. Global cachesare treated as capabilities, which must be passed explicitythrough the source (essentially writable references). This re-quires some careful design, but has not been onerous. Mak-ing the caches global per process adds some plumbing effort,but allows better unified resource management.

Another point of initial concern was whether isolationwould be too restrictive. In practice it also adds some designwork, but our borrowing / recovery features avoid viral lin-earity annotations, so it has not been troublesome. It has alsorevealed subtle aliasing and concurrency bugs, and it enablesmany affine reference design patterns, such as checking lin-ear hand-off in pipeline designs.

The standard library also provides trusted internally-unstrict abstractions for common idioms that would other-wise require wider use of unstrict blocks. Examples includelazy initialization and general memoization for otherwiseimmutable data structures, caches, and diagnostic logging.There are relatively few unstrict blocks, of varying sizes(a count does not give an accurate estimate of uncheckedcode). Most of these are in safe (trusted) standard libraryabstractions and interactions with the runtime system (GC,allocator, etc., which are already not memory-safe). Over thecourse of development, unstrict blocks have also been usefulfor the Microsoft team to make forward progress even whilerelying on effectively nightly builds of the compiler. Theyhave been used to temporarily work around unimplementedfeatures or compiler bugs, with such blocks being marked,and removed once the compiler is updated.

The Microsoft team was surprisingly receptive to usingexplicit destructive reads, as opposed to richer flow-sensitiveanalyses [8, 28] (which also have non-trivial interaction withexceptions). They value the simplicity and predictability ofdestructive reads, and like that it makes the transfer of uniquereferences explicit and easy to find. In general, the team pre-ferred explicit source representation for type system interac-tions (e.g. consume, permission conversion).

The team has also naturally developed their own de-sign patterns for working in this environment. One of themost popular is informally called the “builder pattern” (as inbuilding a collection) to create frozen collections:

isolated List<Foo> list = new List<Foo>();

foreach (var cur in someOtherCollection) {

isolated Foo f = new Foo();

f.Name = cur.Name;

// etc ...

list.Add(consume f);

}

immutable List<Foo> immList = consume list;

Page 17: Uniqueness and Reference Immutability for Safe Parallelism

This pattern can be further abstracted for elements with adeep clone method returning an isolated reference.

Nearly all (non-async) concurrency in the system ischecked. The unchecked concurrency is mostly due to de-velopment priorities (e.g. feature development has been pri-oritized over converting the remaining code); removing allunchecked concurrency from the system remains an explicitgoal for the team. Nonetheless, the safe concurrency fea-tures described here have handled most of the team’s needs.Natural partitioning of tasks, such as in the H.264 and JPEGdecoders (both verified for safe concurrency) is “surprisinglycommon,” and well-supported by these abstractions. Some-times breaking an algorithm into Map-Reduce-style phaseshelps fit problems into these abstractions. The main difficul-ties using the model come in two forms. The first form iswhere partitioning is dynamic rather than structural. This isdifficult to express efficiently; we are working on a frame-work to compute a partitioning blueprint dynamically. Sec-ond, sometimes communication among tasks is not requiredfor correctness, but offers substantial performance benefits:for example, in a parallelized search algorithm, broadcast-ing the best-known result thus far can help all threads prunethe search space. Currently unstrict code is used for a fewinstances of this, which motivates current work to add actorsto the language [22, 24].

7. Related WorkReference immutability [37, 39, 40] is a family of work char-acterized by the ability to make an object graph effectivelyimmutable to a region of code by passing read-only refer-ences to objects that may be mutated later, where the read-only effect of a reference applies transitively to all referencesobtained through a read-only reference. Common extensionsinclude support for class immutability (classes where all in-stances are permanently immutable after allocation) and ob-ject immutability (making an individual instance of a classpermanently immutable). Surprisingly, despite safe paral-lelism being cited as a natural application of reference im-mutability, we are the first to formalize such a use.

Immutability Generic Java (IGJ) [39] is the most simi-lar reference immutability work to ours, though it does notaddress concurrency. IGJ uses Java generics support to em-bed reference immutability into Java syntax (it still requiresa custom compiler to handle permission subtyping). Thusreference permissions are specified by special classes as thefirst type parameter of a generic type. IGJ’s support of ob-ject immutability is also based on the permission passed toa constructor, rather than conversion, so object immutabil-ity is enforced at allocation, and may not be deferred as ourT-RECOVIMM rule allows. This means that creating a cy-cle of immutable objects requires a self-passing constructoridiom, where a constructor for cyclic immutable data struc-tures must pass its this pointer into another constructor callas Immutable. Haack and Poll relax this restriction by lexi-

cally scoping the modification lifetime of an immutable in-stance [21].

Ownership [6, 7, 11, 13] and Universe [14] types describea notion of some objects “owning” others as a method ofstructuring heap references and mutation. The “owner-as-modifier” interpretation of ownership resembles referenceimmutability: any modification to an object o must be donethrough a reference to o’s owner. These systems still per-mit references across ownership domain, but such refer-ences (any references in Universe types) are deeply read-only. Universe types specify a “viewpoint adaptation” re-lation used for adapting type declarations to their use in agiven context, which directly inspired our permission adap-tation relation. Leino, Muller, and Wallenburg [25] boostthe owner-as-modifier restriction to object immutability byadding a freeze operation that transfers ownership of an ob-ject to a hidden owner unavailable to the program source.Since the owner cannot be mentioned, no modifications maybe made to frozen objects. In general, ownership transfer, (asin Leino et al.’s system or UTT [27]) relies on uniquenessand treats ownership domains as regions to merge.

The most similar treatment of polymorphism over muta-bility-related qualifiers to our work is Generic UniverseTypes (GUT). GUT provides polymorphism over permis-sion-qualified types as in our prototype source language,rather than separating qualifiers and class types as our corelanguage does. GUT’s viewpoint adaptation (roughly equiv-alent to our permission combining relation B∆) deals im-mediately with concrete qualifier combinations, and pre-serves some precision when combining a concrete own-ership modifier with a generic one. But when combiningtwo generic ownership modifiers, the result is always any,roughly equivalent to readable in our system. In prac-tice, this is sufficient precision for GUT, because passinga generic type across ownership domains typically convertsto any anyways. Our use cases require additional precision,which we retain by using deferred permission combination(p p) to postpone the combination until all type parame-ters are instantiated. Without this, the generic enumerator inSection 6.3 could only return readable elements, even fora writable collection of immutable elements. IGJ [39]does not discuss this type of permission combining, but ap-pears to have a similar loss of precision: i.e. accessing a fieldwith generic permission through a reference with genericpermission always yields a readable reference. OIGJ [40]can express generic iterators, but mostly because referenceimmutability in OIGJ is not transitive: a read-only iteratorover a mutable list is permitted to mutate the list, and an it-erator over an immutable list of mutable elements can returnmutable references to those elements.

Ownership type systems have been used to achieve datarace freedom [6, 7, 13], essentially by using the owner-ship hierarchy to associate data with locks and beyond thatenforcing data race freedom in the standard way [17–19].

Page 18: Uniqueness and Reference Immutability for Safe Parallelism

Clarke et al. [12] use ownership to preserve thread locality,but allow immutable instances and “safe” references (whichpermit access only to immutable, e.g. Java-style final, por-tions of the referent) to be shared freely across threads, andadd transfer of externally unique references.

Ostlund et al. [30] present an ownership type and effectsystem for a language Joe3 with the explicit aim of sup-porting reference immutability idioms by embedding intoan ownership type system. Owner polymorphic methods de-clare the effects they may have on each ownership domain,treating ownership domains as regions [35, 36]. Joe3 usesa similar technique to ours for delayed initialization of im-mutable instances, as it has (externally) unique references,and writing a unique reference to an immutable variableor field converts the externally unique cluster into a clusterof immutable objects. While Joe3 has blocks for borrow-ing unique references, our T-RECOVIMM rule is more gen-eral, combining borrowing and conversion. Their borrowingmechanism also creates local owners to preserve encapsula-tion, requiring explicit ownership transfer to merge aggre-gates. Our type system also permits invoking some meth-ods directly on unique references (as opposed to the re-quired source-level borrowing in Joe3) because our framerule makes it easy to prove the invocation preserves unique-ness with the additional argument restrictions (Figure 5).

Our T-RECOVISO rule is in some ways a simplification ofexisting techniques for borrowing unique references, giventhe presence of reference immutability qualifiers. The clos-est work on borrowing to ours in terms of simplicity andexpressiveness is Haller and Odersky’s work [23] using ca-pabilities that guard access to regions containing externally-unique clusters. Regions of code that return an input capabil-ity have only borrowed from a region, and have not violatedits external uniqueness. Local variable types ρ B τ are ref-erences to an object of type τ in region ρ. When a referenceto some object in an externally unique aggregate is writtento a heap location, that aggregate’s capability is consumedin a flow-sensitive fashion, and all local variables guardedby that capability become inaccessible. Our recovery rulerequires no invalidation, though its use may require environ-ment weakening. We believe the expressiveness of the twoapproaches to be equal for code without method calls. Formethod calls, Haller and Odersky track borrowing of indi-vidual arguments by what permissions are returned. Our sys-tem would require returning multiple isolated referencesthrough the heap, though our recovery rules would allow in-ferring some method returns as isolated in the proper con-texts, without cooperation of the method called. We also addsome flexibility by allowing arbitrary paths to immutablestate reachable from an externally-unique aggregate.

Another interesting variant of borrowing is adoption [10,16], where one piece of state is logically embedded into an-other piece, which provides a way to manage unique refer-ences without destructive reads.

Boyland proposed fractional permissions [9] to reasonstatically about interference among threads in a languagewith fork-join concurrency. We use a twist on fractional per-missions in the denotation of our types, including to denoteuniqueness, though the fractions do not appear in the sourceprogram or type system. The Plaid language uses fractionalpermissions to manage aliasing and updates to objects whenchecking typestate [2]. Recent work by Naden et al. to sim-plify Plaid [28], like our system, does not require any frac-tions or explicit permission terms to appear in source code,though unlike us an implementation of their type systemmust reason about fractional permissions (our fractional per-missions appear only in our meta-proofs). Like us they usetype qualifiers to specify access permissions to each object,though their permissions do not apply transitively (a distinc-tion driven largely by our differing motivations). Naden’slanguage supports externally-unique and immutable refer-ences, and more fine-grained control than our system overhow permissions for each argument to a method are changed(e.g. preserving uniqueness as an indication of borrowing),though his language does not address concurrency.

Deterministic Parallel Java (DPJ) [4] uses effect typingand nested regions [35, 36] to enable data parallelism anddeterministic execution of parallel programs. An expressionmay have a read or write effect on each of some numberof regions, and expressions with non-conflicting effects (onethread with write effect and none with read effects, or multi-ple threads with read effects and no write effects, on eachregion) may safely be parallelized. This ensures not onlythe absence of data races, but determinism as well (later re-visions add controlled nondeterminism [5]). Our system issimilar in spirit, but requires no mention of regions in thesource, only mention of the permissions required for eachobject a method accesses, not where they reside. This means,for example, that region polymorphism is implicit in our sys-tem; methods need not bind to a specific number of regions,while DPJ requires methods and classes to declare fixednumbers of regions over which they operate (it is possible toinstantiate multiple region arguments with the same region).The upside to the explicit treatment of regions in DPJ is thatnon-interfering writes may be parallelized without requiringany sort of reference uniqueness (the type system must stillbe able to prove two regions are distinct). DPJ also treatsdata parallelism over arrays, whereas we do not.

Westbrook et al. [38] describe Habanero Java with Per-missions (HJp), a language with parallelism structure be-tween our formal and source languages: async(e) begins anasynchronous task and returns unit; finish(e) waits for alltasks spawned within e, rather than allowing joins with indi-vidual tasks. They use qualifiers to distinguish thread-localread, thread-local write, and thread-shared read access to ob-jects (the latter is mutually exclusive with the first two). Theymust distinguish between thread local and shared read-onlyaccess because they cannot guarantee the inaccessibility of

Page 19: Uniqueness and Reference Immutability for Safe Parallelism

writable references to objects for the duration of an async;doing so would require a flow-sensitive set of variables in-accessible until the enclosing finish() because the end ofan async is not statically scoped, and async blocks maycapture any shareable state, not only unique or immutablestate. Their treatment of storing (totally) unique referencesin unique fields and embedding the referent’s permissionsis more flexible for concurrency than our isolated fields.Their embedding allows natural read-only access to uniquefield referents if the containing object is shared read-only,while isolated fields of shared readable objects are in-accessible until recovery or conversion. Reads from non-unique fields in HJp have no static permissions; dereferenc-ing such fields requires dynamically acquiring permissions.We treat permission polymorphism, while they do not.

8. ConclusionsWe have used reference immutability to ensure safe (inter-ference-free) parallelism, in part by combining reference im-mutability with external uniqueness. Applying our approachto an intermediate-level language led us to derive recoveryrules for recovering isolation or immutability in certain con-texts, which offers a natural approach to borrowing for lan-guages with reference immutability. Our type system modelsa reference immutability system in active use in industry, andwe have described their experiences with it.

AcknowledgmentsThanks to Dan Grossman, Brian Burg, and the anonymousreviewers for helpful comments, and to Michael Ernst forhelpful conversations about related work.

The first author’s design work occurred during a Mi-crosoft internship; proof work was supported by NSF grantsCCF-1016701 and CNS-0855252.

References[1] M. Barnett, M. Fahndrich, K. R. M. Leino, P. Muller,

W. Schulte, and H. Venter. Specification and Verification: TheSpec# Experience. Commun. ACM, 54(6):81–91, June 2011.

[2] K. Bierhoff and J. Aldrich. Modular Typestate Checking ofAliased Objects. In OOPSLA, 2007.

[3] G. Bierman, C. Russo, G. Mainland, E. Meijer, and M. Torg-ersen. Pause n Play: Formalizing Asynchronous C]. InECOOP, 2012.

[4] R. L. Bocchino, Jr., V. S. Adve, D. Dig, S. V. Adve,S. Heumann, R. Komuravelli, J. Overbey, P. Simmons,H. Sung, and M. Vakilian. A Type and Effect System for De-terministic Parallel Java. In OOPSLA, 2009.

[5] R. L. Bocchino, Jr., S. Heumann, N. Honarmand, S. V. Adve,V. S. Adve, A. Welc, and T. Shpeisman. Safe Nondeterminismin a Deterministic-by-default Parallel Language. In POPL,2011.

[6] C. Boyapati and M. Rinard. A Parameterized Type System forRace-Free Java Programs. In OOPSLA, 2001.

[7] C. Boyapati, R. Lee, and M. Rinard. Ownership Types forSafe Programming: Preventing Data Races and Deadlocks. InOOPSLA, 2002.

[8] J. Boyland. Alias Burying: Unique Variables without Destruc-tive Reads. Software Practice & Experience, 31(6), 2001.

[9] J. Boyland. Checking Interference with Fractional Permis-sions. In SAS, 2003.

[10] J. T. Boyland and W. Retert. Connecting Effects and Unique-ness with Adoption. In POPL, 2005.

[11] D. Clarke, S. Drossopoulou, and J. Noble. Aliasing, Confine-ment, and Ownership in Object-Oriented Programming. InECOOP 2003 Workshop Reader, 2004.

[12] D. Clarke, T. Wrigstad, J. Ostlund, and E. Johnsen. MinimalOwnership for Active Objects. In APLAS, 2008.

[13] D. Cunningham, S. Drossopoulou, and S. Eisenbach. UniverseTypes for Race Safety. In VAMP, 2007.

[14] W. Dietl, S. Drossopoulou, and P. Muller. Generic UniverseTypes. In ECOOP, 2007.

[15] T. Dinsdale-Young, L. Birkedal, P. Gardner, M. Parkinson, andH. Yang. Views: Compositional Reasoning for ConcurrentPrograms. Technical report, 2012. URL https://sites.

google.com/site/viewsmodel/.

[16] M. Fahndrich and R. DeLine. Adoption and Focus: PracticalLinear Types for Imperative Programming. In PLDI, 2002.

[17] C. Flanagan and M. Abadi. Object Types against Races. InCONCUR, 1999.

[18] C. Flanagan and M. Abadi. Types for Safe Locking. In ESOP,1999.

[19] C. Flanagan and S. N. Freund. Type-Based Race Detectionfor Java. In PLDI, 2000.

[20] C. S. Gordon, M. J. Parkinson, J. Parsons, A. Bromfield, andJ. Duffy. Uniqueness and Reference Immutability for SafeParallelism (Extended Version). Technical Report MSR-TR-2012-79, 2012.

[21] C. Haack and E. Poll. Type-Based Object Immutability withFlexible Initialization. In ECOOP, 2009.

[22] P. Haller. Isolated Actors for Race-Free Concurrent Program-ming. PhD thesis, EPFL, Lausanne, Switzerland, 2010.

[23] P. Haller and M. Odersky. Capabilities for Uniqueness andBorrowing. In ECOOP, 2010.

[24] C. Hewitt, P. Bishop, I. Greif, B. Smith, T. Matson, andR. Steiger. Actor Induction and Meta-Evaluation. In POPL,1973.

[25] K. Leino, P. Muller, and A. Wallenburg. Flexible Immutabilitywith Frozen Objects. In VSTTE, 2008.

[26] K. Mazurak, J. Zhao, and S. Zdancewic. Lightweight LinearTypes in System F◦. In TLDI, 2010.

[27] P. Muller and A. Rudich. Ownership Transfer in UniverseTypes. In OOPSLA, 2007.

[28] K. Naden, R. Bocchino, J. Aldrich, and K. Bierhoff. A TypeSystem for Borrowing Permissions. In POPL, 2012.

[29] P. O’Hearn, J. Reynolds, and H. Yang. Local Reasoning aboutPrograms that Alter Data Structures. In Computer ScienceLogic, 2001.

Page 20: Uniqueness and Reference Immutability for Safe Parallelism

[30] J. Ostlund, T. Wrigstad, D. Clarke, and B. Akerblom. Owner-ship, Uniqueness, and Immutability. In Objects, Components,Models and Patterns, 2008.

[31] M. Parkinson, R. Bornat, and C. Calcagno. Variables asResource in Hoare Logics. In LICS, 2006.

[32] U. S. Reddy and J. C. Reynolds. Syntactic Control of Inter-ference for Separation Logic. In POPL, 2012.

[33] J. Reynolds. Separation Logic: A Logic for Shared MutableData Structures. In LICS, 2002.

[34] J. Richter. CLR Via C], Second Edition. Microsoft Press,2006. ISBN 0735621632.

[35] J.-P. Talpin and P. Jouvelot. Polymorphic Type, Region, andEffect Inference. JFP, 2(2), 1992.

[36] M. Tofte and J.-P. Talpin. Implementation of the Typed Call-by-Value λ-calculus Using a Stack of Regions. In POPL,1994.

[37] M. S. Tschantz and M. D. Ernst. Javari: Adding ReferenceImmutability to Java. In OOPSLA, 2005.

[38] E. Westbrook, J. Zhao, Z. Budimli, and V. Sarkar. PracticalPermissions for Race-Free Parallelism. In ECOOP, 2012.

[39] Y. Zibin, A. Potanin, M. Ali, S. Artzi, A. Kiezun, and M. D.Ernst. Object and Reference Immutability Using Java Gener-ics. In ESEC-FSE, 2007.

[40] Y. Zibin, A. Potanin, P. Li, M. Ali, and M. D. Ernst. Owner-ship and Immutability in Generic Java. In OOPSLA, 2010.