on permitted mutation – Includes immutability • Part of language design – Primitives exploit mutation control • Examples – Reference immutability, typestate, regions & effects, …
can it be updated? • What can it be updated to? How can it change? • Which aliases / modules / threads can update? Key problem: Answers to these questions are entirely implicit in most languages with mutation.
constrain? – Who/What/When/Where • How do we specify constraints? • How precise are constraints? • How much specification effort is required? • Level of guarantees: – Non-interference vs. invariants vs. functional correctness
– “Ordinary” code “just works” • Local checks for rich properties • Single toolset for sequential & concurrent – Avoid extra concurrency-only concepts • Closely match mental models
parallelism • Statically enforce data-race freedom • Safe task & data parallelism –No locks –No mutable statics/globals • Real: millions of LOC –Experience report later
o Cannot get writable reference through a readable o x:readable ⊢ x.f : t ⇒ t is never writable • immutable: permanently immutable • As in Tschantz et al. ‘05, Zibin et al. ‘07, Zibin et al. ‘10
deeply read-only o Cannot get writable reference through a readable – x:readable ⊢ x.f : t ⇒ t is never writable • immutable: permanently immutable • isolated: externally-unique reference
in active use at Microsoft. • Some differences from formal system o e.g. first-class tasks, unstrict blocks • Millions of lines of code • Web server, MPEG decoder, … • Nearly all parallelism checked o Exceptions in runtime system • Anecdotally: More RI finds more bugs • Largest industrial use of such a system
practice • More on uniqueness & borrowing • Generics for permissions • Proof by embedding into a program logic – The Views Framework (POPL’13; See Matt Parkinson’s keynote on Thursday!)
Works (data race freedom!) • Multi-purpose – Even unsound checking helps: • Found data races even without parallelism checks – Single-threaded benefits: • Lightweight caller-callee contract • Found extra (expensive!) defensive copying
– Incremental refinement • Local interference checks – Safe concurrency from local type env. Properties – Local summaries of global behavior • T in readable T is irrelevant • Single model for sequential and concurrent – Single tool behaves the same in both cases – Concurrency as a modest extension • Concurrency primitives introduce no new concepts • Effective as a mental model
permissions to arbitrary relations • E.g. monotonically increasing counter – Generalize reference immutability – Express how state changes, not just whether • Exploit similarity between threads and aliases • Preserve data structure invariants
thread interference – Analyses for one can inspire analyses for the other • Actions through aliases can be seen as concurrent • Rely-Guarantee reasoning is good for threads – Summarizes possible interference – Good match for concurrent data structures
expected interference 2. Guarantee bounds thread actions 3. Stable assertions are preserved by interference 4. Compatible threads: each rely assumes at least the other’s guarantee
alias interference 2. Guarantee bounds actions through this alias 3. Stable predicates preserved by interference 4. Compatible aliases: if x == y, then x.G ⊆ y.R && y.G ⊆ x.R • Subsumes ML references! (OCaml, SML, etc.) – (Incremental)
– Does x := !x + 1 really increment? • But RGrefs subsume RI – Hijack ideas from RI for safe concurrency • General rely-guarantee works for concurrency – Restrict reasoning for exprs with ! – Exploit conversion: • (convert r) has same properties as r with weaker type
• RG is a good match for fine-grained structures: – All easy to specify as rely-guarantee references – O’Hearn et al. identify 18 invariants and step restrictions for lock-free set correctness [1] – Correctness for Michael-Scott Queue, Trieber stack has similar restrictions – Some similarity to Turon’s CaReSL [2] [1] PODC 2010 [2] POPL’13, ICFP’13
design space – Basic mutation ⊆ RI ⊆ … ⊆ RGref • Can build higher-level abstractions above it – E.g. deterministic execution: • Strict non-interference ⇒ determinism • Monotonicity is halfway to determinism
Modules also too coarse, but a good match for abstractions (CAP, Explicit Stabilization) • Individual objects too fine • “Reachability slices” (RI, RGref) seem better, but both too coarse and too fine • Regions? (DPJ, Local Rely-Guarantee) • Other granularities? Missing abstractions?
– RI, RGref, Ownership/Universe Types – specification matches point of mutation • Attached to method specifications – Sep. Logic, Chalice, others – Formalizes current best practices • Attached to regions – Local Rely-Guarantee, DPJ • Where else? Thread init/end?
memory concurrency – Single toolset with sequential & concurrent benefits – “Normal” code has a type in a more expressive system • Many questions remain – Granularity, integration, expressiveness, specification burden? – Non-interference / invariants / full correctness?