Slide 1

Slide 1 text

Verifying Concurrent Programs by 
 Controlling Alias Interference Colin S. Gordon University of Washington [email protected] " Dissertation Defense July 2, 2014

Slide 2

Slide 2 text

Modular Reasoning for Programs • Decompose program into components • Data types + operations • Specifications giving expected input-output behavior • Localize informal reasoning • Devs treat dependencies as black boxes • Localize faults when testing (e.g., unit tests + dependency injection) • Goal Formally reason component X in isolation • ∀ o:operation in X, ensure o’s results meet o’s specification • Assume correctness of X’s dependencies 2

Slide 3

Slide 3 text

Concurrent Programs *x = 3;! " assert(*x > 0); ...! *x = 0;! ... 3 Thread 1 Thread 2

Slide 4

Slide 4 text

Sequential Programs *x = 3;! f(x) ! ! ! ! ! !! assert(*x > 0); f(int* y) {! *y = 0;! } 4 Caller Callee

Slide 5

Slide 5 text

Verification Challenge: Aliasing +Mutation • Aliasing: multiple names for shared memory • 2+ incompatible points of view • *x > 0 vs. *x >= 0 • One view may invalidate another’s assumptions • Decades spent on flexible ways to manage this problem • My research: A new approach based on describing interference between aliases • Local, function, and thread interference is via aliases 5

Slide 6

Slide 6 text

Thesis Statement " " Interference between individual aliases is
 a useful and powerful approach to verifying programs, and
 reduces the distance between verifying 
 sequential and concurrent programs. 6

Slide 7

Slide 7 text

Thesis Statement: Implications Alias interference can be exploited for: • Proving data race freedom • Proving (sequential) data structure invariants • Proving concurrent (lock-free) data structure invariants • Proving functional correctness of data structure operations • Proving data structure invariants for actual Haskell programs 7

Slide 8

Slide 8 text

My Goals
 for Program Verification • Simple conceptual model • Only a few component ideas, all familiar • Flexible precision and rigor • Support serial and concurrent programs • From weak (e.g., readonly) to strong (e.g., functional correctness) properties • From informal (whiteboard) to formal (proof) • Handle gradual verification • Verify pieces of a program in context, not all-or- nothing • Support iterative refinement of program properties 8

Slide 9

Slide 9 text

Talk
 Outline Program Verification Background [Prior work] Reference Immutability Rely-Guarantee References Concurrent Rgrefs Serial Concurrent Non-Interference (mutation yes/no) Logical & Temporal Invariants (x > 0, x only increases) Rgref Trace Refinement Future Work 9 Functional Correctness RGHaskell Practical

Slide 10

Slide 10 text

The Design Space of
 Verification Techniques Verification Style • Global Reasoning • Hoare Logic, Owicki-Gries • Isolation • Separation logic, uniqueness • Serialized Sharing • Locks, invariants • Read Sharing • Reference immutability, DPJ • Interference Summaries • Rely-guarantee Limitations • Non-compositional " • Constrained aliasing " • Coarse-grained interaction " • Coarse guarantees " • Requires summaries

Slide 11

Slide 11 text

• Epitomized by Rely-Guarantee reasoning: " " " • An assertion P is stable w.r.t relation R if: – P is preserved by any action in R – σ⊨P ∧ (σ,σ’)∈R ⇒ σ’⊨P – x=0⊨x≥0 ∧(x=0,x=1)∈inc(x) ⇒ x=1⊨x≥0 • Stable assertions will not be invalidated by other threads! Interference Summaries Union

Slide 12

Slide 12 text

Interference in Sequential Programs • Nearly unexplored for sequential applications! • Wickerson et al. verify UNIXv7 malloc (ESOP’10) • This dissertation (portions in PLDI’13) • Recently: Militao et al., Rely-Guarantee Protocols (to appear at ECOOP’14) • Cause: Historical view that sequential and concurrent verification are inherently different

Slide 13

Slide 13 text

My Approach:
 Reference Interference Summaries • References are capabilities • Holding a reference grants certain abilities • Different references: possibly different capabilities! • Successful mental model • Already used; next JS spec adds support (proxies) • Simple cases already work • Reference immutability, ownership types, object capability systems… 13

Slide 14

Slide 14 text

