Presentation is loading. Please wait.

Presentation is loading. Please wait.

Simplifying Linearizability Proofs Using Reduction and Abstraction Serdar Tasiran Koc University, Istanbul, Turkey Tayfun Elmas, Ali Sezgin, Omer Subasi.

Similar presentations


Presentation on theme: "Simplifying Linearizability Proofs Using Reduction and Abstraction Serdar Tasiran Koc University, Istanbul, Turkey Tayfun Elmas, Ali Sezgin, Omer Subasi."— Presentation transcript:

1 Simplifying Linearizability Proofs Using Reduction and Abstraction Serdar Tasiran Koc University, Istanbul, Turkey Tayfun Elmas, Ali Sezgin, Omer Subasi (Koc University) Shaz Qadeer (Microsoft Research)

2 The QED Proof System Assertions difficult to verify Fine-grain concurrency Annotations at every interleaving point Assertions easy to verify Larger atomic blocks Local, sequential analysis within atomic blocks 2 2... Impl 2 Impl 1 Impl n Original implementation Transformed implementation reduce abstract

3 Overview Proof system for proving linearizability, refinement – Builds on QED: Proof system for verifying assertions [WG 2.3 Mtg. 50, POPL’09] New proof rule: Variable hiding – Implementation in QED-verifier http://qed.codeplex.comhttp://qed.codeplex.com Demonstrated on – Lock-coupling linked list [Vafeiadis, PPoPP’06] – Treiber’s non-blocking stack [Treiber, 1986] – Non-blocking and two-lock queues [Michael, Scott, PODC’96] – Writer mode of readers/writer lock [Krieger et.al., ICPP’93] 3

4 The multiset data structure Collection of integer with multiplicities: {2, 2, 3, 4, 4, 4, 5} Operations – LookUp(x: int) returns (r: bool) If x is in multiset then r == true, otherwise r == false – InsertPair(x: int, y: int) returns (r: bool) Inserts both x and y and returns r == true Inserts nothing and returns r == false Verification goal: Show that a concurrent implementation of multiset is linearizable to a sequential specification of multiset. 4

5 Multiset specification var Spec: map[int -> int]; // Spec[x] == how many x’s? atomic LOOKUP(x:int) returns (r:bool) { r := (Spec[x] > 0); } atomic INSERTPAIR(x:int, y:int) returns (r:bool) { r := nonDetBool(); if (r == true) { Spec[x] := Spec[x] + 1; Spec[y] := Spec[y] + 1; } 5

6 Multiset implementation 6 LookUp(x:int) returns(r:bool) r := false; for each slot Impl[i] { lock(Impl[i]); r := (Impl[i].elt == x && Impl[i].stt == full); unlock(Impl[i]); if (r == true) return; } Specification: atomic LOOKUP(x:int) returns (r:bool) { r := (Spec[x] > 0); } 4 full - empty 9 full 4 - reserved 3 full 9 reserved var Impl: array[int] of Slot; elt stt {4, 9, 4, 3}

7 7 InsertPair(x:int, y:int) returns (r:bool) i := FindSlot(); j := FindSlot(); Impl[i].elt := x; Impl[j].elt := y; lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true; 1 2 3 4 5 6 7 8 9 10 1. Reserve slots 2. Insert numbers 3. Insert logically - reserved... - reserved - empty... - empty y reserved... x reserved y full... x full i j i j i j i j

8 Multiset implementation 8 FindSlot(x:int) returns(k:int) k := -1; for each slot Impl[i] { lock(Impl[i]); if (Impl[i].stt == empty) Impl[i].stt = reserved; k := i; return; unlock(Impl[i]); } 4 full - empty 9 full 4 - reserved 3 full 9 reserved var Impl: array[int] of Slot; elt stt {4, 9, 4, 3}

