Type Soundness and Race Freedom for Mezzo

Similar documents
The theory of Mezzo. François Pottier. IHP, April 2014 INRIA

The practice of Mezzo

An overview of Mezzo

The Mezzo case. Jonathan Protzenko. François Pottier FSFMA'13. Why design a new programming language?

Jonathan Protzenko (INRIA)

Hoare logic. A proof system for separation logic. Introduction. Separation logic

Hiding local state in direct style: a higher-order anti-frame rule

Mutable References. Chapter 1

From IMP to Java. Andreas Lochbihler. parts based on work by Gerwin Klein and Tobias Nipkow ETH Zurich

Rely-Guarantee References for Refinement Types over Aliased Mutable Data

Hoare Logic and Model Checking

Mutation. COS 326 David Walker Princeton University

Hoare Logic and Model Checking. A proof system for Separation logic. Introduction. Separation Logic

Combining Programming with Theorem Proving

RustBelt: Securing the Foundations of the Rust Programming Language

Lecture 6: Sequential Sorting

Program logics for relaxed consistency

CS131 Typed Lambda Calculus Worksheet Due Thursday, April 19th

TYPE INFERENCE. François Pottier. The Programming Languages Mentoring ICFP August 30, 2015

Programming Languages Lecture 15: Recursive Types & Subtyping

Hoare Logic and Model Checking

Hoare logic. WHILE p, a language with pointers. Introduction. Syntax of WHILE p. Lecture 5: Introduction to separation logic

Rely-Guarantee Protocols for Safe Interference over Shared Memory

Hoare logic. Lecture 5: Introduction to separation logic. Jean Pichon-Pharabod University of Cambridge. CST Part II 2017/18

Chapter 13: Reference. Why reference Typing Evaluation Store Typings Safety Notes

Programming Languages Lecture 14: Sum, Product, Recursive Types

Verified Characteristic Formulae for CakeML. Armaël Guéneau, Magnus O. Myreen, Ramana Kumar, Michael Norrish April 27, 2017

CSE-321 Programming Languages 2010 Final

Static and User-Extensible Proof Checking. Antonis Stampoulis Zhong Shao Yale University POPL 2012

Witnessing Purity, Constancy and Mutability APLAS Ben Lippmeier Australian National University 2009/12/14

CMSC 336: Type Systems for Programming Languages Lecture 5: Simply Typed Lambda Calculus Acar & Ahmed January 24, 2008

An Operational and Axiomatic Semantics for Non-determinism and Sequence Points in C

Static Lock Capabilities for Deadlock-Freedom

Semantic Subtyping. Alain Frisch (ENS Paris) Giuseppe Castagna (ENS Paris) Véronique Benzaken (LRI U Paris Sud)

On the Expressiveness of Polyadicity in Higher-Order Process Calculi

Type Systems Winter Semester 2006

CSCI-GA Scripting Languages

Reminder of the last lecture. Aliasing Issues: Call by reference, Pointer programs. Introducing Aliasing Issues. Home Work from previous lecture

Chapter 13: Reference. Why reference Typing Evaluation Store Typings Safety Notes

Substructural Typestates

Ornaments in ML. Thomas Williams, Didier Rémy. April 18, Inria - Gallium

TRELLYS and Beyond: Type Systems for Advanced Functional Programming

Simple proofs of simple programs in Why3

Intersections and Unions of Session Types

Phantom Monitors: A Simple Foundation for Modular Proofs of Fine-Grained Concurrent Programs

Proving liveness. Alexey Gotsman IMDEA Software Institute

Part VI. Imperative Functional Programming

Sequentialising a concurrent program using continuation-passing style

Chapter 1. Introduction

Subtyping. Lecture 13 CS 565 3/27/06

Types for References, Exceptions and Continuations. Review of Subtyping. Γ e:τ τ <:σ Γ e:σ. Annoucements. How s the midterm going?

1 Introduction. 3 Syntax

The Rule of Constancy(Derived Frame Rule)

Meta-programming with Names and Necessity p.1

Why3 where programs meet provers

INCREMENTAL SOFTWARE CONSTRUCTION WITH REFINEMENT DIAGRAMS