Talk
 Outline • Background • Reference Immutability for Data Race Freedom • Rely-Guarantee References • Lock-free Rely-Guarantee References • Functional Correctness via Rely-Guarantee References • Rely-Guarantee References for Haskell • Future Work / Conclusions 14

Slide 15

Slide 15 text

Reference Immutability
 for Data Race Freedom • Collaboration with Microsoft • My work primarily formalizing and proving • Use Reference Immutability • Per-reference permissions allow or prohibit mutation • Permissions on aliases must be compatible: e.g., no writable alias to immutable data • Exploit mutation and aliasing bounds for race freedom • Results • Data race free language (+ generics, borrowing, & more) • Sequential tool ! concurrency (2 simple type rules) • Concurrent proof structure ! sequential concepts! 15

Slide 16

Slide 16 text

Talk
 Outline • Background • Reference Immutability for Data Race Freedom • Rely-Guarantee References • Lock-free Rely-Guarantee References • Functional Correctness via Rely-Guarantee References • Rely-Guarantee References for Haskell • Future Work / Conclusions 16

Slide 17

Slide 17 text

Rely-Guarantee References • Permit some mutation, not just all-or none • Want to reason about invariants • Use rely-guarantee reasoning for aliases • Results • Knowing interference between aliases is useful • Again: concurrent proof ! sequential programs • Formalized subtle developer reasoning 17

Slide 18

Slide 18 text

Monotonic Counters:
 A Verification Challenge Problem • Posed by Pilkiewicz and Pottier (TLDI’11) • Simple to explain • Represents larger challenges: • Enforce state usage over time • Restrict how state changes, not verify the increment function • Infinite state • Little prior work handles this • Pretty complicated • We can specify and enforce this • Simple specification, no abstraction required 18

Slide 19

Slide 19 text

Manual Monotonic Counters let x : ref int = alloc 1 in! // x is an increasing counter > 0! assert (!x > 0);! let x0 = !x in! m(x);! assert (!x >= x0);! assert (!x > 0) 19 m(y : ref int){! y := 0 //bad!! } > 0, ++ ≥ 0, ≥ 0

Slide 20

Slide 20 text

Rely-Guarantee References • Binary Relations specify pre/post state pairs • Rely R: summary of alias behavior • Guarantee G: permitted local behavior • Unary Relations specify invariants • Predicate P: refinement of data • P preserved by (stable w.r.t.) R • Global alias invariant: For any aliases x and y, x.G => y.R and vice versa • Enforced with every new alias creation! • ref T ≡ ref{T|any}{havoc, havoc} • Can encode reference immutability, typestate, related systems 20 ref{τ|P}[R,G] standard reference (≈ T* in C) Rely (e.g. ==) Guarantee (e.g.≤) Predicate (e.g. >0)

Slide 21

Slide 21 text

RGrefs for Increments let x : ref{int|>0}[≤,≤] = alloc 1 in //Statically, 1 > 0 ! !x > 0! m(x); //By rely/guarantee: old(!x) ≤ new(!x) //By stable predicate, value at x > 0 x := !x + 1 // Statically: !x ≤ !x + 1 21 m(y : ref{int|>0}[≤,≤]){…}

Slide 22

Slide 22 text

: ref{ListNode|isSorted} [updateOK, updateOK] Challenge: Nested References Rely, Guarantee, and Predicate can talk about heaps… • Nested rely relations? • Interior pointers can’t surprise “root” pointers • Nested guarantees for restricted aliases? • Propagate restrictions at dereference • How much heap can we restrict? • What’s reachable from referent 22 x : ref{ListNode | isSorted } [ updateOK , readonly ] next val … G allows insert … X

Slide 23

Slide 23 text

Experimenting with RGrefs" • Implemented as a shallow DSL embedded into Coq • Verifications: • Monotonic counter • Prepend-only lists • Append-only lists • Reference Immutability as a library • Race-free rely-guarantee references • Function memoization • https://github.com/csgordon/rgref/ 23

Slide 24

Slide 24 text

Evaluating Verification • Expressiveness • Accepts interesting, useful programs • So far: small programs • Later: lock-free data structures, Haskell • Correctness (Soundness) • Accepts only correct programs • Requires a proof: • Always, the refinement on each reference is true in the current heap 24

Slide 25

Slide 25 text

