600 likes | 787 Views
Verifying Optimistic Concurrency: Prophecy Variables and Backwards Reasoning . Serdar Tasiran Koç University Istanbul, Turkey Tayfun Elmas Shaz Qadeer Ali Sezgin Koç University Microsoft Research Koç University Istanbul , Turkey Redmond, WA Istanbul, Turkey.
E N D
Verifying Optimistic Concurrency: Prophecy Variables and Backwards Reasoning SerdarTasiran Koç University Istanbul, Turkey TayfunElmasShaz Qadeer Ali SezginKoç UniversityMicrosoft ResearchKoç University Istanbul, Turkey Redmond, WAIstanbul, Turkey
Static Reduction Proofs for Optimistic Concurrency • Goal: Verify programs that use optimistic concurrency • Transactional memory, non-blocking data structures, … • “A Compositional Method for Verifying Software Transactional Memory Implementations” [MSR Tech. Report ’07] • Optimistic concurrency:Proceed assuming non-interference • Abort, undo and/or retry if interference detected • Approach: Static proof system QED [POPL ‘09, PADTAD ‘09] • Challenge in applying QED to optimistic concurrency: • Same code, different reduction proofs for different futures • Need mechanism for referring to execution’s future • Prophecy variables and backwards reasoning in QED
Outline • Forward reasoning overview • Lock-protected increment • QED proof • History variables, “assert” annotations • Backward reasoning: Temporal dual • Optimistic concurrency example: Copy • Why forward reasoning doesn’t work • Temporally dual approach • Prophecy variables, “tressa” annotations • Generalized (back and forth) QED • Examples
QED Proof for Blocking Concurrency • Example: Lock-protected increment global int x; x := 0; || assert (x == 2); local int t; acquire (lock); t := x; t := t + 1;x := t; release (lock); local int t; acquire (lock); t := x; t := t + 1;x := t; release (lock);
Idea: Proof by reduction acquire (lock); t := x; t := t + 1; x := t; release(lock); acquire (lock); t := x; t := t + 1; x := t; release(lock); R B REDUCE-SEQUENTIAL B B L
Static mover check fails: Apparent conflict • 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 acquire (lock); t1 := x; t1 := t1 + 1; x := t1; release(lock); acquire (lock); t2 := x; t2 := t2 + 1; x := t2; release(lock);
Auxiliary variable: Which thread holds the lock? • Auxiliary variable a is a history variable • Summarizes relevant part of execution history New invariant: (lock == true) (a != 0) inc (): int t; acquire (lock); a := tid; t = x; t = t + 1 x = t; release(lock); a := 0; inc (): int t; acquire (lock); t = x; t = t + 1 x = t; release(lock); AUX-ANNOTATE
Annotating Actions with Assertions Invariant: (lock == true) (a != 0) acquire (lock); a := tid; assert a == tid; t = x; t = t + 1 assert a == tid; x = t; assert a == tid;release(lock); a := 0; acquire (lock); a := tid; t = x; t = t + 1 x = t; release(lock); a := 0; ABSTRACT • Assertions indicate belief about non interference • Annotate actions locally with global information about execution
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; assert a == tid1;release(lock); a := 0; 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; R B B B L • assert a == tid1; x = t1; andassert a == tid2; x = t2; commute • αβ≤ βα • Because αβ and βα result in assertion violations.
Borrowing and paying back assertions Invariant: (lock == true) (a != 0) Discharges the assertions inc (): int t; acquire (lock); a := tid; assert a == tid; t = x; t = t + 1 assert a == tid; x = t; assert a == tid;release(lock); a := 0; inc (): int t; acquire (lock); a := tid; assert a == tid;t = x; t = t + 1 assert a == tid;x = t; assert a == tid;release(lock); a := 0; R B B B L REDUCE & RELAX
Outline • Forward reasoning overview • Lock-protected increment • QED proof • History variables, “assert” annotations • Backward reasoning: Temporal dual • Optimistic concurrency example: Copy • Why forward reasoning doesn’t work • Temporally dual approach • Prophecy variables, “tressa” annotations • Generalized (back and forth) QED • Examples
Optimistic Concurrency Example Copy(fr: Obj, to: Obj){ atomic{ version := fr.ver; value := fr.val;} atomic{ if (version == fr.ver) {to.val := value; to.ver := to.ver + 1;} } } Wrt(to: Obj, newVal: int){ atomic{ to.val := newVal; to.ver := to.ver + 1;} }
Optimistic Concurrency Example Copy(fr: Obj, to: Obj){ action SS(fr): atomic{ version := fr.ver; value := fr.val;} action ConfNWrt(fr, to): atomic{ if (version == fr.ver) {to.val := value; to.ver := to.ver + 1;} } } Wrt(to: Obj, newVal: int){ atomic{ to.val := newVal; to.ver := to.ver + 1;} }
Optimistic Concurrency Example Copy(fr: Obj, to: Obj){ action SS(fr): atomic{ version := fr.ver; value := fr.val;} action ConfNWrt(fr, to): atomic{ if (version == fr.ver) {to.val := value; to.ver := to.ver + 1;} } } Wrt(to: Obj, newVal: int){ atomic{ to.val := newVal; to.ver := to.ver + 1;} } Goal: Prove that SS(fr) is a right mover
Reduction for Optimistic Concurrency • Copy(x, to_y) running concurrently withWrt(x) • Does SS always commute to the right of Wrt? • Failing Copy (Wrt interferes with Copy) T1: SS(x) ConfNWrt(x, to_y) T2:Wrt(x, val) • Succeeding Copy (No interference) T1: SS(x) ConfNWrt(x, to_y) T2: Wrt(x, val)
Reduction Argument for Optimistic Concurrency • Failing Copy T1: SS(x) ConfNWrt(x, to_y) T2:Wrt(x, val) • Snapshot taken but ConfNWrtdoes not write to to_y. • Abstract SS: action SS(fr): atomic{ version := fr.ver; value := fr.val;} action SS_Abs(fr): atomic{ havoc version, value; }
Copy Example After Abstraction Copy(fr: Obj, to: Obj){ action SS_Abs(fr): atomic{ havoc version, value; } action ConfNWrt(fr, to): atomic{ if (version == fr.ver) {to.val := value; to.ver := to.ver + 1;} } } Wrt(to: Obj, newVal: int){ atomic{ to.val := newVal; to.ver := to.ver + 1;} }
Problem: Too much abstraction • Succeeding Copy (No interference) T1: SS_Abs(x) ConfNWrt(x, to_y) T2: Wrt(x, val) • Arbitrary value written to to_y! • Want to abstract SS(x) toSS_Abs(x) only in executionsin which interference occurs (later). • But, SS_Abs doesn’t yet know whether interference will occur. • Need mechanism to refer to the future of the execution.
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 p = R, G or B
Introducing a Prophecy Variable action ConfNWrt(fr, to): atomic{ if (version == fr.ver) {to.val := value; to.ver := to.ver + 1;} } action ConfNWrt(fr, to): atomic{ if (version == fr.ver) { p =: true; to.val := value; to.ver := to.ver + 1; } else { p =: false; } } }
Backwards Assignment “=:” • Backwards assignment p =: x; • Shorthand for atomic{ assume p == x; havoc p;} • p should match x • p’ can have any value • Thinking backwards in time: Assigns the value of x to p • In the execution, up to the point the following action is taken if (version == fr.ver) { p =: true; to.val := value; to.ver := to.ver + 1; } else { p =: false;} p is true iff the version number check (later) succeeds.
Prophecy Variable Introduction: Soundness • 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. τ
Abstraction Constrained by Prophecy Variable action SS_P(x): atomic{ if (p) {version := x.ver; value := x.val;} else { havoc version, value;} }
Reduction Proof of Optimistic Concurrency • Succeeding Copy T1: SS(x) ConfNWrt(x, to_y) T2: Wrt(x, val) • SS(x) commutes to the right of Wrt(x, val) • Copy succeeds Wrt(x, val) never immediately follows SS(x) • Need to annotate SS(x) with this fact. • Similar case: assert a == tid1; x = t1; andassert a == tid2; x = t2; • These two actions cannot follow each other
Reduction Proof of Optimistic Concurrency • Copy succeeds Wrt(x, val) never immediately follows SS(x) • Need to annotate SS(x) with this fact. • But SS(x) doesn’t yet know whether this is a succeeding Copy. • Think backwards: • Walk back from the end of an execution, • When we reach s2, we know whether Copy has been successful. • Restated fact: If Copy succeeded, at s2, the version number snapshot should be up-to-date. SS(x) ConfNWrt(x) … … … s0 s1 s2
Putting it all together Copy(fr: Obj, to: Obj){ action SS_P(fr): atomic{ if (p) {version := fr.ver; value := fr.val;} else { havoc version, value;} tressa p ==> (version >= fr.ver); } action ConfNWrt(fr, to): atomic{ if (version == fr.ver) { p =: true; to.val := value; to.ver := to.ver + 1; } else { p =: false; } } }
tressa : Temporal Dual of assert • tressa: Mechanism to annotate actions with assertions that can refer to prophecy variables • assert: Discharged by reasoning about history of execution. • tressa: Temporal dual of assert • Example:y := y+1; z := z-1; assume (x == 0);
tressa : Temporal Dual of assert • Example:y := y+1; // x == 0 or execution blocksz := 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!
tressa : Temporal Dual of assert • Example:y := y+1; // x == 0 or execution blocksz := 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 φ
Discharging tressa’s inc (): int t; acquire (lock); p =: 0 tressa a == tid; t = x; t = t + 1 tressa a == tid; x = t; release(lock); p =: tid; inc (): int t; acquire (lock); p =: 0; tressa p == tid;t = x; t = t + 1 tressa a == tid;x = t; release(lock); p =: tid; R B B B L REDUCE & RELAX
Actions with assert’s and tressa’s • Canonical action α: {assert a1; τ1; tressa p1} • τ1( s, s’) : Transition predicate • a1(s): assert predicate on the pre-state • p1(s’): tressa predicate on the post-state • Operational semantics: • assert and tressa predicates have no effect on execution • Execution has assert violation if any αi’sassert predicate is violated. • Execution has tressa violation if any αi’stressa predicate is violated. • QED± preserves assert and tressa violations α0 α1 α2 αn-1 … s0 s1 s2 s3 sn-1 sn
Abstraction and Mover Checks with tressa’s abstracts {assert a1; τ1; tressa p1} ≤ {assert a2; τ2; tressa p2} • Preserve assert violations: a2 a1 • Preserve assert 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 β ? αβ≤βα
Putting it all together Copy(fr: Obj, to: Obj){ action SS_P(fr): atomic{ if (p) {version := fr.ver; value := fr.val;} else { havoc version, value;} tressa p ==> (version >= fr.ver); } action ConfNWrt(fr, to): atomic{ if (version == fr.ver) { p =: true; to.val := value; to.ver := to.ver + 1; } else { p =: false; } } }
SS_P Wrt ≤ Wrt SS_P (Case 1) p == false Wrt(x) SS_P (x): havoc version, value action SS_P(x): atomic{ if (p) {version :=x.ver; value :=x.val;} else { havoc version, value;} tressa p ==> (version >= ver); } Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }
SS_P Wrt ≤ Wrt SS_P (Case 1) p == false SS_P(x): havoc version, value Wrt(x) Wrt(x) SS_P (x): havoc version, value action SS_P(x): atomic{ if (p) {version :=x.ver; value :=x.val;} else { havoc version, value;} tressa p ==> (version >= ver); } Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }
SS_P Wrt ≤ Wrt SS_P (Case 2) p == true Wrt(x) SS_Proph(x): tressa(version>=x.ver) action SS_P(x): atomic{ if (p) {version :=x.ver; value :=x.val;} else { havoc version, value;} tressa p ==> (version >= ver); } Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }
SS_P Wrt ≤ Wrt SS_P (Case 2) p == true Wrt(x) SS_Proph(x): tressa(version>=x.ver) version < x.ver(Case 2) action SS_P(x): atomic{ if (p) {version :=x.ver; value :=x.val;} else { havoc version, value;} tressa p ==> (version >= ver); } Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }
SS_P Wrt ≤ Wrt SS_P (Case 2) p == true version < x.ver Wrt(x) SS_Proph(x): tressa(version>=x.ver) version < x.ver(Case 2) action SS_P(x): atomic{ if (p) {version :=x.ver; value :=x.val;} else { havoc version, value;} tressa p ==> (version >= ver); } Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }
SS_P Wrt ≤ Wrt SS_P (Case 2) p == true SS_P (x): tressa(version>=x.ver) version < x.ver Wrt(x) SS_Proph(x): tressa(version>=x.ver) version < x.ver(Case 2) action SS_P(x): atomic{ if (p) {version :=x.ver; value :=x.val;} else { havoc version, value;} tressa p ==> (version >= ver); } Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }
SS_P Wrt ≤ Wrt SS_P (Case 3) p == true ✔ Wrt(x) SS_Proph(x): tressa(version>=x.ver) action SS_P(x): atomic{ if (p) {version :=x.ver; value :=x.val;} else { havoc version, value;} tressa p ==> (version >= ver); } Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }
SS_P Wrt ≤ Wrt SS_P (Case 3) p == true ✔ Wrt(x) SS_Proph(x): tressa(version>=x.ver) version == x.ver action SS_P(x): atomic{ if (p) {version :=x.ver; value :=x.val;} else { havoc version, value;} tressa p ==> (version >= ver); } Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }
SS_P Wrt ≤ Wrt SS_P (Case 3) p == true ✔ Wrt(x) SS_Proph(x): tressa(version>=x.ver) version+1 == x.ver version == x.ver action SS_P(x): atomic{ if (p) {version :=x.ver; value :=x.val;} else { havoc version, value;} tressa p ==> (version >= ver); } Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }
SS_P Wrt ≤ Wrt SS_P (Case 3) p == true SS_Proph(x): tressa(version>=x.ver) Wrt(x) SS_Proph(x): tressa(version>=x.ver) version+1 == x.ver version == x.ver action SS_P(x): atomic{ if (p) {version :=x.ver; value :=x.val;} else { havoc version, value;} tressa p ==> (version >= ver); } Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }
Copy is Atomic R Copy(fr: Obj, to: Obj){ action SS_P(fr): atomic{ if (p) {version := fr.ver; value := fr.val;} else { havoc version, value;} tressa p ==> (version >= ver); } action ConfNWrt(fr, to): atomic{ if (version == fr.ver) { p =: true; to.val := value; to.ver := to.ver + 1; } else { p =: false; } } }
Proving Optimistic Concurrency in QED • tressa’s allow one to express the following fact locally • If action b immediately follows action a, then this execution will eventually block • Optimistic concurrency proof template: • Introduce prophecy variables • Backwards assign them when non-determinism is resolved • Annotate earlier actions with tressa’s • Perform a case split in the proof based on possible futures • When blocks are large enough, discharge tressa’s by reasoning backwards in time.
Example 2: ReadPair procedure ReadPair(a: int, b: int) returns (s: bool, da: Obj, db: Obj) { varva: int, vb: int; 1: atomic { va := m[a].v; da := m[a].d; } 2: atomic { vb := m[b].v; db := m[b].d; } 3: s := true; 4: atomic { if (va < m[a].v) { s:= false; } } 5: atomic { if (vb < m[b].v) { s:= false; } } 6: if (!s) { da := nil; db := nil; } } procedure Write(a: int, d: Obj) { atomic { m[a].d := d; m[a].v := m[a].v+1; } }
Example 3: Multiset (Insert/Lookup) procedure Lookup(x: data) returns result: bool; { f := false; i := 0; while (i<n && !f) { f := (q[i] == x); i := i+1; } result = f; } procedure Insert(x: data) returns result: bool; { havoc i; assume i<n; cnt := 0; result := false; while (cnt<n && !f) { if (q[i]==-1) {q[i] := x; result := true; } else if (q[i]== x) { result := true; } else { i := (i+1) mod n; cnt := cnt+1; } } }
Example 3: Multiset (Insert/Lookup) procedure Lookup(x: data) returns result: bool; { … while (i<n && !f) { f := (q[i] == x); i := i+1; } result = f; } procedure Insert(x: data) returns result: bool; { ... } • Lookup’s that return true commit when they find x. • Lookup’s that return false commit when they read q[0]. • Different reduction proofs needed for the two cases • Replace Lookup(x) with if (*) { Lookup(x); assume result} else { Lookup(x); assume !result} • Use return value as prophecy variable • Perform different reduction proofs on the two branches
Summary • QED: Proof system for concurrent software • Iteration of abstraction and reduction surprisingly powerful • Intricate algorithms verified • Reduction proofs for optimistic concurrency • Actions need to refer to the future: prophecy variables • Annotate actions locally with info about future: tressa • QED + prophecy variables, tressa’s, forward & backward reasoning • Generalized definition of simulation • Soundness proof • Proved examples making use of optimistic concurrency • Future work: Verify transactional memory algorithms, non-blocking data structures.