Lectures onType Theory
Chapter 49
Chapter 49Optional

Mutable Value Semantics and inout Access

Prerequisites. Direct starred prerequisites: none. No later core chapter depends on this route.

Let p,q:s, where the structure s has two mutable integer fields. Value assignment requires q:=p;q.x:=7p.x is unchanged. Temporary mutable access imposes a different obligation: swapX(&p,&p.x) must be rejected because its two arguments expose overlapping parts of one representation. Copy independence proves the first claim; an access-conflict check must prove the second.

Trees, paths, and pointer stores

The calculus in this chapter is Swiftlet [RSZ^+22]. A structure context Δ maps structure names to qualified field declarations, and a term context Γ maps names to qualified types. Qualifiers are m{let,var}. The fragment needed below is p::=τinoutτ,τ::=Z[τ]sAny(p1,,pk)τ,r::=xe.fe1[e2],a::=e&r. A Swiftlet expression e is an integer, array or structure literal, a name or path, a function declaration or call, a conditional, a binding, an assignment, a sequence, or a cast. The load-bearing forms are displayed by e::=cx[e1,,ek]s(e1,,ek)e(a1,,ak)mx:τ=e1 in e2r=ee1;e2e as τ. A runtime value in this fragment is v::=c[1,,k][1,,k]sλ(x:p,η,e)box(). For a well-typed stored value, typeofΔ,π(v) denotes its unique source type. A boxed value has source type Any; its referenced stored value retains the concrete type used by a checked downcast. A path is an lvalue description, not a storable reference. The array-element qualifier is inherited from the path: an element reached through a let expression is immutable, while an element reached through a mutable path has qualifier var.

A small-step state contains a pointer store π and a nonempty stack η=η1,,ηn, with η1 the innermost, hence currently accessible, frame: π:Loc{let,var}×Val,ηi:NameLoc. Arrays and structures store the locations of their immediate parts.

Definition 49.1 — Accessible representation

For a scalar root, acc(π,)={}. If the value at contains child locations 1,,k, then acc(π,)={}i=1kacc(π,i). Roots are independent when their accessible representations are pairwise disjoint.

Swiftlet excludes recursive structure declarations, so these representations are finite trees. The lvalue judgment and its five load-bearing rules are Δ;Γpathr:mτ, Γ(x)=mτΔ;Γpathx:mτTPathName

Δ;Γe:sΔ(s)(f)=mτ
Δ;Γpathe.f:letτ
T-LetPropRef
Δ;Γpathr:varsΔ(s)(f)=varτ
Δ;Γpathr.f:varτ
T-VarPropRef
Δ;Γe1:[τ]Δ;Γe2:Z
Δ;Γpathe1[e2]:letτ
T-LetElemRef
Δ;Γpathr:var[τ]Δ;Γe:Z
Δ;Γpathr[e]:varτ
T-VarElemRef

The source keeps immutable expression roots and mutable path roots separate; in particular, an immutable field cannot become a mutable access merely because its enclosing structure is mutable.

Lvalue evaluation, written Δπ;η;rlvπ;η;r, starts with

η1(x)=π()=mv
Δπ;η;xlvπ;η;m
PSS-Name
Δπ;η;rlvπ;η;r
Δπ;η;r.flvπ;η;r.f
PSS-Struct
m=min(m,mi)π()=m[1,,k]sΔ(s)(fi)=miτi
Δπ;η;m.filvπ;η;im
PSS-Prop
π()=m[1,,k]0c<k
Δπ;η;m[c]lvπ;η;c+1m
PSS-Elem

The omitted PSS-Array and PSS-Index congruence rules first reduce the array path and its index. The meet m=min(m,mi) in PSS-Prop is the dynamic reason that an immutable field remains immutable below a mutable root.

Lemma 49.2 — Unique descent

If the roots of η1 are independent, evaluation of a well-typed path from x selects a subtree of acc(π,η1(x)). Paths beginning at distinct roots select disjoint subtrees.

Proof of Lemma 49.2 — Unique descent

Proof. Induct on the path. PSS-Name selects the root. A field or element step follows one child of the current tree, so the selected accessible set is a subset of its predecessor’s. Subsets of disjoint root representations remain disjoint. ◻

Swiftlet’s helper copy(π,v)=(π,v) recursively allocates fresh locations for compound values, closures, and boxed Any values.