Typed Assembly Language for Implementing OS Kernels in SMP/Multi-Core Environments with Interrupts

COS 320. Compiling Techniques

Concurrent library abstraction without information hiding

Abstract Interpretation Using Laziness: Proving Conway s Lost Cosmological Theorem

Modal Logic: Implications for Design of a Language for Distributed Computation p.1/53

Introduction to OCaml

Programming Languages

SCHOOL: a Small Chorded Object-Oriented Language

Precision and the Conjunction Rule in Concurrent Separation Logic

Eta-equivalence in Core Dependent Haskell

On the Logical Foundations of Staged Computation

CONVENTIONAL EXECUTABLE SEMANTICS. Grigore Rosu CS522 Programming Language Semantics

CSE-321 Programming Languages 2012 Midterm

Resource-bound process algebras for Schedulability and Performance Analysis of Real-Time and Embedded Systems

Modular Reasoning about Aliasing using Permissions

CSE-321 Programming Languages 2014 Final

Part III. Chapter 15: Subtyping

CONVENTIONAL EXECUTABLE SEMANTICS. Grigore Rosu CS422 Programming Language Semantics

Datatype declarations

Category Item Abstract Concrete

Concepts of programming languages

Tradeoffs. CSE 505: Programming Languages. Lecture 15 Subtyping. Where shall we add useful completeness? Where shall we add completeness?

Functional Programming Language Haskell

LECTURE 16. Functional Programming

ATS: a language to make typeful programming real and fun

Practical Affine Types and Typestate-Oriented Programming

CS 6110 S11 Lecture 25 Typed λ-calculus 6 April 2011

GADTs meet Subtyping

Lists. Michael P. Fourman. February 2, 2010

Logical Relations. Amal Ahmed. Northeastern University. OPLSS Lecture 5, June 26, 2015

Extracting the Range of cps from Affine Typing

Functional Languages. Hwansoo Han

Object-Oriented Languages and Object-Oriented Design. Ghezzi&Jazayeri: OO Languages 1

Assistant for Language Theory. SASyLF: An Educational Proof. Corporation. Microsoft. Key Shin. Workshop on Mechanizing Metatheory

Verifying Program Invariants with Refinement Types

Programming language design and analysis

Functional Programming. Pure Functional Programming

Typed Lambda Calculus and Exception Handling

Symmetry in Type Theory

Chapter 11 :: Functional Languages

Concurrency and Parallelism. CS152: Programming Languages. Lecture 19 Shared-Memory Parallelism and Concurrency. Concurrency vs. Parallelism.

CS152: Programming Languages. Lecture 19 Shared-Memory Parallelism and Concurrency. Dan Grossman Spring 2011

Program Calculus Calculational Programming

Functional programming in μscheme

Transcription:

Type Soundness and Race Freedom for Mezzo Thibaut Balabonski François Pottier Jonathan Protzenko INRIA FLOPS 2014 1 / 42

Mezzo in a few words Mezzo is a high-level programming language, equipped with: algebraic data types; first-class functions; garbage collection; mutable state; shared-memory concurrency Its static discipline is based on permissions 2 / 42

Permissions by example val r1 = newref () (* r1 @ ref () *) 3 / 42

Permissions by example val r1 = newref () (* r1 @ ref () *) val r2 = r1 (* r1 @ ref () * r2 @ =r1 *) 3 / 42

Permissions by example val r1 = newref () (* r1 @ ref () *) val r2 = r1 (* r1 @ ref () * r2 @ =r1 *) (* r1 @ ref () * r2 = r1 *) 3 / 42

Permissions by example val r1 = newref () (* r1 @ ref () *) val r2 = r1 (* r1 @ ref () * r2 @ =r1 *) (* r1 @ ref () * r2 = r1 *) val () = r1 := 0 (* r1 @ ref int * r2 = r1 *) 3 / 42

Permissions by example val r1 = newref () (* r1 @ ref () *) val r2 = r1 (* r1 @ ref () * r2 @ =r1 *) (* r1 @ ref () * r2 = r1 *) val () = r1 := 0 (* r1 @ ref int * r2 = r1 *) val x2 =!r2 + 1 (* r1 @ ref int * r2 = r1 * x2 @ int *) 3 / 42