9 9 InsertPair(x:int, y:int) returns(r:bool) i := FindSlot(); if (i == -1) { r := false; return;} j := FindSlot(); if (j == -1) {Impl[i].stt := empty; r: false; return;} Impl[i].elt := x; Impl[j].elt := y; lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true;

10 Linearizability: Definition 10 A concurrent history call InsertPair(3,5) InsertPair(3,5) returns true call LookUp(5) call LookUp(4) LookUp(4) returns true LookUp(5) returns false Time

11 Linearizability 11 A sequential history, consistent with sequential specification for data structure call InsertPair(3,5) InsertPair(3,5) returns true call LookUp(5) call LookUp(4) LookUp(4) returns true LookUp(5) returns false

12 Linearizability 12 A concurrent history call InsertPair(3,5) InsertPair(3,5) returns true call LookUp(5) call LookUp(4) LookUp(4) returns true LookUp(5) returns false Time

13 Linearizability 13 Concurrent implementation history call InsertPair(3,5) InsertPair(3,5) returns true call LookUp(5) call LookUp(4) LookUp(4) returns true LookUp(5) returns false call InsertPair(3,5) InsertPair(3,5) returns true call LookUp(5) call LookUp(4) LookUp(4) returns true LookUp(5) returns false ≈ Sequential specification history

14 Linearizability 14 Concurrent implementation history call InsertPair(3,5) InsertPair(3,5) returns true call LookUp(5) call LookUp(4) LookUp(4) returns true LookUp(5) returns false call InsertPair(3,5) InsertPair(3,5) returns true call LookUp(5) call LookUp(4) LookUp(4) returns true LookUp(5) returns false ≈ Sequential specification history Equivalence: -Per-thread order of actions preserved -Order of non-overlapping operations preserved -Same call and return values E

15 15 s0s0 s1s1 s2s2 s3s3 Abs s4s4 s5s5 s6s6 s7s7 s8s8 s9s9 q 0 = Abs(s 0 ) q 1 = Abs(s 3 ) q 1 = Abs(s 4 ) q0q0 q 0 = Abs(s 2 ) Abs q 2 = Abs(s 5 ) q 2 = Abs(s 7 ) q2q2 q 3 = Abs(s 8 ) q3q3 For concurrent data structures, model the implementation and spec as state transition systems Spec: A data structure with atomic operations An abstraction function Abs maps each implementation state to a spec state Refinement: For each execution of the implementation its image under Abs is an execution of the specification Refinement between two state-transition systems

16 16 s0s0 s1s1 s2s2 s3s3 s4s4 s5s5 s6s6 s7s7 s8s8 s9s9 q 0 = Abs(s 0 ) q 1 = Abs(s 3 ) q 1 = Abs(s 4 ) q0q0 q 0 = Abs(s 2 ) q 2 = Abs(s 5 ) q 2 = Abs(s 7 ) q2q2 q 3 = Abs(s 8 ) q3q3 Linearizability can be formulated as a special case of refinement – A set V of variables store operation arguments, return values – Abs leaves variables in V unchanged For each implementation execution E I, there is a spec execution E S such that – The projections of E I and E S onto V are the same If all spec operations are atomic, this leads to a linearizability proof. Abs

17 Proving refinement between two transition systems Revised goal: Prove refinement – Method 1: Pick arbitrary execution of implementation, use abstraction map, prove that mapped execution is allowed by spec – Method 2: Work on implementation and spec program text Prove refinement relation using proof system (QED + new rules) Fine-grain atomic actions in implementation make both methods hard Next: – Illustrate this difficulty on multiset Rest of talk: – Address this difficulty 17

18 Refinement proof difficulties Reasonable first guess at abstraction map Abst: Impl  Spec x. Spec[x] == |{ k | 0 <= k < N && Impl[k].elt == x && Impl[k].stt == full }| Abstraction map should update spec state once per operation instance – The “commit action” of the operation 18

