520 likes | 531 Views
A calculus of atomic actions problem, highlighting verifying assertions in concurrent programs through a proof method known as QED. Our approach focuses on local conditions, program invariants, data integrity, and absence of null pointer dereferences. We use the QED tool, emphasizing atomicity for separation of concerns and concurrency handling. Proof by reduction techniques, including action patterns and abstraction, are employed for successful assertion validation in concurrent programming. The application of QED proof rules and program transformations is central to our approach, ensuring soundness and validation of program assertions.
E N D
Serdar Tasiran Koc University, Istanbul, Turkey Tayfun Elmas Shaz Qadeer Koc University Microsoft Research A Calculus of Atomic Actions
Problem • Verifying assertions in concurrent programs • Local conditions, program invariants, data integrity, absence of null pointer dereferences, ... static void transfer(Account from, Account to, int amount) { assert (from != null && to != null); assert (from.balance >= amount); old_total := from.balance + to.balance; tmp := from.balance; from.balance := tmp – amount; tmp := to.balance; to.balance := tmp + amount; assert (from.balance + to.balance == old_total); }
Our Approach: QED • Proof method for verifying assertions • Implementation: The QED tool • Central idea: Atomicity • Allows separation of concerns • Concurrency: Non-interference, synchronization mechanism • Data: Sequential reasoning within atomic blocks to prove assertions • QED proof rules: Program transformation steps • Abstraction: To increase “non-interference” between actions • Reduction: Prove bigger blocks atomic • Grow atomic blocks • Sequential reasoning within atomic blocks
Outline • Motivating example • Proof method • Experience • Full example: Multiset
Example – inc () Main: assume (x == 0); inc() || inc() assert (x == 2) inc (): int t; acquire (lock); t = x; t = t + 1; x = t; release(lock);
Proof by Owicki-Gries A: <B@L0=>x=0, B@L5=>x=1> L0: acquire(l); <B@L0=>x=0, B@L5=>x=1, held(l,A)> L1: t := x; <B@L0=>x=0, B@L5=>x=1, held(l,A), t=x> L2: t := t + 1; <B@L0=>x=0, B@L5=>x=1, held(l,A), t=x+1> L3: x := t; <B@L0=>x=1, B@L5=>x=2, held(l,A)> L4: release(l) <B@L0=>x=1, B@L5=>x=2> B: <A@L0=>x=0, A@L5=>x=1> L0:acquire(l); <A@L0=>x=0, A@L5=>x=1, held(l,B)> L1:t := x; <A@L0=>x=0, A@L5=>x=1, held(l,B), t=x> L2:t := t + 1; <A@L0=>x=0, A@L5=>x=1, held(l,B), t=x+1> L3:x := t; <A@L0=>x=1, A@L5=>x=2, held(l,B)> L4:release(l) <A@L0=>x=1, A@L5=>x=2> ||
Reduction • α right mover (R) if for every execution ξ = ……. α β …….ξ’ = ……. β α ……. is equivalent to ξ • Static check for α being a right mover: • For all actions β in program, is α β always “simulated” by β α? • Easy case: β cannot follow α • Example: α = β = lock.acquire() • Reduction: Atomic patterns • RRRR N LLLLL • RRRRR • LLLLL • ......
Proof by reduction - 1 inc (): int t; acquire (lock); t = x; t = t + 1 x = t; release(lock); inc (): int t; acquire (lock); t = x; t = t + 1 x = t; release(lock); R B REDUCE-SEQUENTIAL B B L inc (): x = x + 1;
Proof by reduction - 2 assume (x == 0); x = x + 1; x = x + 1; assert (x == 2) assume (x == 0); inc() || inc() assert (x == 2) assume (x == 0); x = x + 1 || x = x + 1 assert (x == 2) B B REDUCE-PARALLEL INLINE-CALL
Non-blocking increment inc (): int t; while(true) { t = x; if(CAS(x, t, t+1)) break; } CAS(x, t, t + 1): □ assume (x == t); x = t + 1; return true; assume (x != t); return false;
Abstraction for reduction inc (): int t; while(*) { t = x; assume x != t; } t = x; assume x == t; x = t + 1; inc (): int t; while(true) { t = x; if(CAS(x, t, t+1)) break; }
Abstraction for reduction inc (): int t; while(*) { havoc t; skip; } havoc t; assume x == t; x = t + 1; inc (): int t; while(*) { t = x; assume x != t; } t = x; assume x == t; x = t + 1; SIMULATE SIMULATE SIMULATE
Proof by reduction inc (): int t; while(*) { havoc t; skip; } havoc t; assume x == t; x := t + 1; inc (): int t; havoc t; havoc t; x := x + 1; REDUCE-LOOP B
ActionPL Syntax: Procedure bodies Global variables Program’s main body Program:
Gated actions Gate: Assertion on pre-state Transition: Two-store relation x = x + 1; assert (x != 0); y = y / x; x = x + 1; assert (x != 0); y = y / x;
Semantics of ActionPL Current dynamic statement Current store Execution: Atomic transitions: Pre- and post-store satisfies transition Pre-store violates assertion Pre-store satisfies assertion
Proof method Proof context Program invariant (proof ensures this) Each execution starts from invariant Each transition preserves invariant Current program • Proof step: Governed by a proof rule • May strengthen invariant • May rewrite program
Soundness • Theorem [Preservation] • Each proof step: • Let . If goes from to then • goes from to , or • goes wrong from • Theorem [Soundness] • Each proof: • If goes from to such that then: • goes from to , or • goes wrong from skip, error,other dyn. stmt.
Validating assertions • Proof succeeds if • For all in , holds. • Verifying each assertion is local & sequential
Invariants Each action either goes wrong or preserves new invariant New invariant is stronger true (y >= 0) Does not touch y x = y; x = x + 1; x = y; x = x + 1; Preserves invariant if assertion is not violated assert (x > 0); y = y / x; assert (x > 0); y = y / x;
Validating assertions - example (y >= 0) (y > -1) (y >= 0) (y >= 0) (y >= 0) x = y; x = x + 1; assert (y > -1); x = y; x = x + 1; assert (x > 0); y = y / x; assert (true); x = y; x = x + 1; y = y / x; assert (x > 0); y = y / x;
Auxiliary variables inc (): int t; acquire (lock); a := tid; t := x; t := t + 1 x := t; release(lock); a := 0;
Auxiliary variables 24 We add an auxiliary variable We may modify all actions New transition modifies new aux. variable New transitions preserve invariant Auxiliary variable does not affect rest of transition predicate, cannot block.
Simulation From each store satisfying invariant, goes wrong or simulates . New action preserves invariant New action simulates the former Adding behaviors that go wrong Adding non-determinism assert (x > 0); y = y / x; if (x == 1) y = y + 1; if (*) y = y + 1; y = y / x;
Read abstraction inc (): int t; while(*) { havoc t; skip; } havoc t; assume x == t; x = t + 1; inc (): int t; while(*) { t = x; assume x != t; } t = x; assume x == t; x = t + 1; SIMULATE SIMULATE
Abstraction with assertions inc (): int t; acquire (lock); a := tid; t := x; t := t + 1 x := t; release(lock); a := 0; inc (): int t; acquire (lock); t := x; t := t + 1 x := t; release(lock); AUX-ANNOTATE
Abstraction with assertions inc (): int t; acquire (lock); a := tid; assert a == tid; t := x; assert a == tid; t := t + 1 assert a == tid; x := t; assert a == tid;release(lock); a := 0; inc (): int t; acquire (lock); a := tid; t := x; t := t + 1 x := t; release(lock); a := 0; SIMULATE
Abstraction with assertions Discharges the assertions inc (): int t; acquire (lock); a := tid; assert a == tid;t := x; assert a == tid;t := t + 1 assert a == tid;x := t; assert a == tid;release(lock); a := 0; inc (): int t; acquire (lock); a := tid; assert a == tid; t := x; assert a == tid; t := t + 1 assert a == tid; x := t; assert a == tid;release(lock); a := 0; R B B B L REDUCE & RELAX • “Borrowing assertions”: • Introduce assertions, use them to prove larger blocks atomic • Discharge later, when atomic blocks are large enough.
Reduction Moving to the left makes the execution either - go to same end state or - makes execution go wrong Actions executed by different threads
Reduce – nondet. choice CAS(x, t, t + 1): CAS(x, t, t + 1): □ □ assume (x == t); x = t + 1; return true; assume (x != t); return false; assume (x == t); x = t + 1; return true; assume (x != t); return false; DIV(x, y): DIV (x, y): □ assert (x != 0 && y != 0); assert (x != 0); y = y / x; return y; assert (y != 0); x = x / y; return x; □ y = y / x; return y; x = x / y; return x;
Reduce - loop Specification preserves invariant Loop body is either left or right mover Specification simulates zero or more iterations Specification is reflexive
Reduce - parallel Explicitly expand the parallel statement to its possible executions Reduces parallel composition to sequential composition
Experience • Implementation: Spec# BoogiePL QED • Generate verification condition (VC) for validity of each proof step • VC fed to the Z3 theorem prover • Purity benchmarks [Flanagan et.al, 2005] • Non-blocking algorithms • Obstruction-free deque [Herlihy et.al. 2003] • Non-blocking stack [Michael, 2004] • Bakery [Lamport, 1974] • Multiset • Conclusion: • Iteration of abstraction and reduction is powerful • Growing atomic code blocks useful approach 35
Multiset 2 9 3 5 3 8 6 8 3 elt vld 36 • Multiset data structure M = { 2, 3, 3, 9, 8, 8, 5 } • Represented by M[1..n] • elt: The element • vld: Is it in the set? LookUp (x) for i =1 to n acq (M[i]); if (M[i].elt==x && M[i].vld) rel (M[i]); return true; else rel (M[i]); return false; M
FindSlot and InsertPair InsertPair (x,y) i =FindSlot (x); if (i == -1) { return failure; } j =FindSlot (y); if (j == -1) { M[i].elt= null; return failure; } acq (M[i]) acq (M[j]) M[i].vld = true; M[j].vld = true; rel (M[i]); rel (M[j]); return success; FindSlot (x) r = -1; i = 0; while(i < N && r == -1) { acq (A[i]); if (M[i].elt ==null) { M[i].elt= x; rel (M[i]); r = i; } else { rel (M[i]); } i = i + 1; } return r;
Rewriting the “if” statement 38 FindSlot (x) r = -1; i = 0; while(i < N && r == -1) { acq (A[i]); assume (M[i].elt ==null); M[i].elt= x; rel (M[i]); r = i; acq (A[i]); assume (M[i] != null); rel (M[i]); i = i + 1; } return r; FindSlot (x) r = -1; i = 0; while(i < N && r == -1) { acq (A[i]); if (M[i].elt ==null) { M[i].elt= x; rel (M[i]); r = i; } else { rel (M[i]); } i = i + 1; } return r; □
mutex (M[i].lock == true) 39 FindSlot (x) r = -1; i = 0; while(i < N && r == -1) { acq (A[i]); assume (M[i].elt ==null); M[i].elt= x; rel (M[i]); r = i; acq (A[i]); assume (M[i] != null); rel (M[i]); i = i + 1; } return r; FindSlot (x) r = -1; i = 0; while(i < N && r == -1) { acq (A[i]); assume (M[i].elt ==null); M[i].elt= x; rel (M[i]); r = i; acq (A[i]); assume (M[i] != null); rel (M[i]); i = i + 1; } return r; REDUCE □ □
Read abstraction 40 FindSlot (x) r = -1; i = 0; while(i < N && r == -1) { acq (A[i]); assume (M[i].elt ==null); M[i].elt= x; rel (M[i]); r = i; acq (A[i]); skip rel (M[i]); i = i + 1; } return r; FindSlot (x) r = -1; i = 0; while(i < N && r == -1) { acq (A[i]); assume (M[i].elt ==null); M[i].elt= x; rel (M[i]); r = i; acq (A[i]); assume (M[i] != null); rel (M[i]); i = i + 1; } return r; □ □ SIMULATE
Rewriting 41 FindSlot (x) r = -1; i = 0; while(i < N && r == -1) { acq (A[i]); assume (M[i].elt ==null); M[i].elt= x; rel (M[i]); r = i; acq (A[i]); skip; rel (M[i]); i = i + 1; } return r; FindSlot (x) r = -1; i = 0; while(i < N && r == -1) { acq (A[i]); assume (M[i].elt ==null); M[i].elt= x; rel (M[i]); r = i; skip i = i + 1; } return r; □ □
Reducing loop 42 FindSlot (x) r = -1; i = 0; havoc i; assume (0 <= i < N); assume (M[i].elt == null); M[i].elt = x; r = i; skip; return r; FindSlot (x) r = -1; i = 0; while(i < N && r == -1) { acq (A[i]); assume (M[i].elt ==null); M[i].elt= x; rel (M[i]); r = i; skip i = i + 1; } return r; R REDUCE-LOOP □ □
Rewriting 43 FindSlot (x) i = 0; havoc i; assume (0 <= i < N); assume (M[i].elt == null); M[i].elt = x; return i; return -1; FindSlot (x) r = -1; i = 0; havoc i; assume (0 <= i < N); assume (M[i].elt == null); M[i].elt = x; r = i; skip; return r; □ □
FindSlot 44 FindSlot (x) i = 0; havoc i; assume (0 <= i < N); assume (M[i].elt == null); M[i].elt = x; return i; return -1; Invariant: (0 <= i < N): (M[i].elt == null) (M[i].vld == false) □
mutex (M[i].elt != null && M[i] == false) 45 InsertPair (x,y) i =FindSlot (x); if (i == -1) return failure; a[i] = tid; j =FindSlot (y); if (j == -1) M[i].elt= null; return failure; a[j] = tid; acq (M[i]) acq (M[j]) M[i].vld = true; M[j].vld = true; rel (M[i]); rel (M[j]); return success; InsertPair (x,y) i =FindSlot (x); if (i == -1) return failure; j =FindSlot (y); if (j == -1) M[i].elt= null; return failure; acq (M[i]) acq (M[j]) M[i].vld = true; M[j].vld = true; rel (M[i]); rel (M[j]); return success; R R SIMULATE
Reduce 46 InsertPair (x,y) i =FindSlot (x); if (i == -1) return failure; a[i] = tid; j =FindSlot (y); if (j == 0) M[i].elt= null; return failure; a[j] = tid; acq (M[i]) acq (M[j]) M[i].vld = true; M[j].vld = true; rel (M[i]); rel (M[j]); return success; InsertPair (x,y) i =FindSlot (x); if (i == -1) return failure; a[i] = tid; j =FindSlot (y); if (j == 0) M[i].elt= null; return failure; a[j] = tid; acq (M[i]) acq (M[j]) M[i].vld = true; M[j].vld = true; rel (M[i]); rel (M[j]); return success; R R REDUCE-SEQ.
Future work • Proof script templates for encoding synchronization idioms • Guidelines for adding annotation and code transformations • Mutual-exclusion, readers/writers lock, barriers • Barriers, event synchronization • Optimistic concurrency • Statically checking serializability of marked-atomic blocks • Verifying STM implementations using QED 47