Permissions by example val r1 = newref () (* r1 @ ref () *) val r2 = r1 (* r1 @ ref () * r2 @ =r1 *) (* r1 @ ref () * r2 = r1 *) val () = r1 := 0 (* r1 @ ref int * r2 = r1 *) val x2 =!r2 + 1 (* r1 @ ref int * r2 = r1 * x2 @ int *) val p = (r1, r2) (* r1 @ ref int * r2 = r1 * x2 @ int * p @ (=r1, =r2) *) 3 / 42

Permissions by example val r1 = newref () (* r1 @ ref () *) val r2 = r1 (* r1 @ ref () * r2 @ =r1 *) (* r1 @ ref () * r2 = r1 *) val () = r1 := 0 (* r1 @ ref int * r2 = r1 *) val x2 =!r2 + 1 (* r1 @ ref int * r2 = r1 * x2 @ int *) val p = (r1, r2) (* r1 @ ref int * r2 = r1 * x2 @ int * p @ (=r1, =r2) *) val () = assert p @ (ref int, ref int) (* REJECTED *) 3 / 42

Permissions by example val r1 = newref () (* r1 @ ref () *) val r2 = r1 (* r1 @ ref () * r2 @ =r1 *) (* r1 @ ref () * r2 = r1 *) val () = r1 := 0 (* r1 @ ref int * r2 = r1 *) val x2 =!r2 + 1 (* r1 @ ref int * r2 = r1 * x2 @ int *) val p = (r1, r2) (* r1 @ ref int * r2 = r1 * x2 @ int * p @ (=r1, =r2) *) val () = assert p @ (ref int, ref int) (* REJECTED *) val () = assert p @ (r: ref int, =r) (* ACCEPTED *) 3 / 42

Permissions by example val r1 = newref () (* r1 @ ref () *) val r2 = r1 (* r1 @ ref () * r2 @ =r1 *) (* r1 @ ref () * r2 = r1 *) val () = r1 := 0 (* r1 @ ref int * r2 = r1 *) val x2 =!r2 + 1 (* r1 @ ref int * r2 = r1 * x2 @ int *) val p = (r1, r2) (* r1 @ ref int * r2 = r1 * x2 @ int * p @ (=r1, =r2) *) val () = assert p @ (ref int, ref int) (* REJECTED *) val () = assert p @ (r: ref int, =r) (* ACCEPTED *) val () = assert p @ (=r, r: ref int) (* ACCEPTED *)

Permissions by example val r1 = newref () (* r1 @ ref () *) val r2 = r1 (* r1 @ ref () * r2 @ =r1 *) Why do this? (* r1 @ ref () * r2 = r1 *) val () = r1 := 0 (* r1 @ ref int * r2 = r1 *) val x2 =!r2 + 1 (* r1 @ ref int * r2 = r1 * x2 @ int *) val p = (r1, r2) (* r1 @ ref int * r2 = r1 * x2 @ int * p @ (=r1, =r2) *) val () = assert p @ (ref int, ref int) (* REJECTED *) val () = assert p @ (r: ref int, =r) (* ACCEPTED *) val () = assert p @ (=r, r: ref int) (* ACCEPTED *)

Permissions by example val r1 = newref () (* r1 @ ref () *) val r2 = r1 For fun and (* r1 @ ref () * r2 @ =r1 *) profit, of course! Why do this? (* r1 @ ref () * r2 = r1 *) val () = r1 := 0 (* r1 @ ref int * r2 = r1 *) val x2 =!r2 + 1 (* r1 @ ref int * r2 = r1 * x2 @ int *) val p = (r1, r2) (* r1 @ ref int * r2 = r1 * x2 @ int * p @ (=r1, =r2) *) val () = assert p @ (ref int, ref int) (* REJECTED *) val () = assert p @ (r: ref int, =r) (* ACCEPTED *) val () = assert p @ (=r, r: ref int) (* ACCEPTED *) 3 / 42

Motivation Imagine an imperative implementation of sets: val make: [a] () -> set a val insert: [a] (set a, consumes a) -> () val merge: [a] (set a, consumes set a) -> () 4 / 42