19 Refinement proof cartoon 19 call INSERTPAIR(4,5) returns true call INSERTPAIR(4,5) returns true call LOOKUP(5) returns false call LOOKUP(5) returns false call LOOKUP(4) returns true Implementation trace Specification trace ≈ call InsertPair(3,5) InsertPair(3,5) returns true call LookUp(5) call LookUp(4) LookUp(4) returns true LookUp(5) returns false commit Time

20 20 Commit action: InsertPair(x:int, y:int) returns (r:bool) i := FindSlot(); j := FindSlot(); Impl[i].elt := x; Impl[j].elt := y; lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true; 1 2 3 4 5 6 7 8 9 10 Proof obligations: 1. Non-commit actions do not change abstract state Abst(Impl after ) == Abst(Impl before ) 2. Commit action updates abstract state atomically (According to INSERTPAIR specification) Abst(Impl after )[x] == Abst(Impl before )[x] + 1 Abst(Impl after )[y] == Abst(Impl before )[y] + 1

21 21 Abstraction function attempt: Spec[x] == |{ k | 0 <= k < N && Impl[k].elt == x && Impl[k].stt == full }| Spec[x] incremented: Spec[y] incremented: InsertPair(x:int, y:int) returns (r:bool) i := FindSlot(); j := FindSlot(); Impl[i].elt := x; Impl[j].elt := y; lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true; 1 2 3 4 5 6 7 8 9 10 Not good! Abstract state updated more than once.

22 22 Abstraction function: Spec[x] == |{ k | 0 <= k < N && Impl[k].elt == x && Impl[k].stt == full && !Locked(Impl[k]) }| Spec[x] incremented BUT NOT Spec[y] ! Spec[x] NOT incremented : Spec[y] NOT incremented : InsertPair(x:int, y:int) returns (r:bool) i := FindSlot(); j := FindSlot(); Impl[i].elt := x; Impl[j].elt := y; lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true; 1 2 3 4 5 6 7 8 9 10

23 23 Abstraction function: Spec[x] == |{ k | 0 <= k < N && Impl[k].elt == x && Impl[k].stt == full && (!Locked(Impl[k]) || LockerAtLine(Impl[k],10)) }| Spec[x] and Spec[y] incremented atomically InsertPair(x:int, y:int) returns (r:bool) i := FindSlot(); j := FindSlot(); Impl[i].elt := x; Impl[j].elt := y; lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true; 1 2 3 4 5 6 7 8 9 10

24 24 Abstraction function: Spec[x] == |{ k | 0 <= k < N && Impl[k].elt == x && Impl[k].stt == full && (!Locked(Impl[k]) || LockerAtLine(Impl[k],10)) }| InsertPair(x:int, y:int) returns (r:bool) i := FindSlot(); j := FindSlot(); Impl[i].elt := x; Impl[j].elt := y; lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true; 1 2 3 4 5 6 7 8 9 10 Messy! Abstraction map refers to program counter and locking state of every thread

25 25 InsertPair(x:int, y:int) returns (r:bool) i := FindSlot(); j := FindSlot(); Impl[i].elt := x; Impl[j].elt := y; atomic { lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true; } 1 2 3 4 Abstraction function: Spec[x] == |{ k | 0 <= k < N && Impl[k].elt == x && Impl[k].stt == full && (!Locked(Impl[k]) || LockerAtLine(Impl[k],10)) }| Commit action: Spec[x] and Spec[y] both incremented

26 QED Proof System: Reduction and Abstraction 26... Impl 2 Impl 1 Impl n Difficult to prove Fine-grained concurrency  Complicated abstraction map Concurrency control + data Complete/rollback partial actions Easy to prove Coarse-grained concurrency  Clean abstraction map Separates data from concurrency No need to consider partial actions Original implementation Transformed implementation reduce abstract

27 27 InsertPair(x:int, y:int) returns (r:bool) atomic { i := FindSlot(); j := FindSlot(); Impl[i].elt := x; Impl[j].elt := y; lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true; } InsertPair(x:int, y:int) returns (r:bool) i := FindSlot(); j := FindSlot(); Impl[i].elt := x; Impl[j].elt := y; atomic { lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true; } reduction + abstraction

