460 likes | 722 Views
Generalizing Reduction and Abstraction to Simplify Concurrent Programs: The QED Approach. http://qed.codeplex.com. Shaz Qadeer Microsoft Research Redmond, WA. Serdar Taşıran , Tayfun Elmas , Ali Sezgin Koç University Istanbul, Turkey. QED: What is it good for?.
E N D
Generalizing Reduction and Abstraction to Simplify Concurrent Programs:The QED Approach http://qed.codeplex.com ShazQadeer Microsoft ResearchRedmond, WA Serdar Taşıran, TayfunElmas, Ali Sezgin Koç UniversityIstanbul, Turkey
QED: What is it good for? Read(X) Write(X) Write(Y) Undo(Y) Want to verify:- Aborted transactions do not modify their write-set - Y is not modified here. - Straightforward if code were sequential
QED: What is it good for? t = X havoc(t) Write(X) Write(Y) Undo(Y) Read(X) Want to verify:- Aborted transactions do not modify their write-set - Y is not modified here. - Straightforward if code were sequential
QED-verified examples • Fine-grained locking • Linked-list with hand-over-hand locking [Herlihy-Shavit 08] • Two-lock queue [Michael-Scott 96] • Non-blocking algorithms • Bakery [Lamport 74] • Non-blocking stack [Treiber86] • Obstruction-free deque [Herlihy et al. 03] • Non-blocking stack [Michael 04] • Writer mode of non-blocking readers/writer lock [Krieger et al. 93] • Non-blocking queue [Michael-Scott 96] • Synchronous queue [Scherer-Lea-Scott 06]
QED: Simplify (coarsen), then verify P1 P2 Pn . . . check Correct
Coarser Atomic Actions • Difficult to prove • Fine-grain concurrency • Annotations at every • interleaving point • Easy to prove • Larger atomic blocks • Local, sequential analysis • within atomic blocks P1 P2 Pn . . . check Correct
Example: Concurrent increment Main thread x := 0; assert x == 2; Thread A Thread B acquire(lock); t := x; t := t + 1; x := t; release(lock); acquire(lock); k := x; k := k + 1; x := k; release(lock); ||
Owicki-Gries proof, fine-grain actions Thread A Thread B x := 0; assert x == 2; { A@L0=>x=0, A@L5=>x=1 } { A@L0=>x=0, A@L5=>x=1, held(l,B) } { A@L0=>x=0, A@L5=>x=1, held(l,B), k=x } { A@L0=>x=0, A@L5=>x=1, held(l,B), k=x+1 } { A@L0=>x=1, A@L5=>x=2, held(l,B) } { A@L0=>x=1, A@L5=>x=2 } { B@L0=>x=0, B@L5=>x=1 } { B@L0=>x=0, B@L5=>x=1, held(l,A) } { B@L0=>x=0, B@L5=>x=1, held(l,A), t=x } { B@L0=>x=0, B@L5=>x=1, held(l,A), t=x+1 } { B@L0=>x=1, B@L5=>x=2, held(l,A) } { B@L0=>x=1, B@L5=>x=2 } L0: acquire(lock); L1: t := x; L2: t := t + 1; L3: x := t; L4: release(lock); L5:// end of thread L0: acquire(lock); L1: k := x; L2: k := k + 1; L3: x := k; L4: release(lock); L5:// end of thread ||
Reduction 9 inc (): acquire (lock); t := x; t := t + 1; x := t; release(lock); inc (): acquire (lock); t := x; t := t + 1; x := t; release(lock); Right mover Both mover B REDUCE-SEQUENTIAL B Left mover inc (): x := x + 1;
Soundness P1 P2 Pn . . . check Soundness theorem: IfPn is correct (satisfies all assertions) then 1. all P1 ≤ i ≤n are correct. 2. Pnpreserves behaviors of all P1 ≤ i ≤n . Completeness: Subsumes Owicki-Gries [Nieto, 2007] Correct
QED: Simplifier; complements other methods x := 0; assert x == 2; x := 0; assert x == 2; atomic { acquire(lock); t := x; t := t + 1; x := t; release(lock); } atomic { acquire(lock); k := x; k := k + 1; x := k; release(lock); } acquire(lock); t := x; t := t + 1; x := t; release(lock); acquire(lock); k := x; k := k + 1; x := k; release(lock); x := 0; x := x + 1; x := x + 1; assert x == 2; || || Owicki-Gries (12 location invariants) Simpler Owicki-Gries (4 location invariants) Sequential analysis Correct Correct Correct
QED-verifier http://qed.codeplex.com P1 P2 Pn ... P1 QEDPL program Correct Pn reduce abstract ..... reduce check Boogie 2, Z3 Proof script
Automation using SMT solver P1 P2 Pn . . . reduce abstract reduce VC VC VC Valid Valid Valid check Correct VC Valid
QED Transformations: Abstraction I,P I,P’ • P’ : Atomic statement [ S ] in P replaced with [ S’ ] • When? • When atomic statement [S’] abstracts statement [S]
QED’s Idea of Abstraction 15 abstracted by s1 If for all : s1 s1 1. If error error then s1 s2 s1 s2 2. If then s1 or error • Going wrong more often is sound for assertion checking
Flavors of Abstraction 16 Adding non-determinism if (x == 1) y := y + 1; if (*) y := y + 1; t := x; havoc t; “Read abstraction” assume x != t; skip; Addingassertions (more “wrong” behaviors) assert (lock_owner == tid); x := t + 1; x := t + 1;
QED Transformations: Reduction I,P I,P’ If [S1] and [S2] are actions of correct mover types P’ [ S1 ]; [ S2 ] [ S1 ] || [ S2 ] P [ S1; S2] [ S1 ; S2 ] P’ P
Reduction ; ; right-mover: ... 12 ... n ... For each execution: Exist equivalent executions: ... 1 2 ... n ... ........... ... 1 2 ... n ... ... 12 ... n ; ...
Use of movers in reduction atomic { acquire(lock); t := x; t := t + 1; x := t; release(lock); } acquire(lock); k := x; k := k + 1; x := k; release(lock); Right-mover Both-mover Both-mover Both-mover Left-mover reduce R B B B L E1: E2: acquire(lock) ... t := x ... ... t := t + 1; ... x := t ... release(lock) ... ... ... acquire(lock) t := x t := t + 1 release(lock) ... ... x := t E1 ≈ E2 Reason about only E2
Mover check in QED: Static, local, semantic A B S1 S2 S3 A Right-mover ? All actions in program run by different thread B A S1 T2 S3 ... For each : ... B ... ; ; A B B A First-order verification condition
Traditional use of reduction [Lipton, 1975] locked access release x x S1 S1 S2 S2 S3 S3 locked access release x x S1 S1 T2 T2 S3 S3 locked access acquire y y S1 S1 S2 S2 S3 S3 y y locked-access acquire S1 S1 T2 T2 S3 S3 Left-mover Right-mover Both-mover Both-mover
Static mover check • Static right-mover check between and : • Simple cases • Mover check passes: • and access different variables • and disable each other • Fails: • writes to a variable and reads it • and both write to a variable, writes do not commute
x release S1 S2 S3 release x S1 T2 S3 acquire y S1 S2 S3 y acquire S1 T2 S3 Reduction: Syntactic to Semantic Notions of Commuting • Accesses to independent variables • y := 2 and x := z + t; • Increment and increment • x := x + 1 and x := x + 2 • Acquire: Right mover • Commutes to the right of any action • But what aboutacq(L) acq(L) acq(L) acq(L) • Both LHS and RHS block • No execution has two consecutive acq(L)’s
Reduction: Normal to Weird Notions of Commuting • Lock protected accesses by two different threads • p q < q p • Why do they commute? • q is never followed by p • How is this captured in QED?
Static mover check fails: Apparent conflict 25 • Static mover check is local, fails! • Individual actions do not locally contain the information: • “Whenever this action executes, this thread holds the lock” • Annotate action with local assertion: • Express belief about non-interference acquire (lock); t1 := x; t1 := t1 + 1; x := t1; release(lock); acquire (lock); t2 := x; t2 := t2 + 1; x := t2; release(lock);
Auxiliary variable: Which thread holds the lock? 26 • Auxiliary variable a is a history variable • Summarizes relevant part of execution history New invariant: (lock == true) (a != 0) inc (): acquire (lock); a := tid; t2= x; t2= t2+ 1 x = t2; release(lock); a := 0; inc (): acquire (lock); t1= x; t1= t1+ 1 x = t1; release(lock); AUX-ANNOTATE
Annotating Actions with Assertions 27 Invariant: (lock == true) (a != 0) acquire (lock); a := tid; assert a == tid; t1= x; t1= t1+ 1 assert a == tid; x = t1; assert a == tid;release(lock); a := 0; acquire (lock); a := tid; t1= x; t1= t1+ 1 x = t1; release(lock); a := 0; ABSTRACT • Assertions indicate belief about non interference • Annotate actions locally with global information about execution
28 History Variable Annotations Make Static Mover Check Pass Thread 1 acquire (lock); a := tid1; assert a == tid1; t1 := x; t1 := t1 + 1 assert a == tid1; x := t1; assert a == tid1;release(lock); a := 0; Thread 2 acquire (lock); a := tid2; assert a == tid2; t2 := x; t2 := t2 + 1 assert a == tid2; x := t2; assert a == tid2;release(lock); a := 0; R B B B L • assert a == tid1; x := t1; andassert a == tid2; x := t2; commute • α β β α • Because both α β and β α result in assertion violations.
Borrowing and paying back assertions 29 Invariant: (lock == true) (a != 0) Discharges the assertions inc (): acquire (lock); a := tid; assert a == tid; t1= x; t1= t1+ 1 assert a == tid; x = t1; assert a == tid;release(lock); a := 0; inc (): acquire (lock); a := tid; assert a == tid;t1= x; t1= t1+ 1 assert a == tid;x = t1; assert a == tid;release(lock); a := 0; R B B B L REDUCE-SEQUENTIAL, DISCHARGE ASSERTIONS
Reduction: Syntactic to Semantic Notions of Commuting • What else commutes? • Actions that operate on different parts of memory • Different entries of a linked list • Actions on nodes not yet inserted into a data structure withactions already in the data structure • Currently thread local access with all actions • Assertions annotate action with reason for non-interference
31 Semantic Reduction: Ruling out Apparent Interference • possiblyInList[t] : • False when a newly created node assigned to t. • Set to true when p.next := t for some p. Remains true afterwards. assert possiblyInList[p2]; n2:= p2.next; assert !possiblyInList[t1]; t1.next := n1; assert !possiblyInList[t1]; t1.next := n1; assert possiblyInList[p2]; n2:= p2.next; • If p2 and t1 refer to the same node: • LHS and RHS lead to assertion violations (i.e., not possible) • Otherwise, no conflict.
class VersionedInteger { int v; int d; } VersionedInteger[] m; procedure Write(int a, int d) { atomic { m[a].d := d; m[a].v := m[a].v+1; } } procedure Snapshot(int a, int b, out bool s, out int da, out intdb) { intva, vb; atomic { va := m[a].v; da := m[a].d; } atomic { vb := m[b].v; db := m[b].d; } s := true; atomic { if (va < m[a].v) { s := false; } } atomic { if (vb < m[b].v) { s := false; } } }
class VersionedInteger { int v; int d; } VersionedInteger[] m; procedure Write(int a, int d) { atomic { m[a].d := d; m[a].v := m[a].v+1; } } procedure Snapshot(int a, int b, out bool s, out int da, out intdb) { intva, vb; atomic { havoc va, da; assume va <= m[a].v; if (va == m[a].v) { da := m[a].d; } } atomic { havoc vb, db; assume vb <= m[b].v; if (vb == m[b].v) { db := m[b].d; } } s := true; atomic { if (va < m[a].v) { s := false; } if (s) { havoc s; } } atomic { if (vb < m[b].v) { s := false; } if (s) { havoc s; } } } Right Mover Right Mover Left Mover Left Mover
class VersionedInteger { int v; int d; } VersionedInteger[] m; procedure Write(int a, int d) { atomic { m[a].d := d; m[a].v := m[a].v+1; } } procedure Snapshot(int a, int b, out bool s, out int da, out intdb) { intva, vb; atomic { havoc va, da; assume va <= m[a].v; if (va == m[a].v) { da := m[a].d; } havoc vb, db; assume vb <= m[b].v; if (vb == m[b].v) { db := m[b].d; } s := true; if (va < m[a].v) { s := false; } if (s) { havoc s; } if (vb < m[b].v) { s := false; } if (s) { havoc s; } } }
class VersionedInteger { int v; int d; } VersionedInteger[] m; procedure Write(int a, int d) { atomic { m[a].d := d; m[a].v := m[a].v+1; } } procedure Snapshot(int a, int b, out bool s, out int da, out intdb) { intva, vb; atomic { havoc va, da, vb, db, s; if (s) { va := m[a].v; da := m[a].d; vb := m[b].v; db := m[b].d; s := true; } } }
class VersionedInteger { int v; int d; } VersionedInteger[] m; procedure Write(int a, int d) { atomic { m[a].d := d; m[a].v := m[a].v+1; } } procedure Snapshot(int a, int b, out bool s, out int da, out intdb) { atomic { havoc da, db, s; if (s) { da := m[a].d; db := m[b].d; } } } Hide va, vb
38 Abstraction + Reduction: Increment with CAS t1 := x; s1 := CAS(x,t1,t1+1); t2 := x; s2 := CAS(x,t2,t2+1); || havoc t1; s1 := CAS(x,t1,t1+1); [ if (*) { s1:=false; } else { x:=x+1; s1:= true; } ]
QED-verified examples • Fine-grained locking • Linked-list with hand-over-hand locking [Herlihy-Shavit 08] • Two-lock queue [Michael-Scott 96] • Non-blocking algorithms • Bakery [Lamport 74] • Non-blocking stack [Treiber86] • Obstruction-free deque [Herlihy et al. 03] • Non-blocking stack [Michael 04] • Writer mode of non-blocking readers/writer lock [Krieger et al. 93] • Non-blocking queue [Michael-Scott 96] • Synchronous queue [Scherer-Lea-Scott 06]
QED and Optimistic Concurrency • tressa: Mechanism to annotate actions with assertions that can refer to prophecy variables (future) • assert: Discharged by reasoning about history of execution. • tressa: Temporal dual of assert • Example:y := y+1; z := z-1; assume (x == 0);
tressa : Temporal Dual of assert • Example:y := y+1; // x == 0 or execution blocksz := z-1; // x == 0 or execution blocks assume (x == 0); • But • atomic{ assert x == 0; y := y+1;} atomic{ assert x == 0; z := z-1;} assume (x == 0);does not work! • Cannot discharge the assertions!
tressa : Temporal Dual of assert • Example:y := y+1; // x == 0 or execution blocksz := z-1; // x == 0 or execution blocks assume (x == 0); • tressa φ: Either φ holds in the post state, or execution does not terminate (blocks). • atomic{ y := y+1; tressa x == 0;} atomic{ z := z-1; tressa x == 0;} assume (x == 0); • tressa annotations discharged by backwards reasoning within an atomic block. • Discharged tressaφ: You cannot come back from a final state of the program and violate φ
Discharging tressa’s inc (): int t; acquire (lock); p =: 0 tressa a == tid; t = x; t = t + 1 tressa a == tid; x = t; release(lock); p =: tid; inc (): int t; acquire (lock); p =: 0; tressa p == tid;t = x; t = t + 1 tressa a == tid;x = t; release(lock); p =: tid; R B B B L REDUCE & RELAX
Pair Snapshot Example: Write public void Write(int a, int d) { atomic{ m[a].d = d; // Write data m[a].v++; // Increment version number } }
if TrySnapshot ends with s == true TrySnapshot(inta, int b) { atomic{ va = m[a].v; da = m[a].d; } atomic{ vb = m[b].v; db = m[b].d; } s = true; atomic{ if (va!=m[a].v) s = false; } atomic{ if (vb!=m[b].v) s = false; } } a not written to (da,db) isa consistentsnapshot b not written to
Other Work on QED • Variable hiding • Linearizability-preserving soundness theorem • Annotation assistance: • Automating proofs for simple programs • Common synchronization idioms • Verifying parallelizing compilers