Motivation Imagine an imperative implementation of sets: val make: [a] () -> set a val insert: [a] (set a, consumes a) -> () val merge: [a] (set a, consumes set a) -> () Then, let s = make() in produces s @ set t 4 / 42

Motivation Imagine an imperative implementation of sets: val make: [a] () -> set a val insert: [a] (set a, consumes a) -> () val merge: [a] (set a, consumes set a) -> () Then, let s = make() in produces s @ set t cannot do merge(s, s); 4 / 42

Motivation Imagine an imperative implementation of sets: val make: [a] () -> set a val insert: [a] (set a, consumes a) -> () val merge: [a] (set a, consumes set a) -> () Then, let s = make() in produces s @ set t cannot do merge(s, s); cannot do merge(s1, s2); insert(s2, x); 4 / 42

Motivation Imagine an imperative implementation of sets: val make: [a] () -> set a val insert: [a] (set a, consumes a) -> () val merge: [a] (set a, consumes set a) -> () Then, let s = make() in produces s @ set t cannot do merge(s, s); cannot do merge(s1, s2); insert(s2, x); cannot do insert(s, x1) and insert(s, x2) in independent threads 4 / 42

Motivation Imagine an imperative implementation of sets: val make: [a] () -> set a val insert: [a] (set a, consumes a) -> () val merge: [a] (set a, consumes set a) -> () Then, error in sequential code: let s = make() in produces s @ set t protocol violation cannot do merge(s, s); cannot do merge(s1, s2); insert(s2, x); cannot do insert(s, x1) and insert(s, x2) in independent threads 4 / 42

Motivation Imagine an imperative implementation of sets: val make: [a] () -> set a val insert: [a] (set a, consumes a) -> () val merge: [a] (set a, consumes set a) -> () Then, error in concurrent code: let s = make() in produces s @ set t data race cannot do merge(s, s); cannot do merge(s1, s2); insert(s2, x); cannot do insert(s, x1) and insert(s, x2) in independent threads 4 / 42

Permissions, in a nutshell Like a program logic, Mezzo's static discipline is flow-sensitive A current (set of) permission(s) exists at each program point Different permissions exist at different points There is no such thing as the type of a variable A permission has layout and ownership readings A permission is either duplicable or affine 5 / 42

What this talk is not about The paper and talk do not discuss: algebraic data types, which describe tree-shaped data, (static) regions, which can describe non-tree-shaped data, adoption & abandon, a dynamic alternative to regions, and much more (ICFP 2013) 6 / 42

Algebraic data types data list a = Nil Cons { head: a; tail: list a } data mutable mlist a = MNil Censored MCons { head: a; tail: mlist a } 7 / 42

Melding mutable lists val rec meld_aux [a] (xs: MCons { head: a; tail: mlist a }, consumes ys: mlist a) : () = match xstail with MNil -> xstail <- ys MCons -> end Censored meld_aux (xstail, ys) 8 / 42

