RGSep action inference Viktor Vafeiadis Microsoft Research Cambridge/ University of Cambridge
Michael & Scott non-blocking queue head tail null X 1 2 3 4 typedef struct Node_s *Node; struct Queue_s *Q; struct Node_s { init() { int val; n = new Node(); Node next; n → next = null; } Q = new Queue(); Q → head = node; struct Queue_s { Q → tail = node; Node head; } Node tail; }
A slight complication... The tail pointer can lag behind by one node: head tail null X 1 2 3 Except when the queue is empty: head tail null Y X
Enqueue & dequeue Don’t read the code ! dequeue () { enqueue(v) { while (true) { m = new Node(); m → val = v; h = Q → head; m → next = null; t = Q → tail; while (true) { n = h → next; t = Q → tail; if (Q → tail ≠ t) continue ; n = tail → next; if (h == t) { if (Q → tail ≠ tail) continue ; if (n == null) if (next == null) { return EMPTY; if (CAS(&t → next,n,m)) CAS(&Q → tail,t,n); break; } else { } else { if (CAS(&Q → head,h,n)) CAS(&Q → tail,t,n); return n → val; } } } } CAS(&Q → tail,t,n); } }
Verification challenge Functional correctness: Every method executes ‘atomically’ and obeys a high-level specification VMCAI ’09 work in progress Liveness properties, e.g. lock-freedom: At all times, some outstanding method call is guaranteed to terminate. POPL ’09 But first, do shape analysis to: (1) Find data structure invariants this talk (2) Prove memory safety
RGSep Combining rely-guarantee and separation logic
Whence RGSep? Separation logic: Good at describing dynamically allocated data structures head tail null ∃ h t. Q ↦ head:h,tail:t ∗ lseg(h,t) ∗ t ↦ next:null Rely-guarantee: Good at reasoning about concurrency Describes interference between threads: how the state evolves
Local & shared assertions Logically divide the state into: Local: only one thread can access it Shared: any thread can access it. RGSep assertions: p, q ::= (P local ¦ P Shared ) | p 1 ∨ p 2 normal separation logic assertions (about the local & | ∃ x. p shared state respectively)
Rely-guarantee specifications Rely: interference caused by environment; Describes how the environment is allowed to change the shared precondition state R,G ⊢ RGSep {p} cmd {q} Guarantee: postcondition interference caused by the program. Describes how the program is allowed to change the shared state.
RGSep actions (pre-/postcondition pairs) Summarize the shared state updates head tail head tail Enqueue null null A B A head tail head tail Dequeue A B A B head tail head tail Advance tail pointer A B A B
RGSep actions (pre-/postcondition & context) Summarize the shared state updates head tail head tail Enqueue null null A B A head tail head tail Dequeue A B A B head tail head tail Advance tail pointer A B A B
The actions of enqueue & dequeue enqueue(v) { dequeue () { m = new Node(); while (true) { m → val = v; Local h = Q → head; m → next = null; updates t = Q → tail; while (true) { n = h → next; t = Q → tail; if (Q → tail ≠ t) continue ; n = tail → next; if (h == t) { if (Q → tail ≠ tail) continue ; if (n == null) if (next == null) { return EMPTY; if (CAS(&t → next,n,m)) ENQUEUE CAS(&Q → tail,t,n); ADV. TAIL break; } else { } else { if (CAS(&Q → head,h,n)) DEQUEUE CAS(&Q → tail,t,n); ADV. TAIL return n → val; } } } } } CAS(&Q → tail,t,n); ADV. TAIL }
The semantics of actions precondition postcondition [[R|P ↝ Q]] ≝ {(s ⊎ s ctx , s’ ⊎ s ctx ) | ∃ I. [[P]] I (s) ∧ [[Q]] I (s’) context ∧ [[R ∗ true]] I (s ctx ) } R & G are sets of such actions: [[{a 1 ,...a n }]] ≝ ([[a 1 ]] ∪ ... ∪ [[a n ]])*
Action Inference
The action inference problem Given cmd, R, p: find G, q s.t. R, G ⊢ RGSep {p} cmd {q}. Preferably, the strongest G and q Top-level programs: R = ∅ and p = true. Libraries; the most general client: init(); ‖ ? { if (?) enqueue(?) else if (?) dequeue() else length() }
(Must)-Subtraction a.k.a. entailment with frame inference S UBTRACT (P, Q, xs) returns R such that [[P]] I (s) ⇒ ∃ s 1 s 2 vs. s=s 1 ⊎ s 2 ∧ [[Q]] I[xs → vs] (s 1 ) ∧ [[R]] I[xs → vs] (s 2 ), or fails if no such an R exists, or because if cannot find it. Used to calculate postcondition of normal commands. Given precondition P & command with spec {P o } _ {Q o }, the postcondition is: S UBTRACT (P, P o ) ∗ Q o
May-Subtraction M AY -S UBTRACT (P, Q, R) returns R’ such that [[P]] I (s 1 ⊎ s 2 ) ∧ [[Q]] I (s 1 ) ∧ [[R ∗ true]] I (s 2 ) ⇒ [[R’]] I (s 2 ) Generalization of septraction (existential magic wand) Used to calculate postcondition after interference. Given precondition P and an action (R o | P o ↝ Q o ), the postcondition is: M AY -S UBTRACT (P, P o , R o ) ∗ Q o
Examples S UBTRACT (a ↦ 3 ∗ b ↦ 4, a ↦ x, {}) = Error! S UBTRACT (a ↦ 3 ∗ b ↦ 4, a ↦ x, {x}) = b ↦ 4 ∗ (x = 3) M AY -S UBTRACT (a ↦ 3, a ↦ x, emp) = (x = 3) M AY -S UBTRACT (a ↦ 3, emp, a ↦ x) = a ↦ 3 ∗ (x = 3) M AY -S UBTRACT (a ↦ 3 ∗ b ↦ 4, x ↦ y, emp) = b ↦ 4 ∗ (x = a) ∗ (y = 3) ∨ a ↦ 3 ∗ (x = b) ∗ (y = 4)
Stabilization Calculate the e fg ect of interference on an assertion: overapproximate P; Rely* repeat P old := P; for each R i |P i ↝ Q i in Rely do P := P ∨ M AY -S UBTRACT (P old , P i , R i ) ∗ Q i ; until (P = P old )
Symbolic execution of memory reads S YMB -E XEC (Rely, p 1 ∨ p 2 , x=*E) ≝ // Do the obvious case split... R, G 1 ⊢ {p 1 } C {q 1 } (G 1 , q 1 ) := S YMB -E XEC (Rely, p 1 , x=*E); R, G 2 ⊢ {p 2 } C {q 2 } (G 2 , q 2 ) := S YMB -E XEC (Rely, p 2 , x=*E); R, G 1 ∪ G 2 ⊢ {p 1 ∨ p 2 } C {q 1 ∨ q 2 } return (G 1 ∪ G 2 , q 1 ∨ q 2 ) { ∃ α . E ↦ α ∗ R} S YMB -E XEC (Rely, (P L ¦ P S ), x=*E) ≝ x=*E // Is it a local read? { ∃ αβ . E[ β /x] ↦ α ∗ x= α ∗ R[ β /x]} if S UBTRACT( P L , ∃ α . E ↦ α ) = R L then return ( ∅ , ( ∃ αβ . x= α ∗ E[ β /x] ↦ α ∗ R L [ β /x] ¦ P S [ β /x])) // Is it a shared read? else if S UBTRACT( P S , ∃ α . E ↦ α ) = R S then return ( ∅ , ( ∃ αβ . x= α ∗ E[ β /x] ↦ α ∗ P L [ β /x] ¦ R S [ β /x])) else Verification-Error
Symbolic execution of memory writes S YMB -E XEC (Rely, (P L ¦ P S ), *E=E’) ≝ { ∃ α . E ↦ α ∗ R} *E=E’ {E ↦ E’ ∗ R} // Is it a local write? if S UBTRACT( P L , ∃ α . E ↦ α ) = R L then return ( ∅ , (E ↦ E’ ∗ R L ¦ P S )) // Is it a shared write? else if S UBTRACT( P S , ∃ α . E ↦ α ) = R S then // Anything in the local state reachable from E’ becomes shared (P L2S , P L ) := R EACHABLE- S PLIT (P L , E ↦ E’); act := (R S | E ↦ α ↝ E ↦ E’ ∗ P L2S ); q := (P L ¦ E ↦ E’ ∗ P L2S ∗ R S ); // Abstract the action & return Transfer of ownership return ({A-A BS (act)}, q) Local to shared else Verification-Error
Dealing with the parallel composition R ∪ G , G ⊢ {p} cmd {q} R, G ⊢ {p} cmd ∥ cmd {q} Fixed point calculation: Start with G = ∅ . Symbolically execute cmd and get new G’. Extend G with G’ and repeat until G’=G.
Action abstraction Abstraction. Given an action (C | P ↝ Q), return (C’ | P’ ↝ Q’) such that [[C | P ↝ Q]] ⊆ [[C’ | P’ ↝ Q’]]. Replace local variables with fresh logical variables. Abstract C, P, Q separately. For locking and unlocking actions, let C’=emp.
Initial experiments Algorithm Iter Actions Time (s) Treiber stack 4 5 0.1 M&S two-lock queue 5 26 0.3 M&S non-blocking queue 5 10 1.7 DGLM non-blocking queue 5 12 2.2 Lock-coupling list 4 21 1.0 Too many Optimistic list 5 30 109.1 actions Lazy list 5 48 60.0 Vechev’s CAS list 3 9 24.7 Vechev’s DCAS list 2 6 0.3
Lossless join on action sets Given action sets X & Y, return Z s.t. [[Z]] = [[X ∪ Y]]. Don’t do Z=X ∪ Y, but try to find a Z with fewer elements. Approximation to inclusion. (C 1 |P 1 ↝ Q 1 ) ⊑ (C 2 |P 2 ↝ Q 2 ) Exists substitution σ of the logical variables such that P 1 = σ (P 2 ) and Q 1 = σ (Q 2 ) and C 1 ⊢ σ (C 2 ) ∗ true If (C 1 |P 1 ↝ Q 1 ) ⊑ (C 2 |P 2 ↝ Q 2 ), then [[C 1 |P 1 ↝ Q 1 ]] ⊆ [[C 2 |P 2 ↝ Q 2 ]]. Lossless join. JOIN(A,{b}) ≝ if ∃ a ∊ A. b ⊑ a then A else {b} ∪ {a ∊ A|a ⋢ b} JOIN(A,{b 1 ,...,b n }) ≝ JOIN(...(JOIN(JOIN(A, {b 1 }),{b 2 })...),{b n })
Experiments No join With lossless join Algorithm Iter Actions Time (s) Iter Actions Time (s) Treiber stack 4 5 0.1 4 2 0.1 M&S two-lock queue 5 26 0.3 5 12 0.3 M&S non-blocking queue 5 10 1.7 5 6 1.5 DGLM non-blocking queue 5 12 2.2 5 8 2.0 Lock-coupling list 4 21 1.0 3 10 0.8 Optimistic list 5 30 109.1 3 10 52.3 Lazy list 5 48 60.0 4 13 26.2 Vechev’s CAS list 3 9 24.7 3 5 8.8 Vechev’s DCAS list 2 6 0.3 3 4 0.3 Run action inference, finding data structure invariants & proving memory safety. Iter: number of iterations for finding the rely-guarantee specs of each thread. Actions: number of actions inferred.
Recommend
More recommend