54
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

Reduction, abstraction, and atomicity: How much can we prove about concurrent programs using them? Serdar Tasiran Koç University Istanbul, Turkey Tayfun

Embed Size (px)

Citation preview

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

Programs

Syntax: 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 S3

acquire y

S1 T2 S3

acquirey

S1 T2 S3

release x

S1 S2 S3

releasex

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

33

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 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] := x

chk(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