Concatenating immutable lists val rec append_aux [a] (consumes ( dst: MCons { head: a; tail: () }, xs: list a, ys: list a )) : ( dst @ list a) = match xs with Cons -> let dst' = MCons { head = xshead; tail = () } in dsttail <- dst'; Nil -> tag of dst <- Cons; append_aux (dst', xstail, ys) dsttail <- ys; tag of dst <- Cons end Censored 9 / 42

Regions abstract region val newregion: () -> region abstract rref (rho : value) a fact duplicable (rref rho a) val newrref: (consumes x: a rho @ region) -> rref rho a val get: (r: rref rho a duplicable a rho @ region) -> a Censored val set: (r: rref rho a, consumes x: a rho @ region) -> () 10 / 42

Adoption and abandon val dfs [a] (g: graph a, f: a -> ()) : () = let s = stack::new groots in stack::work (s, fun (n: dynamic g @ graph a * s @ stack dynamic) : () = take n from g; if not nvisited then begin nvisited <- true; ) end; f ncontent; stack::push (nneighbors, s) give n to g Censored 11 / 42

What this talk is about So, what are the paper and talk about? extend Mezzo with threads and locks; describe a modular, machine-checked proof of type soundness; data race freedom 12 / 42

Outline Threads, data races, and locks (by example) Mezzo's architecture The kernel and its proof (glimpses) Conclusion 13 / 42

A data race open thread val r = newref 0 val f ( r @ ref int ) : () = r :=!r + 1 val () = spawn f ; spawn f 14 / 42

A data race open thread val r = newref 0 val f ( r @ ref int ) : () = r :=!r + 1 val () = spawn f ; spawn f f requires r @ ref int and gives it back 14 / 42

A data race open thread val r = newref 0 val f ( r @ ref int ) : () = r :=!r + 1 val () = spawn f ; spawn f spawn f requires r @ ref int and does NOT give it back 14 / 42

A data race open thread val r = newref 0 val f ( r @ ref int ) : () = r :=!r + 1 val () = spawn f ; spawn f TYPE ERROR! (in fact, this code is racy) 14 / 42

Introducing synchronization open thread open lock val r = newref 0 val l : lock (r @ ref int) = new() val f ( ) : () = acquire l; r :=!r + 1; release l val () = spawn f ; spawn f 15 / 42

Introducing synchronization open thread open lock val r = newref 0 val l : lock (r @ ref int) = new() val f ( ) : () = acquire l; r :=!r + 1; release l val () = spawn f ; spawn f this consumes r @ ref int the lock now mediates access to it 15 / 42

Introducing synchronization open thread open lock val r = newref 0 val l : lock (r @ ref int) = new() val f ( ) : () = acquire l; r :=!r + 1; release l val () = spawn f ; spawn f f requires no permission l @ lock () is duplicable 15 / 42

Introducing synchronization open thread open lock val r = newref 0 val l : lock (r @ ref int) = new() val f ( ) : () = acquire l; r :=!r + 1; release l val () = spawn f ; spawn f acquiring the lock produces r @ ref int 15 / 42

Introducing synchronization open thread open lock val r = newref 0 val l : lock (r @ ref int) = new() val f ( ) : () = acquire l; r :=!r + 1; release l val () = spawn f ; spawn f r @ ref int allows updating r 15 / 42

Introducing synchronization open thread open lock val r = newref 0 val l : lock (r @ ref int) = new() val f ( ) : () = acquire l; r :=!r + 1; release l val () = spawn f ; spawn f releasing the lock consumes r @ ref int 15 / 42

Introducing synchronization open thread open lock val r = newref 0 val l : lock (r @ ref int) = new() val f ( ) : () = acquire l; r :=!r + 1; release l val () = spawn f ; spawn f WELL-TYPED! (yup, this code is race free) 15 / 42

Abstracting synchronization (* A second-order function *) val hide : [a, b, s : perm ] ( f : (consumes a s) -> b consumes s ) -> (consumes a) -> b 16 / 42

Abstracting synchronization (* A second-order function *) val hide : [a, b, s : perm ] ( f : (consumes a s) -> b consumes s ) -> (consumes a) -> b hide is polymorphic in s eg, r @ ref int 16 / 42

Abstracting synchronization (* A second-order function *) val hide : [a, b, s : perm ] ( f : (consumes a s) -> b consumes s ) -> (consumes a) -> b hide takes a function f which has a side effect on s 16 / 42

Abstracting synchronization (* A second-order function *) val hide : [a, b, s : perm ] ( f : (consumes a s) -> b consumes s ) -> (consumes a) -> b hide consumes s which becomes owned by the lock 16 / 42

Abstracting synchronization (* A second-order function *) val hide : [a, b, s : perm ] ( f : (consumes a s) -> b consumes s ) -> (consumes a) -> b hide produces a new function which has no advertised effect 16 / 42

A synchronization pattern open lock val hide [a, b, s : perm] ( f : (consumes a s) -> b consumes s ) : (consumes a) -> b = let l : lock s = new () in fun (consumes x : a) : b = acquire l; let y = f x in release l; y 17 / 42

Introducing synchronization, revisited open thread open hide val r = newref 0 val f ( r @ ref int) : () = r :=!r + 1 val f = hide f val () = spawn f; spawn f 18 / 42

Introducing synchronization, revisited open thread open hide val r = newref 0 val f ( r @ ref int) : () = r :=!r + 1 val f = hide f val () = spawn f; spawn f r @ ref int 18 / 42

Introducing synchronization, revisited open thread open hide val r = newref 0 val f ( r @ ref int) : () = r :=!r + 1 val f = hide f val () = spawn f; spawn f r @ ref int f @ ( r @ ref int) -> () 18 / 42

Introducing synchronization, revisited open thread open hide val r = newref 0 val f ( r @ ref int) : () = r :=!r + 1 val f = hide f val () = spawn f; spawn f f @ () -> () 18 / 42

Introducing synchronization, revisited open thread open hide val r = newref 0 val f ( r @ ref int) : () = r :=!r + 1 val f = hide f val () = spawn f; spawn f WELL-TYPED! (yup, this code is race free) 18 / 42

Outline Threads, data races, and locks (by example) Mezzo's architecture The kernel and its proof (glimpses) Conclusion 19 / 42

What is Mezzo? A kernel: a λ-calculus with threads; affine, polymorphic, value-dependent, with type erasure Several extensions: mutable state: references; hidden state: locks; dynamic ownership control: adoption and abandon All machine-checked in Coq (14KLOC) 20 / 42

Modularity We wish to prove that well-typed programs: do not go wrong; are data-race free This is trivial - true of all programs - in the kernel calculus! Subject reduction and progress are non-trivial results We set up their proof so that it is robust in the face of extensions 21 / 42

Modularity We parameterize the kernel with: a type of machine states s; a type of instrumented states R, or resources; which must form a monotonic separation algebra; a correspondence relation, s R Subject reduction and progress hold for all such parameters 22 / 42

Pseudo-Modularity The kernel is not parameterized wrt the extensions We add the extensions, one after another, on top of the kernel So, the Coq code is monolithic Fortunately, each extension is (morally) independent of the others; the key statements do not change with extensions; only new proof cases appear 23 / 42

Outline Threads, data races, and locks (by example) Mezzo's architecture The kernel and its proof (glimpses) Conclusion 24 / 42

Values and terms A fairly unremarkable untyped λ-calculus with threads κ ::= value term soup (Kinds) v ::= x λxt (Values) t ::= v v ṫ spawn v v (Terms) 25 / 42

Operational semantics initial configuration new configuration s / (λxt) v s / [v/x]t s / E[t] s / E[t ] if s / t s / t s / thread (t) s / thread (t ) if s / t s / t s / t 1 t 2 s / t 1 t 2 if s / t 1 s / t 1 s / t 1 t 2 s / t 1 t 2 if s / t 2 s / t 2 s / thread (D[spawn v 1 v 2 ]) s / thread (D[()]) thread (v 1 v 2 ) 26 / 42

Operational semantics an abstract notion of machine state initial configuration new configuration s / (λxt) v s / [v/x]t s / E[t] s / E[t ] if s / t s / t s / thread (t) s / thread (t ) if s / t s / t s / t 1 t 2 s / t 1 t 2 if s / t 1 s / t 1 s / t 1 t 2 s / t 1 t 2 if s / t 2 s / t 2 s / thread (D[spawn v 1 v 2 ]) s / thread (D[()]) thread (v 1 v 2 ) 26 / 42

Types and permissions κ ::= type perm (Kinds) T, U ::= x =v T T (T P) (Types) x : κt x : κt P, Q ::= x v @ T empty P P (Permissions) x : κp x : κp duplicable θ θ ::= T P 27 / 42

The typing judgement A traditional type system uses a list Γ of type assumptions: Γ t : T Mezzo splits it into a list K of kind assumptions and a permission P: K, P t : T This can be read like a Hoare triple: K { P} t { T} 28 / 42

The typing judgement A typing judgement about a running program (or thread) depends on a resource R: R, K, P t : T R is the thread's partial, instrumented view of the machine state 29 / 42

Resources A resource is: partial: a resource could be, say, a heap fragment; instrumented: a resource could record whether each location is mutable or immutable 30 / 42

Resources A resource is: partial: a resource could be, say, a heap fragment; instrumented: a resource could record whether each location is mutable or immutable At this stage, though, resources are abstract What properties must we require of them? 30 / 42

Monotonic separation algebra R R 1 R 2 R R 1 R 2 resource eg, an instrumented heap fragment maps every address to, N, X v, or D v conjunction eg, requires separation at mutable addresses requires agreement at immutable addresses duplicable core eg, throws away mutable addresses keeps immutable addresses tolerable interference (rely) eg, allows memory allocation 31 / 42

Working with abstract resources Star is commutative and associative R 1 R 2 ok implies R 1 ok R R = R R 1 R 2 = R and R ok imply R 1 = R R R = R implies R = R R R = R R R R 1 ok and R 1 R 2 imply R 2 ok R 1 R 2 implies R 1 R 2 rely preserves splits: R 1 R 2 R R 1 R 2 ok R 1R 2, R 1 R 2 = R R 1 R 1 R 2 R 2 32 / 42

A small set of typing rules Singleton R; K; P v : =v Frame R; K; P t : T R; K; P Q t : T Q Function R; K, x : value; P x @ T t : U R; K; (duplicable P) P λxt : T U ForallIntro t is harmless R; K, x : κ; P t : T R; K; x : κp t : x : κt ExistsIntro R; K; P v : [U/x]T R; K; P v : x : κt Cut R 2 ; K; P 1 P 2 t : T R 1 ; K P 1 R 1 R 2 ; K; P 2 t : T ExistsElim R; K, x : κ; P t : T R; K; x : κp t : T SubLeft K P 1 P 2 R; K; P 1 t : T R; K; P 2 t : T SubRight R; K; P t : T 1 K T 1 T 2 R; K; P t : T 2 Application R; K; Q t : T R; K; (v @ T U) Q v t : U Spawn R; K; (v 1 @ T U) (v 2 @ T) spawn v 1 v 2 : 33 / 42

Selected typing rules The kernel typing rules manipulate R abstractly R ; K, x : value; P x @ T t : U R; K; (duplicable P) P λxt : T U R 2 ; K; P 1 P 2 t : T R 1 ; K P 1 R 1 R 2 ; K; P 2 t : T 34 / 42

Selected typing rules The kernel typing rules manipulate R abstractly R ; K, x : value; P x @ T t : U R; K; (duplicable P) P λxt : T U cannot capture an arbitrary resource R can capture its duplicable core R R 2 ; K; P 1 P 2 t : T R 1 ; K P 1 R 1 R 2 ; K; P 2 t : T 34 / 42

Selected typing rules The kernel typing rules manipulate R abstractly R R 2 ; K; P 1 P 2 t : T ; K, x : value; P x @ T t : U R 1 ; K P 1 R; K; (duplicable P) P λxt : T U R 1 R 2 ; K; P 2 t : T if a typing rule has two premises then R must be split between them 34 / 42

Subject reduction Lemma (SR, preliminary form) R 2 R 2 s 1 / t 1 s 2 / t 2 s 1 R 1 R 1 R 1 ; ; empty t 1 : T s 2 R 2 R 2 R 2 ; ; empty t 2 : T R 1 R 2 35 / 42

Subject reduction one thread takes a step Lemma (SR, preliminary form) R 2 R 2 s 1 / t 1 s 2 / t 2 s 1 R 1 R 1 R 1 ; ; empty t 1 : T s 2 R 2 R 2 R 2 ; ; empty t 2 : T R 1 R 2 35 / 42

Subject reduction Lemma (SR, preliminary form) R 2 R 2 this thread's view is R 1 the other threads' view is R 1 s 1 / t 1 s 2 / t 2 s 1 R 1 R 1 R 1 ; ; empty t 1 : T s 2 R 2 R 2 R 2 ; ; empty t 2 : T R 1 R 2 35 / 42

Subject reduction Lemma (SR, preliminary form) R 2 R 2 this thread is well-typed under its view s 1 / t 1 s 2 / t 2 s 1 R 1 R 1 R 1 ; ; empty t 1 : T s 2 R 2 R 2 R 2 ; ; empty t 2 : T R 1 R 2 35 / 42

Subject reduction Lemma (SR, preliminary form) R 2 R 2 s 1 / t 1 s 2 / t 2 s 1 R 1 R 1 R 1 ; ; empty t 1 : T s 2 R 2 R 2 R 2 ; ; empty t 2 : T R 1 R 2 this thread's view and the other threads' view evolve 35 / 42

Subject reduction Lemma (SR, preliminary form) R 2 R 2 s 1 / t 1 s 2 / t 2 s 1 R 1 R 1 R 1 ; ; empty t 1 : T s 2 R 2 R 2 R 2 ; ; empty t 2 : T R 1 R 2 the new machine state agrees with the new views 35 / 42

Subject reduction Lemma (SR, preliminary form) R 2 R 2 s 1 / t 1 s 2 / t 2 s 1 R 1 R 1 R 1 ; ; empty t 1 : T s 2 R 2 R 2 R 2 ; ; empty t 2 : T R 1 R 2 the thread remains well-typed under its view 35 / 42

Subject reduction Lemma (SR, preliminary form) R 2 R 2 s 1 / t 1 s 2 / t 2 s 1 R 1 R 1 R 1 ; ; empty t 1 : T s 2 R 2 R 2 R 2 ; ; empty t 2 : T R 1 R 2 the interference inflicted on the other threads is tolerable 35 / 42

Subject reduction Theorem (Subject Reduction) Reduction preserves well-typedness c 1 c 2 c 1 c 2 36 / 42

Progress A configuration c is acceptable if every thread: has reached an answer; or is able to make one step; or (after introducing locks) is waiting on a locked lock Theorem (Progress) Every well-typed configuration is acceptable 37 / 42

Data race freedom Cannot be stated for the kernel We introduce references first There, writing requires an exclusive access right Hence, it is easy to prove that: Theorem A well-typed program cannot exhibit a data race 38 / 42

Outline Threads, data races, and locks (by example) Mezzo's architecture The kernel and its proof (glimpses) Conclusion 39 / 42

Related work Alias Types Separation Logic L 3 (And a lot more) Views (Dinsdale-Young et al, 2013) are particularly relevant extensible framework; monolithic machine state, composable views, agreement; while-language instead of a λ-calculus 40 / 42

A few lessons The good old syntactic approach to type soundness works Formalization helps clarify and simplify A lot In the end, it is just affine λ-calculus 41 / 42

Thank you More information in the paper and online: http://galliuminriafr/~protzenk/mezzo-lang/ Try it out! 42 / 42

Road map weakening affinity duplication substitution stability classification decomposition type soundness subject reduction progress soundness of subsumption canonicalization 1 / 7

Dealing with binding In Coq, we use only one syntactic category Well-kindedness distinguishes values, terms, types, etc avoids a quadratic number of substitution functions! makes it easy to deal with dependency Binding encoded via de Bruijn indices Re-usable library, dblib The main hygiene lemmas have >90 cases and 4-line proofs 2 / 7

Algebraic data types data list a = Nil Cons { head: a; tail: list a } data mutable mlist a = MNil MCons { head: a; tail: mlist a } 3 / 7

Melding mutable lists val rec meld_aux [a] (xs: MCons { head: a; tail: mlist a }, consumes ys: mlist a) : () = match xstail with MNil -> xstail <- ys MCons -> meld_aux (xstail, ys) end 4 / 7

Concatenating immutable lists val rec append_aux [a] (consumes ( dst: MCons { head: a; tail: () }, xs: list a, ys: list a )) : ( dst @ list a) = match xs with Cons -> let dst' = MCons { head = xshead; tail = () } in dsttail <- dst'; tag of dst <- Cons; append_aux (dst', xstail, ys) Nil -> dsttail <- ys; tag of dst <- Cons end 5 / 7

Regions abstract region val newregion: () -> region abstract rref (rho : value) a fact duplicable (rref rho a) val newrref: (consumes x: a rho @ region) -> rref rho a val get: (r: rref rho a duplicable a rho @ region) -> a val set: (r: rref rho a, consumes x: a rho @ region) -> () 6 / 7

Adoption and abandon val dfs [a] (g: graph a, f: a -> ()) : () = let s = stack::new groots in stack::work (s, fun (n: dynamic g @ graph a * s @ stack dynamic) : () = take n from g; if not nvisited then begin nvisited <- true; f ncontent; stack::push (nneighbors, s) end; give n to g ) 7 / 7