Proposition 49.3 — Copy produces an independent value

If v is well typed in π and copy(π,v)=(π,v), then v has the type of v, and every new representation location of v lies outside the accessible set of every old frame root. Successive copies may be chosen mutually fresh.

Proof of Proposition 49.3 — Copy produces an independent value

Proof. Type preservation is the source’s Lemma A.2. For independence, induct on the definition of copy. A compound case chooses fresh child locations and invokes the induction hypothesis once for each child. The finite union in definition 49.1 is disjoint from the old roots and from the unions produced by earlier copies. ◻

The source states type preservation as Lemma A.2 and the corresponding memory preservation fact as Lemma A.3. Mutual freshness of successive copies is the additional invariant proved above.

Exercise 49.1

★☆☆ Let p contain two scalars and execute q:=p;q.x:=7. Draw both accessible sets and identify the disjointness fact a shallow interior-pointer copy would violate.

The source relation and a sound repair

Racordon et al.’s Definition 4.1 writes &r&r. Despite the suggestive glyph, its fourth rule makes it an access-relatedness relation, not literal syntactic prefix. It is the least reflexive, transitive relation closed by &r&r&r&r.fSrcField&r&r&r&r[e]SrcIndex and ¬const(e)¬const(e)&r[e]&r[e]SrcUnknownIndex Here const(e) holds exactly when e is an integer literal, as in the paper’s deliberately conservative definition. Thus &a[0] and &a[1] may be separated, whereas &a[i]&a[j] when either index is unknown.

The published relation does not close related paths on the left. Consequently it relates &a[i] to &a[j] but need not relate &a[i].x to &a[j].x. If i and j evaluate to the same index, those nested paths alias. The two-sentence proof of source Lemma A.1 does not resolve this case. We therefore do not use that lemma at its unrestricted published signature.

Definition 49.4 — Book conflict relation