Lessons from 
 Rely-Guarantee References • Reference-based mutation control is useful • Verifies interesting examples • Interference is the right model for aliasing • Applied concurrency verification ideas to aliasing • Current developer reasoning is subtle • RGrefs roughly formalize what devs already think about • Informal reasoning can be made precise • Informal pitfalls highlighted 25

Slide 26

Slide 26 text

Talk
 Outline • Background • Reference Immutability for Data Race Freedom • Rely-Guarantee References • Lock-free Rely-Guarantee References • Functional Correctness via Rely-Guarantee References • Rely-Guarantee References for Haskell • Future Work / Conclusions 26

Slide 27

Slide 27 text

RGrefs for Concurrency • Rely-Guarantee originally for threads • Hoare logic with rely and guarantee for thread actions:
 R,G⊢{P} C {Q} • To make RGrefs useful for concurrency: 1. Fix atomicity assumptions in first revision 2. Add concurrency primitives • Results • Concurrent reasoning is incremental over sequential • RGrefs strong enough for new general results • Proof structure simplified by considering interference 27

Slide 28

Slide 28 text

Lock-Free Monotonic Counter atomic_inc(c : ref{ int | >0 }[ ≤ , ≤ ]) {! do {! let x = !c! } while (not CAS(c, x, x + 1))! } Instead of proving: !c ≤ !c + 1 Prove:
 h[c] = x ! h[c] ≤ x + 1 28 bool CAS(r:ref{T|P}[R,G], old:T, new:T) { atomic { if (!r = old) then { [r]:=new; return true } else { return false } } }

Slide 29

Slide 29 text

Verified Lock Free Data Structures • Treiber Stack • Updates push/pop 1 element • Representation is acyclic • Michael-Scott Queue • Simplified for exposition • Enqueue / dequeue 1 element • Representation is acyclic • Lock-free Union Find • Parent chains acyclic, parent rank decreases • All ops are union or path compression • Elements in the same set remain in same set 29

Slide 30

Slide 30 text

Lessons from
 Concurrent RGrefs • Concurrent reasoning can be an incremental refinement of sequential case • RGrefs strong enough for new developments • Verifying lock-free union find invariants • Proof by embedding into Views Framework • As in reference immutability work • Synthesize thread interference from alias interference 30

Slide 31

Slide 31 text

Talk
 Outline • Background • Reference Immutability for Data Race Freedom • Rely-Guarantee References • Lock-free Rely-Guarantee References • Functional Correctness via Rely-Guarantee References • Rely-Guarante References for Haskell • Future Work / Conclusions 31

Slide 32

Slide 32 text

Functional Correctness via Tracing • Generate abstract trace from typing • Interleave local actions and interference • Loop(R!read)!R!increment!R • Simplify abstract trace: R is a rely from a reference! • Loop(R!read)!R!increment!R • Loop(R)!R!increment!R • R!R!increment!R • R!increment!R • This is a spec! • Examples: • Counter • Treiber stack 32

Slide 33

Slide 33 text

Talk
 Outline • Background • Reference Immutability for Data Race Freedom • Rely-Guarantee References • Lock-free Rely-Guarantee References • Functional Correctness via Rely-Guarantee References • Rely-Guarantee References for Haskell • Future Work / Conclusions 33

Slide 34

Slide 34 text

Rely-Guarantee Haskell • Embed (a variant of) RGRefs into Liquid Haskell • Haskell + Refinements + SMT + Rely-Guarantee • Results • RGRef implementation for a real language • Experience with real-world code • Experience with inference • Experience with automation (SMT) • RGRefs nicely complement general refinement types 34

Slide 35

Slide 35 text

Embedding RGRefs into LH • Reduce higher-order logical statements to first-order (stability, reflexivity)
 stable p r = 
 x:a

-> y:a -> {z:a

| z = y}! " • No dereference expression (Haskell state is fully monadic) • Haskell code often uses modifyIORef • Observations imply stable refinements • Alternative: Embed Hoare Logic in LH (Hard) 35

Slide 36

Slide 36 text

Core RGRefs in Liquid Haskell 36 module RG where! {-@ data RGRef a

Prop, r :: a -> a -> Prop > = Wrap (rgref_ref :: IORef a

) @-}! RGRef a = Wrap (IORef a) ! " {-@ newRGRef :: forall

