Download presentation
Presentation is loading. Please wait.
Published byBasil Franklin Modified over 8 years ago
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
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.