28 28 InsertPair(x:int, y:int) returns (r:bool) atomic { i := FindSlot(); j := FindSlot(); Impl[i].elt := x; Impl[j].elt := y; lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true; } introduce Spec InsertPair(x:int, y:int) returns (r:bool) atomic { i := FindSlot(); j := FindSlot(); Impl[i].elt := x; Impl[j].elt := y; lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true; Spec[x] := Spec[x] + 1; Spec[y] := Spec[y] + 1; } code modifying new variable

29 29 add invariant InsertPair(x:int, y:int) returns (r:bool) atomic { i := FindSlot(); j := FindSlot(); Impl[i].elt := x; Impl[j].elt := y; lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true; Spec[x] := Spec[x] + 1; Spec[y] := Spec[y] + 1; } forall x. Spec[x] == |{ k | 0 <= k < N && Impl[k].elt == x && Impl[k].stt == full }|

30 30 hide Impl InsertPair(x:int, y:int) returns (r:bool) atomic { i := FindSlot(); j := FindSlot(); Impl[i].elt := x; Impl[j].elt := y; lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true; Spec[x] := Spec[x] + 1; Spec[y] := Spec[y] + 1; } InsertPair(x:int, y:int) returns (r:bool) atomic { r := true; Spec[x] := Spec[x] + 1; Spec[y] := Spec[y] + 1; } LookUp(x:int) returns (r:bool) atomic { if(Spec[x] > 0) r := true; else r := false; }

31 QED + Variable Hiding Proof Rule + New Soundness Theorem 31... Impl 2 Impl 1 Spec If Spec satisfies all assertions then [POPL’09]: Impl 1 satisfies all assertions [TACAS’10]: Impl 1 is linearizable to Spec Original implementationSpecification reduce abstract

32 Programs Syntax: Gated action 32 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

33 33 Gated actions x = x + 1; Transition: Two-store relation Gate: Assertion on pre-state 33

34 34 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 34

35 35 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; assert (x == 2) 35

36 Rule 1: Strengthen invariant 36

37 Rule 2: Abstraction 37 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;

38 S1S1 S2S2 S3S3 acquirey S1S1 T2T2 S3S3 y S1S1 T2T2 S3S3 releasex S1S1 S2S2 S3S3 x Right and left movers (Lipton 1975) 38

39 Rule 3: Reduction 39

40 40 Reduction  ;  ;  ...   1   2 ...   n   ;  ...  right-mover: For each execution: Exist equivalent executions:...     1   2 ...   n   ......   1     2 ...   n   .................   1   2 ...     n   ...  ;  40

41 41 Static mover check Right mover: Commutes to the right of any other action run by a different thread Static right-mover check for  :...... For every action  in program: (run by different thread).......................................     41

42 42 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   42

43 43 Rule 4: Auxiliary variable addition inc (): acquire (lock); t1 := x; t1 := t1 + 1 x := t1; release(lock); inc (): acquire (lock); a := tid; t1 := x; t1 := t1 + 1 x := t1; release(lock); a := 0; AUX-ANNOTATE 43

44 Rule 5: Variable Hiding 44 LookUp(x:int) returns (r:bool) atomic { if (Spec[x] > 0) r := true; else r := false; } LookUp(x:int) returns(r:bool) atomic { r := \Exists k. 0 <= k < N && Impl[k].elt == x && Impl[k].stt == full } forall x. Spec[x] == | { k | 0 <=k< N && Impl[k].elt == x && Impl[k].stt == full } | The invariant: HIDE VARIABLE

