540 likes | 773 Views
Reduction, abstraction, and atomicity: How much can we prove about concurrent programs using them?. Serdar Tasiran Koç University Istanbul, Turkey Tayfun Elmas Shaz Qadeer Ali Sezgin Koç University Microsoft Research Koç University Istanbul , Turkey Redmond, WA Istanbul, Turkey. 2.
E N D
Reduction, abstraction, and atomicity: How much can we prove about concurrent programs using them? SerdarTasiran Koç University Istanbul, Turkey TayfunElmasShaz Qadeer Ali SezginKoç UniversityMicrosoft ResearchKoç University Istanbul, Turkey Redmond, WAIstanbul, Turkey
2 Outline • QED proof system (Cartoon illustration) • QED overview • Half-baked part: • Backward reasoning in time • Prophecy variables, “tressa” annotations
Example: increment 3 x := 0; || assert (x == 2); acquire(lock); t1 := x; t1:= t1 + 1;x := t1; release (lock); acquire(lock); t2 := x; t2 := t2 + 1;x := t2; release (lock);
Proof with “fine grain” actions 4 B: <A@L0=>x=0, A@L5=>x=1> L0:acquire(l); <A@L0=>x=0, A@L5=>x=1, held(l,B)> L1:t2 := x; <A@L0=>x=0, A@L5=>x=1, held(l,B), t2=x> L2:t2 := t2 + 1; <A@L0=>x=0, A@L5=>x=1, held(l,B), t2=x+1> L3:x := t2; <A@L0=>x=1, A@L5=>x=2, held(l,B)> L4:release(l) <A@L0=>x=1, A@L5=>x=2> || A: <B@L0=>x=0, B@L5=>x=1> L0: acquire(l); <B@L0=>x=0, B@L5=>x=1, held(l,A)> L1: t1 := x; <B@L0=>x=0, B@L5=>x=1, held(l,A), t1=x> L2: t1 := t1 + 1; <B@L0=>x=0, B@L5=>x=1, held(l,A), t1=x+1> L3: x := t1; <B@L0=>x=1, B@L5=>x=2, held(l,A)> L4: release(l) <B@L0=>x=1, B@L5=>x=2>
QED Proof of Increment (Cartoon 1) 5 inc (): acquire (lock); t1 := x; t1 := t1 + 1; x := t1; release(lock); inc (): acquire (lock); t1 := x; t1 := t1 + 1; x := t1; release(lock); Right mover Both mover B REDUCE-SEQUENTIAL B Left mover inc (): x := x + 1;
QED Proof of “increment” (Cartoon 2) 6 Main: x := 0; inc() || inc() assert (x == 2) Main: x := 0; x := x + 1; x := x + 1; assert (x == 2) Main: x := 0; x := x + 1 || x := x + 1 assert (x == 2) B B REDUCE-PARALLEL INLINE-CALL
The QED approach Soundness theorem: Starting from a state siin In • P1has an assertion violationPnhas an assertion violation • P1 can go to final state sf Pn can go to final state sf or has an assertion violation. Invariant Program (P1, I1) ... (Pi, Ii) ... (Pn, In) • Difficult to prove • Fine-grain concurrency • Annotations at every • interleaving point • Easy to prove • Larger atomic blocks • Local, sequential analysis • within atomic blocks
8 Outline • QED idea • QED overview • Half-baked part: • Backward reasoning in time • Prophecy variables, “tressa” annotations
9 Programs Gated action Syntax: Syntax in code examples: S ::= assume e | assert e | x := e | havoc x | S ; S | if (e) then S else S | while (e) do S | proc(a, out b) | S || S | [ S ] Semantics: • A collection of threads and a global store • Non-deterministically pick a thread and execute one atomic step • Failed assert makes the thread and program go wrong • A distinguished state “error” • Failed assume blocks the executing thread
10 Gated actions Gate: Assertion on pre-state Transition: Two-store relation x = x + 1;
11 Gated actions – examples Gate: Assertion on pre-state Transition: Two-store relation assume (x != 0); y = y / x; assert (x != 0); y = y / x; x = x + 1; assert (x != 0); y = y / x;
12 Verifying the program • Proof succeeds when all executions of starting from states in satisfy all assertions. • Sufficient condition: For all actions in the program, • Actions “large enough” to establish assertions within themselves x := 0; x := x + 1; x := x + 1; assert (x == 2)
Rule 1: Strengthen invariant I,P I’,P I’ I • All statements in P must preserve I’.
Rule 2: Abstract program I,P I,P’ • P’ : Atomic statement[ S ] in P replaced with [ S’ ] • Atomic statement [S’] abstracts statement [S]
Abstracting Actions 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; assume x != t; skip; Addingassertions assert (lock_owner == tid); x := t + 1; x := t + 1;
Rule 3: Reduce program I,P I,P’ [ S1 ]; [ S2 ] [ S1 ] || [ S2 ] [ S1; S2] [ S1 ; S2 ]
x release S1 S2 S3 release x S1 T2 S3 acquire y S1 S2 S3 y acquire S1 T2 S3 Right and left movers (Lipton 1975)
Static mover check ...... ...... For every action in program: (run by different thread) ...... ....... ...... ...... ...... ...... ....... ...... ...... ...... ....... Right mover: Commutes to the right of any other action run by a different thread Static right-mover check for :
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
Reduction ; ; right-mover: ... 12 ... n ... For each execution: Exist equivalent executions: ... 1 2 ... n ... ........... ... 1 2 ... n ... ... 12 ... n ; ...
Static mover check: a subtlety s1 s1 error Static right-mover check between and : Consider such that No execution reaching s1 executes followed by Do not need to do mover check for state pairs starting with s1
Increment: Proof by reduction 23 acquire (lock); t1:= x; t1:= t1+ 1; x := t1; release(lock); acquire (lock); t1:= x; t1:= t1+ 1; x := t1; release(lock); R B REDUCE-SEQUENTIAL B B L
Static mover check fails: Apparent conflict 24 • 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? 25 • 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 26 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
27 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 28 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
29 : Example: 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; • p2 and t1 refer to the same node: • LHS and RHS lead to assertion violations. • Otherwise, no conflict.
30 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]
Outline • QED proof system • QED overview • Half-baked part: • Backward reasoning in time • Prophecy variables, “tressa” annotations
Static Reduction Proofs and the Future • Challenge in some QED proofs • Different reduction proof needed for different execution futures • Example: Optimistic concurrency • Proceed assuming non-interference • Abort, undo and/or retry if interference detected • Prophecy variables and backwards reasoning in QED
Example: Funny Set procedure Insert(x: data) returns success: bool; { havoc i; assume 0<=i<n; // Start from arbitrary // array slot cnt := 0; success := false; while ( cnt<n && !success) { if (q[i]==-1) { q[i] := x; success := true; } else if (q[i]== x) { success := true; } else { i := (i+1) mod n; cnt := cnt+1; } } }
Set Lookup procedure Lookup(x: data) returns found: bool; { found := false; i := 0; while (i<n && !found) { found := (q[i] == x); i := i+1; } return found; }
Set Lookup procedure Lookup(x: data) returns found: bool; { [ found := false; i := 0; ] while (*) { [ assume(i<n && !found); found := (q[i] == x); i := i+1; ] } [ return found;] } chk(i,x):
Case 1: Lookup returns false chk(0,x) chk(1,x) chk(2,x) . . . chk(k-1,x) . . . chk(n-1,x) • chk(i,x): no x in slot q[i] • Intuition: • chk(i,x)should be a left mover for all i. • q[i]:= x may come after chk(i,x) • So, chk(i,x) not a right mover. • No q[i]:= x can come before chk(i,x) • Looks like a left mover.
Case 2: Lookup returns true chk(0,x) chk(1,x) chk(2,x) . . . chk(k-1,x) q[k] := x chk(k,x) • chk(k,x): Slot q[i] has x • Cannot be a left-mover • Does not commute to the left ofq[i]:= x • Is a right mover (no deletes) • Need all earlier chk(i,x)to be right movers. • Dilemma: • What is the mover type of chk(i,x) ?
Code Duplication procedure Lookup(x: data) returns found: bool; { [ found := false; i := 0; ] while (*) { chkL(i,x);// Left // mover } assume !found; [ return found;] } { [ found := false; i := 0; ] while (*) { chkR(i,x);// Right • // mover } assume found; [ return found;] } ☐
Failing Lookup: chk(i,x) chk(i,x) • From an initial state with q[i] == -1 and y == x • LHS yields found == true • RHS yields found == false • chkL(i,x):is not a left mover. • Annotate chkL to say “I am part of a failing execution of Lookup.” found := (q[i] == x); assume(q[i]==-1); q[i]:= y; then is not simulated by found := (q[i] == x); assume(q[i]==-1); q[i]:= y; then chk(i,x) found := (q[i] == x);
Failing Lookup: chk(i,x) { [ found := false; i := 0; ] while (*) { chkL(i,x);// Left // mover }assume !found; [ return found;] } • We would like to say “This copy of the action only occurs in executions in which found is false.” • [chk(i,x); assert !found;]does not work. • Cannot discharge assertion. • Prefix of execution does not guarantee !found.
tressa: Temporal dual of assert { [ found := false; i := 0; ] while (*) { chkL(i,x);// Left // mover }assume !found; [ return found;] } • Postfix of execution justifies!found • Code split has produced artificial executions that block when they get to assume!found • Annotate chkL(i,x)to say: • “Unless !found is true, this is an artificial execution that blocks before Lookup returns.” • [ chkL(i,x); • tressa(!found);]
tressa(pictorial) semantics • Tressa violation in this execution fragment if (s) is false. All future events refers to have happened. [ …tressa ] … … … s
44 Abstraction and Mover Checks with tressa’s [assert a1; τ1; tressa p1] [assert a2; τ2; tressa p2] • Preserve assert violations: a2 a1 • Preserve tressa violations: p2 p1 • Forward simulate or replace with assert violation: τ1 (s,s’) τ2 (s,s’) ∨ a2(s) • Backward simulate orreplace with tressa violation: τ1 (s,s’) τ2 (s,s’) ∨ p2(s’) • Does α commute to the right of β ? α β β α
Failing Lookup { [ found := false; i := 0; ] while (*) { [ chkL(i,x); tressa(!found);] }assume !found; [ return found;] } { [ found := false; i := 0; ] while (*) { chkL(i,x);// Left // mover }assume !found; [ return found;] }
Failing Lookup • In mover checks, can ignore scenarios where, on the LHSthe tressa is violated • Only worry about (s1,s3) if s3 satisfies !found • Then x != y • Simulation holds! found :=(q[i] == x);tressa !found q[i]:= y; then found :=(q[i] == x);tressa !found simulated by then q[i]:= y; found :=(q[i] == x);tressa !found q[i]:= y; s1 s2 s3
Discharging tressa’s: Backwards Reasoning while (*) { [ chkL(i,x); tressa(!found);] } assume !found; • found := • \Exists i: 0<=i<n && q[i]==x; • tressa(!found); • assume !found; • tressa’s discharged by backwards reasoning within an atomic block.
Discharging tressa’s: Backwards Reasoning • assume’s are like assignments in the reverse direction in time: • [havoc x; x := 2;] • A set of transitions (x, x’) where x is arbitrary and x’ is 2. • [assume x == 2; havoc x;] • A set of transitions (x, x’) where x’ is arbitrary and x is 2. • Denoted x =: 2 • “Reverse assignment”
Prophecy Variables acquire (lock); p =: 0 t = x; tressa p == tid; t = t + 1 x = t; tressa p == tid; release(lock); p =: tid; acquire (lock); p =: 0 t = x; t = t + 1 x = t; release(lock); p =: tid; R B B B L
Prophecy variables and tressas acquire (lock); p =: 0 t = x; tressa p == tid; t = t + 1 x = t; tressa p == tid; release(lock); p =: tid; acquire (lock); p =: 0; t = x; tressa p == tid; t = t + 1 x = t; tressa p == tid; release(lock); p =: tid; R B B B L