Transcript slides

Generalizing Reduction and
Abstraction to
Simplify Concurrent Programs:
The QED Approach
http://qed.codeplex.com
Serdar Taşıran, Tayfun Elmas, Ali Sezgin
Shaz Qadeer
Koç University
Istanbul, Turkey
Microsoft Research
Redmond, WA
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
2
QED: What is it good for?
t=X
havoc(t)
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
3
4
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]
QED: Simplify (coarsen), then verify
P1
P2
Pn
...
check
Correct
5
Coarser Atomic Actions
Difficult to prove
• Fine-grain concurrency
• Annotations at every
interleaving point
P1
Easy to prove
• Larger atomic blocks
• Local, sequential analysis
within atomic blocks
P2
Pn
...
check
Correct
6
Example: Concurrent increment
Main thread
Thread A
x := 0;
Thread B
acquire(lock);
acquire(lock);
t := x;
k := x;
t := t + 1;
||
k := k + 1;
x := t;
x := k;
release(lock);
release(lock);
assert x == 2;
7
Owicki-Gries proof, fine-grain actions
Thread A
x := 0;
{ B@L0=>x=0, B@L5=>x=1 }
Thread B
{ A@L0=>x=0, A@L5=>x=1 }
L0: acquire(lock);
L0: acquire(lock);
{ A@L0=>x=0, A@L5=>x=1, held(l,B) }
{ 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 }
L1: k := x;
{ A@L0=>x=0, A@L5=>x=1, held(l,B), k=x }
L2: t := t + 1;
L2: k := k + 1;
{ B@L0=>x=0, B@L5=>x=1, held(l,A), t=x+1 }
{ A@L0=>x=0, A@L5=>x=1, held(l,B), k=x+1 }
L3: x := t;
L3: x := k;
{ B@L0=>x=1, B@L5=>x=2, held(l,A) }
{ A@L0=>x=1, A@L5=>x=2, held(l,B) }
L4: release(lock);
L4: release(lock);
{ A@L0=>x=1, A@L5=>x=2 }
{ B@L0=>x=1, B@L5=>x=2 }
L5: // end of thread
assert x == 2;
L5: // end of thread
8
9
Reduction
inc ():
acquire (lock);
inc ():
Right mover
acquire (lock);
t := x;
Both mover
t := x;
t := t + 1;
B
t := t + 1;
x := t;
release(lock);
REDUCE-SEQUENTIAL
B
Left mover
x := t;
release(lock);
inc ():
x := x + 1;
9
Soundness
P1
P2
Pn
...
check
Soundness theorem:
If Pn is correct (satisfies all assertions)
then
1. all P1 ≤ i ≤ n are correct.
2. Pn preserves behaviors of all P1 ≤ i ≤ n .
Correct
Completeness: Subsumes Owicki-Gries [Nieto, 2007]
10
QED: Simplifier; complements other methods
x := 0;
x := 0;
acquire(lock);
acquire(lock);
t := x;
k := x;
t := t + 1;
||
atomic {
k := k + 1;
atomic {
acquire(lock);
acquire(lock);
t := x;
k := x;
||
t := t + 1;
k := k + 1;
x := t;
x := k;
x := t;
x := k;
release(lock);
release(lock);
release(lock);
release(lock);
assert x == 2;
}
x := 0;
x := x + 1;
x := x + 1;
assert x == 2;
}
assert x == 2;
Owicki-Gries
(12 location invariants)
Simpler Owicki-Gries
(4 location invariants)
Sequential
analysis
Correct
Correct
Correct
11
QED-verifier
http://qed.codeplex.com
P1
QEDPL
program
P2
Pn
...
P1
Correct
Pn
reduce
abstract
.....
reduce
check
Proof script
Boogie 2, Z3
12
Automation using SMT solver
P1
P2
reduce
VC
Valid
Pn
abstract
VC
Valid
...
reduce
VC
Valid
check
VC
Correct
Valid
13
14
QED Transformations: Abstraction
I,P
I,P’
• P’ : Atomic statement [ S ] in P
replaced with [ S’ ]
• When?
•When atomic statement [S’] abstracts statement [S]
15
QED’s Idea of Abstraction
abstracted by


If for all s1
:
1. If
s1

2. If
s1

error
then
s1
s2
then
s1
or
s1



error
s2
error
– Going wrong more often is sound for assertion checking
15
16
Flavors of Abstraction
Adding non-determinism
if (x == 1)
y := y + 1;
if (*)
y := y + 1;
t := x;
havoc t;
assume x != t;
“Read abstraction”
skip;
Adding assertions (more “wrong” behaviors)
x := t + 1;
assert (lock_owner == tid);
x := t + 1;
16
17
QED Transformations: Reduction
I,P
I,P’
If [S1] and [S2] are actions of correct mover types
P
P’
[ S1 ] ; [ S2 ]
[ S1; S2]
P
P’
[ S1 ] || [ S2 ]
[ S1 ; S2 ]
18
Reduction
 right-mover:
 ;   ;
For each execution:
...    1  2  ...  n    ...
Exist equivalent
executions:
...  1    2  ...  n    ...
...........
...  1  2  ...    n    ...
...  1  2  ...  n   ; 
18
 ...
Use of movers in reduction
atomic {
acquire(lock); Right-mover
k := x;
Both-mover
k := k + 1;
Both-mover
x := k;
Both-mover
acquire(lock);
t := x;
reduce
t := t + 1;
x := t;
release(lock);
release(lock); Left-mover
}
R
E1:
E2:
B
...
acquire(lock)
...
...
...
t := x
B
...
acquire(lock)
E1 ≈ E2
...
t := x
B
t := t + 1;
...
t := t + 1
x := t
x := t
L
...
release(lock)
release(lock)
...
...
 Reason about only E2