Write a path as a root followed by a component list u::=ϵfu[c]u[?]u. The last form stands for a nonliteral index. Define the total Boolean comparisons stable(ϵ)=true,stable(fu)=stable(u),stable([c]u)=stable(u),stable([?]u)=false, apart(ϵ,u)=false,apart(u,ϵ)=false,apart(fu,gv)={apart(u,v),f=g,stable(u)stable(v),fg,apart([c]u,[d]v)={apart(u,v),c=d,stable(u)stable(v),cd,apart(u,v)=falseotherwise. For access arguments a=&x.u and a=&y.v, put aBa¬(stable(u)stable(v)(xyapart(u,v))). Thus every accepted path has literal array indices, and separation has a first pair of distinct tree children. This strengthening prevents evaluation of a later index expression from mutating the store after an earlier access has already selected its location. The relation is symmetric and decidable by structural recursion on the two component lists.

This conservative repair deliberately rejects (&a[i].x,&a[i].y): the unknown array choice is settled before the distinct fields are reached. It also rejects unknown indices below an already distinct root or component. It is stronger than Definition 4.1 and is not attributed to the source.

Definition 49.5 — Book exclusivity premise

For the inout indices Iref of a call, the book system requires ijIref¬(aiBaj).

Lemma 49.6 — Static separation implies physical disjointness

Suppose π;η is well formed and two well-typed access arguments satisfy definition 49.5. Evaluate both paths from this same state; their literal indices take no expression steps. If the evaluations select i and j, then acc(π,i)acc(π,j)=.

Proof of Lemma 49.6 — Static separation implies physical disjointness

Proof. Because both component lists are stable, path evaluation performs only PSS-Name, congruence, PSS-Prop, and PSS-Elem; it leaves the store unchanged. Different roots are handled by lemma 49.2. On a common root, induction over the equal prefix shows that both evaluations have selected the same location after each pair of equal fields or equal literal indices. The first unequal fields or literal indices therefore select distinct children of that one tree. Their subtrees are disjoint, and further stable descent preserves disjointness. Every case without such a witness is classified as a conflict. ◻

For the opening call, &pB&p.x, because the first path ends before the second. By contrast, distinct fields &p.x and &p.y are separated at their first unequal component.

Exercise 49.2

★☆☆ Classify the following pairs under definition 49.4; then classify them under the source relation and identify the pair on which the two checks differ: (&p.x,&p.y),(&p,&p.x),(&a[0],&a[1]),(&a[i].x,&a[i].y).

Calling by temporary access

An access argument must denote a mutable path: Δ;Γpathr:varτΔ;Γarg&r:inoutτTInout Assignment has the same qualifier boundary: Δ;Γpathr:varτΔ;Γe:τΔ;Γr=e:unitTAssign For example, if Δ(s)(x)=varZ and Γ(o)=vars, then Δ;Γpatho.x:varZΔ;Γarg&o.x:inoutZTInout

Let a closure have parameters xi:pi, captured frame ηλ, and body e. The call step copies every ordinary value into a mutually fresh root i, maps every access parameter directly to the location selected by &rilvi, and checks pairwise physical disjointness. Its new innermost frame extends ηλ. The generated form e;popL records exactly the fresh copied roots L. Put Icpy={ipi=τi},Iref={ipi=inoutτi}. The static and dynamic call rules expose the two corresponding premises: Δ;Γe:(p1,,pk)τ(Δ;Γargai:pi)1ikexclusiveB(a1,,ak)Δ;Γe(a1,,ak):τTCall Here exclusiveB is exactly definition 49.5, including stability of every access path. After evaluation has produced a closure λ(x:p,ηλ,e), ordinary values vi, and access locations i, the root call step is {iiIcpy}dom(π)=ijIref.acc(π,i)acc(π,j)=π=π[iletviiIcpy]η=ηλ[xiiiIcpyIref]Δπ;η;λ(x:p,ηλ,e)(v)π;η,η;e;pop{iiIcpy}ESSCall In the conservative variant, the stable static premise lets lemma 49.6 establish the dynamic disjointness premise in the single final store: reducing access arguments performs no index-expression steps. On return, π0;η,η;v;popLdrop(π0,L);η;v, where drop recursively removes the representations rooted at L. Borrowed caller locations are absent from L. Assignment reaches a root step only through a mutable lvalue: π()=mv0π0=drop(π,v0)π=π0[varv]Δπ;η;var=v;eπ;η;eESSAssign Successful checked downcast exposes the concrete stored value and copies it: τAnyπ()=letvtypeofΔ,π(v)=τcopy(π,v)=(π,v)Δπ;η;box() as τ;eπ;η;v;eESSDowncast Two and only two dynamic side-condition failures are designated runtime errors: a downcast box() as τ, where π()=letv but typeofΔ,π(v)τ, and an array access at a literal c with c<0 or ck. They are the failed premises of ESS-Downcast and PSS-Elem, respectively. No other stuck state is included in the error alternative below.

Example 49.7 — Two nested-path calls

Let bump:(inoutZ)unit and bump2:(inoutZ,inoutZ)unit. If o.p.x and o.p.y are mutable fields, then olvo,o.plvp,o.p.xlvx. Hence bump(&o.p.x) binds its parameter to x. The two binary cases are bump2(&o.p.x,&o.p)reject: one path ends,bump2(&o.p.x,&o.p.y)accept: last fields differ.

Exercise 49.3

★★☆ Give the complete PSS-Name, PSS-Struct, and PSS-Prop trace for &o.p.x. Derive T-Inout, and explain why simultaneous &o.q.x is separated when pq.

No rule turns &r into an ordinary returnable value. This is temporary, second-class access, not a calculus of first-class borrows.

Independence and type safety

Definition 49.8 — Well-formed and well-typed memory

A state π;η is well formed when η is nonempty and distinct bindings in its currently accessible frame η1 have disjoint accessible representations. It is well typed, written Δπ;η:Γ, when it is well formed and, for every frame ηj in η and every contextual binding mx:τ, π(ηj(x))=mv for a value v of type τ, whenever ηj(x) is defined.

Expression reduction is Δπ;η;eπ;η;e. It includes evaluation contexts, allocation, copy, path evaluation, call, assignment, cast, and pop.

Theorem 49.9 — Safety of the conservative Swiftlet variant

Replace the published call side condition by definition 49.5. If Δ;Γ;πe:τ and Δπ;η:Γ, then:

  1. e is a value, takes a step, or is stuck at exactly one of the two runtime errors defined above: a failed downcast or an out-of-bounds access;

  2. if the state steps, some Γ types the reduct and its memory at the same result type.

Proof of Theorem 49.9 — Safety of the conservative Swiftlet variant

Proof. Follow the source inductions for Lemmas 4.1–4.2, with one repaired case. In a call, lemma 49.6 proves the dynamic disjointness premise. By proposition 49.3, the copied roots are fresh from one another and from all borrowed roots. Assignment uses T-Assign, PSS-Prop, and ESS-Assign, so it can update only a location reached with qualifier var. Pop invokes drop only on copied roots. The remaining nine source syntax families—bindings, reads, literals, functions, conditionals, sequences, casts, evaluation contexts, and pop—are the finite case analysis printed in the proofs of Lemmas 4.1–4.2. They do not use the repaired call premise; their induction hypotheses reconstruct the corresponding rule and memory-typing judgment. ◻

Corollary 49.10 — Static guarantee

Every reachable non-error state of a closed program in the conservative variant is well formed and well typed. No step assigns through an immutable path, and no call exposes overlapping inout representations.

Proof of Corollary 49.10 — Static guarantee

Proof. Induct on the reduction prefix. Preservation maintains memory well-typedness. Inversion of T-Assign gives a mutable path, PSS-Prop propagates the meet of root and field qualifiers, and ESS-Assign requires the final var; together these prove that no immutable cell is overwritten. Inversion of ESS-Call gives pairwise disjointness for every exposed inout representation. ◻

The source states corresponding progress and preservation results as Lemmas 4.1–4.2 and packages them as Theorem 4.1. Because its access relation has the nested-index gap isolated above, this chapter does not claim that the published proof establishes the unrestricted theorem.

Representation correspondence and boundaries

Swiftlet also has a value-tree semantics in which abstract copying is the identity. A readback |v|Δπ expands a pointer-store value.

Theorem 49.11 — Representation correspondence, source sketch

For an expression in the natural-semantics fragment, if small-step execution from the initial state reaches π;η;v, then natural evaluation reaches some μ;v0 with |v|Δπ=v0.

Proof of Theorem 49.11 — Representation correspondence, source sketch

Imported proof. The JOT article labels this Theorem 4.2 and gives an induction-on-run-length proof sketch. It does not verify LLVM lowering, stack allocation, copy-on-write, reference counting, or any particular compiler revision. The exact source is Racordon et al.’s Theorem 4.2, with the natural and small-step semantics of its Sections 4.1–4.5 [RSZ^+22]. ◻

Current Swift access enforcement and Hylo yielding projections are comparison boundaries, not metatheory for Swiftlet. A yielding projection temporarily opens a component of a value to a caller without turning that component into an independently owned result. The pinned Swift design archive documents exclusive-access and yielding-access proposals [Swi17]; Hylo’s richer checker is described by Racordon and Abrahams [RA23]. Racordon’s dissertation gives background on assignment semantics, but owns none of the Swiftlet results [Rac19].

Sources.

Sections 4.1–4.5, Definitions 4.1–4.2, Lemmas 4.1–4.2, Theorems 4.1–4.2, and Appendix A of Racordon et al. own the published calculus and proof claims [RSZ^+22]. The conservative conflict relation and its disjointness proof are book-owned repairs.

Suggested first pass.

Start with exercise 49.4, exercise 49.5; finish with the finite auditor.

Exercise 49.4

★★☆ For one ordinary array argument and two separated inout integers, construct the complete callee frame, including a captured frame assumed well formed before the call. State which roots are copied, borrowed, and removed by pop, and prove frame independence.

Exercise 49.5

★★☆ Give a well-typed out-of-bounds access. Explain why its stuck state is allowed by theorem 49.9, while an overlapping access call has no typing derivation.

Exercise 49.6

★★☆ State theorem 49.11. Name two compiler transformations it does not verify and the simulation obligation each needs.

Exercise 49.7

★★★ Practical project.swiftlet-access-auditor Implement definition 49.4 in Kappa. The invariant is: acceptance has found a first component pair that denotes distinct tree children; reaching the end of either path or any unknown index before that witness means conflict. Use the chapter’s four pairs as named oracles, add a different-root pair with unknown indices, and compute the decisive comparison for every rejection. Mutate the stability test to accept a nonliteral index; the mutant must check and audit cleanly but fail the different-root negative oracle.

Search the book

Type to search the local edition.