Reduction, abstraction, and atomicity:
How much can we prove about concurrent programs using them?
Serdar TasiranKoç University
Istanbul, Turkey
Tayfun Elmas Shaz Qadeer Ali Sezgin Koç University Microsoft Research Koç University
Istanbul, Turkey Redmond, WA Istanbul, Turkey
Outline
• QED proof system (Cartoon illustration)
• QED overview
• Half-baked part: • Backward reasoning in time• Prophecy variables, “tressa” annotations
2
2
3
Example: increment
acquire (lock);t1 := x;t1:= t1 + 1;x := t1;release (lock);
x := 0;
||
assert (x == 2);
acquire (lock);t2 := x;t2 := t2 + 1;x := t2;release (lock);
3
4
Proof with “fine grain” actions
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>
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>
||
4
5
QED Proof of Increment (Cartoon 1)
inc (): acquire (lock);
t1 := x;
t1 := t1 + 1;
x := t1;
release(lock);
Right mover
Both mover
B
B
Left mover
inc (): acquire (lock);
t1 := x;
t1 := t1 + 1;
x := t1;
release(lock);
inc (): x := x + 1;
REDUCE-SEQUENTIAL
5
6
QED Proof of “increment” (Cartoon 2)
Main:x := 0;
inc() || inc()
assert (x == 2)
Main:x := 0;
x := x + 1 || x := x + 1
assert (x == 2)
B B
INLINE-CALL REDUCE-PARALLEL
Main:x := 0;
x := x + 1;
x := x + 1;
assert (x == 2)
6
The QED approach
Soundness theorem: Starting from a state si in In
• P1 has an assertion violation Pn has an assertion violation
• P1 can go to final state sf Pn can go to final state sf or has an assertion violation.
Difficult to prove• Fine-grain concurrency• Annotations at every interleaving point
Easy to prove• Larger atomic blocks• Local, sequential analysis within atomic blocks
7
(P1, I1) ... (Pi, Ii) ... (Pn, In)
Program Invariant
7
Outline
• QED idea
• QED overview
• Half-baked part: • Backward reasoning in time• Prophecy variables, “tressa” annotations
8
8
ProgramsSyntax: Gated action
9
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 ]
Syntax in code examples:
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
x = x + 1;
Transition:Two-store relation
Gate:Assertion on pre-state
10
11
Gated actions – examples
assert (x != 0);y = y / x;
x = x + 1;assert (x != 0);y = y / x;
assume (x != 0);y = y / x;
Transition:Two-store relation
Gate:Assertion on pre-state
11
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)
12
Rule 1: Strengthen invariant
I,P I’,P
I’ I
• All statements in P must preserve I’.
13
Rule 2: Abstract program
I,P I,P’
• P’ : Atomic statement [ S ] in P replaced with [ S’ ]
• Atomic statement [S’] abstracts statement [S]
14
15
Abstracting Actions
If for all :
errors1 errors11. If then
s12. If thens2 s1 s2
or errors1
s1
– Going wrong more often is sound for assertion checking
abstracted by
15
16
Flavors of Abstraction
if (x == 1) y := y + 1;
if (*) y := y + 1;
Adding non-determinism
Adding assertions
t := x; havoc t;
assume x != t; skip;
assert (lock_owner == tid);x := t + 1;x := t + 1;
16
Rule 3: Reduce program
[ S1; S2]
[ S1 ; S2 ]
[ S1 ] ; [ S2 ]
[ S1 ] || [ S2 ]
I,P I,P’
17
S1 S2 S3acquire y
S1 T2 S3acquirey
S1 T2 S3release x
S1 S2 S3releasex
Right and left movers (Lipton 1975)18
19
Static mover check
• Right mover: Commutes to the right of any other actionrun by a different thread
• Static right-mover check for :......
......
......For every action in program:(run by different thread) .......
......
......
......
.......
......
......
......
.......
......
19
20
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
20
21
Reduction ;
... 1 2 ... n ; ...
right-mover:
For each execution:
Exist equivalent executions:
... 1 2 ... n ...
... 1 2 ... n ... ...........
... 1 2 ... n ...
;
21
22
Static mover check: a subtlety
• 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
s1 errors1
22
23
Increment: Proof by reduction
acquire (lock);
t1 := x;
t1 := t1 + 1;
x := t1;
release(lock);
R
B
B
B
L
acquire (lock);
t1 := x;
t1 := t1 + 1;
x := t1;
release(lock);
REDUCE-SEQUENTIAL
23
24
Static mover check fails: Apparent conflict
acquire (lock);
t1 := x;
t1 := t1 + 1;
x := t1;
release(lock);
acquire (lock);
t2 := x;
t2 := t2 + 1;
x := t2;
release(lock);
• 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
24
25
Auxiliary variable: Which thread holds the lock?
inc (): acquire (lock);
t1 = x;
t1 = t1 + 1
x = t1;
release(lock);
inc (): acquire (lock); a := tid;
t2 = x;
t2 = t2 + 1
x = t2;
release(lock); a := 0;
AUX-ANNOTATE
New invariant: (lock == true) (a != 0)
• Auxiliary variable a is a history variable• Summarizes relevant part of execution history
25
26
Annotating Actions with Assertions
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
Invariant: (lock == true) (a != 0)
• Assertions indicate belief about non interference• Annotate actions locally with global information about execution
26
History Variable Annotations Make Static Mover Check Pass
27
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;
R
B
B
B
L
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;
• assert a == tid1; x := t1; and assert a == tid2; x := t2; commute
• α β β α • Because both α β and β α result in assertion violations.
27
28
Borrowing and paying back 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;
REDUCE-SEQUENTIAL, DISCHARGE ASSERTIONS
R
B
B
B
L
Dischargesthe assertions
Invariant: (lock == true) (a != 0)
28
29
: Example: Ruling out apparent interference
assert !possiblyInList[t1];t1.next := n1;
assert possiblyInList[p2];n2 := p2.next;
• 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;
• p2 and t1 refer to the same node:
• LHS and RHS lead to assertion violations.
• Otherwise, no conflict.
29
Increment with CAS
30
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; } ]
30
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 [Treiber 86]• 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]
31
Outline
• QED proof system
• QED overview
• Half-baked part: • Backward reasoning in time• Prophecy variables, “tressa” annotations
32
33Static 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 Setprocedure 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; } }}
34
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;}
35
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;]}
36
chk(i,x):
Case 1: Lookup returns falsechk(0,x) chk(1,x) chk(2,x) . . .
chk(k-1,x)
. . .
chk(n-1,x)
37
• 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 truechk(0,x) chk(1,x) chk(2,x) . . .
chk(k-1,x)
q[k] := xchk(k,x)
38
• 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 Duplicationprocedure Lookup(x: data) returns found: bool;
{ [ found := false; i := 0; ]
while (*) { chkL(i,x);// Left // mover }
assume !found; [ return found;]}
39
{ [ found := false; i := 0; ]
while (*) { chkR(i,x);// Right // mover }
assume found; [ return found;]}
☐
Failing Lookup: chk(i,x) 40
assume(q[i]==-1); q[i]:= y;
found := (q[i] == x);
found := (q[i] == x);
is not simulated by
then
then
• 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;
chk(i,x)
chk(i,x)
Failing Lookup: chk(i,x)
{ [ found := false; i := 0; ]
while (*) { chkL(i,x);// Left // mover }
assume !found; [ return found;]}
41
– 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;]}
42
• 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.
43
… …
[ … tressa ]
All future events refers to have happened.
s…
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 β ? α β β α
44
44
Failing Lookup
{ [ found := false; i := 0; ]
while (*) { chkL(i,x);// Left // mover }
assume !found; [ return found;]}
45
{ [ found := false; i := 0; ]
while (*) { [ chkL(i,x); tressa(!found);] }
assume !found; [ return found;]}
Failing Lookup46
q[i]:= y;
found :=(q[i] == x);tressa !found
q[i]:= y;simulated by
then
thenfound :=(q[i] == x);tressa !found
• 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!
s1 s2
q[i]:= y;
found :=(q[i] == x);tressa !found
s3
Discharging tressa’s: Backwards Reasoning
47
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”
48
49
Prophecy Variables
acquire (lock); p =: 0
t = x;
t = t + 1
x = t;
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
50
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
Prophecy Variables• Prophecy variable: Auxiliary variable, encodes future non-determinism
– Allows actions to refer to future locally– Can use in annotations, abstraction.
• Different reduction proofs for different futures• Concurrent systems: Non-determinism due to thread interleaving
51
p = R, G or B
Prophecy Variable Introduction: Soundness52
– Annotating action α(s,s’) with prophecy variable p
α(s,s’) becomes β(s,p, s’,p’) – Must satisfy
• p’. p. β(s,p, s’,p’) (History variables: h. h’. β(s,h, s’,h’) )• Backwards assignment satisfies this
– Soundness:• Every state of every execution can be annotated with a value of p.
τ
tressas as partial specificationsReadPair(a: int, b: int) returns (s: bool, da: Obj, db: Obj){
var va: int, vb: int;
[va := m[a].v; da := m[a].d; ] [vb := m[b].v; db := m[b].d; ] s := true; tressa ( exit(s) ==> da == m[a].d && db == m[b].d) ); [ if (va < m[a].v) { s:= false; } ] [ if (vb < m[b].v) { s:= false; } ]
} // exit(s) =: s when procedure returns.
procedure Write(a: int, d: Obj){ [ m[a].d := d; m[a].v := m[a].v+1; ]}
53
tressas + prophecy variables + abstraction
ReadPair(a: int, b: int) returns (s: bool, da: Obj, db: Obj){
var va: int, vb: int;
[va := m[a].v; da := m[a].d; if (exit(s)) havoc va, da; tressa ( exit(s) ==> va == m[a].v ); ]
[vb := m[b].v; db := m[b].d; if (exit(s)) havoc vb, db; tressa ( exit(s) ==> vb == m[b].v ); ]
s := true; [ if (va < m[a].v) { s:= false; } ] [ if (vb < m[b].v) { s:= false; } ]
} // exit(s) =: s when procedure returns.
54