Prop, r :: a -> a -> Prop >.! e:a

-> ! e2:a -> ! f:(x:a

-> y:a -> {v:a

| (v = y)}) -> IO (RGRef a) @-} ! newRGRef :: a -> a -> (a -> a -> a) -> IO (RGRef a)! newRGRef e e2 stabilityPf = do { r <- newIORef e; return (Wrap r) } ! " {-@ assume readRGRef :: forall

Prop, r :: a -> a -> Prop >. ! x:RGRef a -> IO (a

) @-} ! readRGRef (Wrap x) = readIORef x ! " {-@ assume writeRGRef :: forall

Prop, r :: a -> a -> Prop>. ! x:(RGRef a) -> old:a -> new:a -> IO () @-}! writeRGRef :: RGRef a -> a -> a -> IO () ! writeRGRef (Wrap x) old new = writeIORef x new! " {-@ modifyRGRef :: forall

Prop, r :: a -> a -> Prop >.! rf:(RGRef a) -> ! f:(x:a

-> a) -> ! pf:(x:a

-> y:a -> {v:a

| (v = y)}) -> IO () @-}! modifyRGRef :: RGRef a -> (a -> a) -> (a -> a -> a) -> IO () ! modifyRGRef (Wrap x) f pf = modifyIORef x (\ v -> pf v (f v)) !

Slide 37

Slide 37 text

MonotonicCounter.hs -- Stability Proof: Requires explicit type annotation {-@ stable_monocount :: x:{v:Int | v > 0 } -> ! y:{v:Int | x <= v } -> {v:Int | ((v = y) && (v > 0)) } @-} stable_monocount :: Int -> Int -> Int! stable_monocount x y = y -- Monotonically increasing counter! {-@ alloc_counter :: ! () -> IO (RGRef<{\x -> x > 0}, {\x y -> x <= y}> Int) @-} alloc_counter :: () -> IO (RGRef Int) alloc_counter _ = newRGRef 1 3 stable_monocount! {-@ inc_counter :: ! RGRef<{\x -> x > 0}, {\x y -> x <= y}> Int -> IO () @-} inc_counter :: RGRef Int -> IO () inc_counter r = modifyRGRef r (\x -> x + 1) stable_monocount! -- the stability proof is used as a cast! 37

Slide 38

Slide 38 text

Lock Free Monotonic Counter {-@ atomic_inc :: ! RGRef<{\x -> x > 0}, {\x y -> x <= y}> Int -> IO () @-}! atomic_inc :: RGRef Int -> IO ()! atomic_inc r = ! ! atomicModifyRGRef r (\x -> x + 1) stable_monocount 38

Slide 39

Slide 39 text

Other Liquid Haskell Verification • STM Undo log • Sequential fallback for STM Haskell (slightly modified) • Verified undo log is only appended to (on the right end) • Lock-free Linked List • GHC test case • Append, delete, lookup modify correctly • Logical and physical deletion separated, preserve link structure • Observed refinement: A logically deleted node is immutable 39

Slide 40

Slide 40 text

Lessons from Liquid RGRefs • RGRefs can be simplified while retaining utility • First order logic • Only local heap access + exploiting frozen cells • RGRefs are amenable to inference • RGRefs are useful for actual Haskell code 40

Slide 41

Slide 41 text

Talk
 Outline • Background • Reference Immutability for Data Race Freedom • Rely-Guarantee References • Lock-free Rely-Guarantee References • Functional Correctness via Rely-Guarantee References • Rely-Guarantee References for Haskell • Future Work / Conclusions 41

Slide 42

Slide 42 text

Future Work • RGRefs for Inductive Data Types • Subtlety of nested pointers • RGRefs + … • Hoare/Separation Logic • Tried in Liquid Haskell, but the tool isn’t ready • Ghost State • Linear Capabilities • Temporal Logic • Simpler assertions (reference immutability < L < FOL) • Flexible choice of heap fragment size 42

Slide 43

Slide 43 text

Conclusions • Interference between individual aliases is
 a useful and powerful approach to verifying programs; • 4 prototypes, verified sophisticated examples • It reduces the distance between verifying 
 sequential and concurrent programs • Sequential and concurrent versions nearly the same, for metatheory, code, and proofs 43

Slide 44

Slide 44 text

Thanks! And thanks to my excellent collaborators: 44 47