500 likes | 622 Vues
This paper presents the QED approach for simplifying concurrent programs by leveraging reduction and abstraction techniques. It discusses how coarser atomic actions can be used to simplify verification processes while maintaining correctness. By minimizing fine-grain concurrency through larger atomic blocks and local sequential analysis, the authors demonstrate a method for easier proofs of program correctness. Examples and the soundness of the approach are illustrated, laying a foundation for efficient concurrent program verification strategies.
E N D
Generalizing Reduction and Abstraction to Simplify Concurrent Programs:The QED Approach http://qed.codeplex.com [POPL ’09, TACAS ‘10, PADTAD ‘10, VSTTE ‘10] ShazQadeer Microsoft ResearchRedmond, WA Serdar Taşıran, TayfunElmas, Ali Sezgin Koç UniversityIstanbul, Turkey
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 6 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
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
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 19 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 20 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 29 • 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? 30 • 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 31 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
32 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 33 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
35 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
42 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 • Handling relaxed memory models [Ongoing]