19
Mover check in QED: Static, local, semantic
A
Right-mover ?
All actions in program
run by different thread
S1
A
S1
B
S2
B
S3
T2
A
S3
...
For each
B
:
...
...
A
;
B
B
;
A
First-order verification condition
20
Traditional use of reduction [Lipton, 1975]
Left-mover
Right-mover
S1
S1
acquire
y
S2
T2
y
S3
acquire
S3
S1
S1
x
release
Both-mover
locked access
S1
S1
S2
y
y
S3
locked-access
T2
S3
S1
x
S2
T2
x
S3
S3
Both-mover
locked access
S2
locked access
S1
release
T2
S3
x
S3
21
22
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
22
Reduction: Syntactic to Semantic Notions of Commuting
• Accesses to independent variables
• y := 2 and x := z + t;
S1
acquire
S2
y
S3
• Increment and increment
• x := x + 1
and x := x + 2
• Acquire: Right mover
S1
y
T2
acquire
S3
• Commutes to the right of any action
• But what about
acq(L)  acq(L)
acq(L)  acq(L)
• Both LHS and RHS block
• No execution has
two consecutive acq(L)’s
S1
S1
x
release
S2
T2
release
S3
x
S3
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?
25
Static mover check fails: Apparent conflict
acquire (lock);
acquire (lock);
t1 := x;
t2 := x;
t1 := t1 + 1;
t2 := t2 + 1;
x := t1;
x := t2;
release(lock);
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
25
Auxiliary variable: Which thread holds the lock?
26
New invariant: (lock == true) (a != 0)
inc ():
inc ():
acquire (lock); a := tid;
acquire (lock);
t1 = x;
AUX-ANNOTATE
t2 = x;
t1 = t1 + 1
t2 = t2 + 1
x = t1;
x = t2;
release(lock);
release(lock); a := 0;
• Auxiliary variable a is a history variable
• Summarizes relevant part of execution history
26
Annotating Actions with Assertions
27
Invariant: (lock == true) (a != 0)
acquire (lock); a := tid;
acquire (lock); a := tid;
t1= x;
t1 = t1 + 1
x = t1;
release(lock); a := 0;
ABSTRACT
assert a == tid; t1 = x;
t1 = t1+ 1
assert a == tid; x = t1;
assert a == tid; release(lock); a := 0;
• Assertions indicate belief about non interference
• Annotate actions locally with global information about execution
27
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;
Thread 2
R
B
B
B
assert a == tid1; release(lock); a := 0; L
• assert a == tid1; x := t1;
and
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 == tid2; x := t2;
commute
• αβ
βα
• Because both α  β and β  α result in assertion violations.
28
Borrowing and paying back assertions
29
Invariant: (lock == true) (a != 0)
inc ():
inc ():
acquire (lock); a := tid;
assert a == tid; t1 = x;
t1 = t1 + 1
assert a == tid; x = t1;
R
B
acquire (lock); a := tid;
assert a == tid; t1 = x;
B
B
assert a == tid; release(lock); a := 0; L
Discharges
the assertions
t1 = t1 + 1
assert a == tid; x = t1;
assert a == tid; release(lock); a := 0;
REDUCE-SEQUENTIAL,
DISCHARGE ASSERTIONS
29
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
with actions 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[t1];
t1.next := n1;

assert possiblyInList[p2];
n2 := p2.next;
assert possiblyInList[p2];
n2 := p2.next;

assert !possiblyInList[t1];
t1.next := n1;
• If p2 and t1 refer to the same node:
• LHS and RHS lead to assertion violations (i.e., not possible)
• Otherwise, no conflict.
31
Atomic Snapshot
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 int db) {
int va, 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 int db) {
int va, 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 int db) {
int va, 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 int db) {
int va, 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 int db) {
atomic {
havoc da, db, s;
if (s) {
da := m[a].d;
Hide va, vb
db := m[b].d;
}
}
}
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;
}
]
38
39
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]
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);
40
tressa : Temporal Dual of assert
• Example:
y := y+1; // x == 0 or execution blocks
z := 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!
41
tressa : Temporal Dual of assert
• Example:
y := y+1; // x == 0 or execution blocks
z := 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 φ
42
Discharging tressa’s
inc ():
int t;
inc ():
int t;
acquire (lock); p =: 0
tressa a == tid; t = x;
t=t+1
R
B
acquire (lock); p =: 0;
tressa p == tid; t = x;
B
tressa a == tid; x = t;
B
release(lock); p =: tid;
L
t=t+1
tressa a == tid; x = t;
release(lock); p =: tid;
REDUCE & RELAX
43
Pair Snapshot Example: Write
public void Write(int a, int d)
{
atomic{
m[a].d = d;
// Write data
m[a].v++;
// Increment version number
}
}
44
if TrySnapshot ends with s == true
TrySnapshot(int a, int b) {
atomic{ va = m[a].v; da = m[a].d; }
atomic{ vb = m[b].v; db = m[b].d; }
s = true;
a not
written to
atomic{ if (va!=m[a].v)
s = false; }
atomic{ if (vb!=m[b].v)
s = false; }
b not
written to
(da,db) is
a consistent
snapshot
}
45
Other Work on QED
• Variable hiding
• Linearizability-preserving soundness theorem
• Annotation assistance:
• Automating proofs for simple programs
• Common synchronization idioms
• Verifying parallelizing compilers
46