45 Rule 5: Variable Hiding 45 LookUp(x:int) returns (r:bool) atomic { if (Spec[x] > 0) r := true; else r := false; } LookUp(x:int) returns(r:bool) atomic { r := \Exists k. 0 <= k < N && Impl[k].elt == x && Impl[k].stt == full } forall x. Spec[x] == | { k | 0 <=k< N && Impl[k].elt == x && Impl[k].stt == full } | The invariant: HIDE VARIABLE

46 Soundness theorem 46 If Impl n satisfies all assertions then [POPL’09]: Impl 1 satisfies all assertions. [TACAS’10]: If Impl n is linearizable to Spec then Impl 1 is linearizable to Spec... Impl 2 Impl 1 Impl n Original implementation Transformed implementation reduce abstract

47 Proof in QED and soundness 47... Impl 2 Impl 1 Spec If Spec satisfies all assertions then [POPL’09]: Impl 1 satisfies all assertions [TACAS’10]: Impl 1 is linearizable to Spec Original implementationSpecification reduce abstract

48 48

49 49 InsertPair(x:int, y:int) returns (r:bool) atomic { i := FindSlot(); j := FindSlot(); Impl[i].elt := x; Impl[j].elt := y; lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true; } InsertPair(x:int, y:int) returns (r:bool) i := FindSlot(); j := FindSlot(); Impl[i].elt := x; Impl[j].elt := y; atomic { lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true; } reduction + abstraction

50 50 InsertPair(x:int, y:int) returns (r:bool) atomic { i := FindSlot(); j := FindSlot(); Impl[i].elt := x; Impl[j].elt := y; lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true; } introduce Spec InsertPair(x:int, y:int) returns (r:bool) atomic { i := FindSlot(); j := FindSlot(); Impl[i].elt := x; Impl[j].elt := y; lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true; Spec[x] := Spec[x] + 1; Spec[y] := Spec[y] + 1; } code modifying new variable

51 51 add invariant InsertPair(x:int, y:int) returns (r:bool) atomic { i := FindSlot(); j := FindSlot(); Impl[i].elt := x; Impl[j].elt := y; lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true; Spec[x] := Spec[x] + 1; Spec[y] := Spec[y] + 1; } forall x. Spec[x] == |{ k | 0 <= k < N && Impl[k].elt == x && Impl[k].stt == full }|

52 52 hide Impl InsertPair(x:int, y:int) returns (r:bool) atomic { i := FindSlot(); j := FindSlot(); Impl[i].elt := x; Impl[j].elt := y; lock(Impl[i]); lock(Impl[j]); Impl[i].stt := full; Impl[j].stt := full; unlock(Impl[i]); unlock(Impl[j]); r := true; Spec[x] := Spec[x] + 1; Spec[y] := Spec[y] + 1; } InsertPair(x:int, y:int) returns (r:bool) atomic { r := true; Spec[x] := Spec[x] + 1; Spec[y] := Spec[y] + 1; } LookUp(x:int) returns (r:bool) atomic { if(Spec[x] > 0) r := true; else r := false; }

53 53

54 54

55 55 Superficial Conflict Not a right mover: Modifies Tail

56 56 Variable Addition and Hiding Removes Conflict Add auxiliary var _Tail: “Real” tail of list Introduce invariant: Hide _Tail Superficial conflict disappears: Move_Tail d oesn’t access _Tail New invariant: Define auxiliary var for sequence of elements in list, hide everything else

57 57 Variable Addition and Hiding Removes Conflict Add auxiliary var _Tail: “Real” tail of list Introduce invariant: Hide _Tail Superficial conflict disappears: Move_Tail d oesn’t access _Tail New invariant: Define auxiliary var for sequence of elements in list, hide everything else

58 58 Variable Addition and Hiding Removes Conflict Apply reduction and variable hiding to arrive at specification Final invariant: Implementation is linearizable to specification

59 59


Download ppt "Simplifying Linearizability Proofs Using Reduction and Abstraction Serdar Tasiran Koc University, Istanbul, Turkey Tayfun Elmas, Ali Sezgin, Omer Subasi."

Similar presentations


Ads by Google