RGSep action inference Viktor Vafeiadis Microsoft Research - - PowerPoint PPT Presentation
RGSep action inference Viktor Vafeiadis Microsoft Research - - PowerPoint PPT Presentation
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
Michael & Scott non-blocking queue
X 1 3 4
null
2 head tail
typedef struct Node_s *Node; struct Node_s { int val; Node next; } struct Queue_s { Node head; Node tail; } struct Queue_s *Q; init() { n = new Node(); n→next = null; Q = new Queue(); Q→head = node; Q→tail = node; }
A slight complication...
The tail pointer can lag behind by one node: Except when the queue is empty:
Y X
null
head tail X 1 3
null
2 head tail
Enqueue & dequeue
enqueue(v) { m = new Node(); m→val = v; m→next = null; while (true) { t = Q→tail; n = tail→next; if (Q→tail ≠ tail) continue; if (next == null) { if (CAS(&t→next,n,m)) break; } else { CAS(&Q→tail,t,n); } } CAS(&Q→tail,t,n); }
dequeue () { while (true) { h = Q→head; t = Q→tail; n = h→next; if (Q→tail ≠ t) continue; if (h == t) { if (n == null) return EMPTY; CAS(&Q→tail,t,n); } else { if (CAS(&Q→head,h,n)) return n→val; } } }
Don’t read the code!
Verification challenge
Functional correctness: Every method executes ‘atomically’ and obeys a high-level specification Liveness properties, e.g. lock-freedom: At all times, some outstanding method call is guaranteed to terminate. But first, do shape analysis to: (1) Find data structure invariants (2) Prove memory safety
VMCAI ’09
work in progress
POPL ’09
this talk
RGSep
Combining rely-guarantee and separation logic
Whence RGSep?
Separation logic:
- Good at describing dynamically allocated data
structures ∃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 null
head tail
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 ::= (Plocal ¦ PShared) | p1 ∨ p2 | ∃x. p
normal separation logic assertions (about the local & shared state respectively)
Rely-guarantee specifications
R,G ⊢RGSep {p} cmd {q}
Rely: interference
caused by environment; Describes how the environment is allowed to change the shared state
Guarantee:
interference caused by the program. Describes how the program is allowed to change the shared state.
precondition postcondition
RGSep actions (pre-/postcondition pairs)
- Summarize the shared state updates
A B
null
head tail A B head tail A B head tail A
null
head tail A B head tail A B head tail Enqueue Dequeue Advance tail pointer
RGSep actions (pre-/postcondition & context)
- Summarize the shared state updates
A B
null
head tail A B head tail A B head tail A
null
head tail A B head tail A B head tail Enqueue Dequeue Advance tail pointer
The actions of enqueue & dequeue
enqueue(v) { m = new Node(); m→val = v; m→next = null; while (true) { t = Q→tail; n = tail→next; if (Q→tail ≠ tail) continue; if (next == null) { if (CAS(&t→next,n,m)) break; } else { CAS(&Q→tail,t,n); } } CAS(&Q→tail,t,n); }
dequeue () { while (true) { h = Q→head; t = Q→tail; n = h→next; if (Q→tail ≠ t) continue; if (h == t) { if (n == null) return EMPTY; CAS(&Q→tail,t,n); } else { if (CAS(&Q→head,h,n)) return n→val; } } }
ENQUEUE DEQUEUE
- ADV. TAIL
- ADV. TAIL
- ADV. TAIL
Local updates
The semantics of actions
[[R|P↝Q]] ≝ {(s⊎sctx , s’⊎sctx) | ∃I. [[P]]I(s) ∧ [[Q]]I(s’) ∧ [[R∗true]]I(sctx) }
R & G are sets of such actions:
[[{a1,...an}]] ≝ ([[a1]] ∪ ... ∪ [[an]])*
precondition postcondition context
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 SUBTRACT(P, Q, xs) returns R such that [[P]]I(s) ⇒ ∃s1s2vs. s=s1⊎s2 ∧ [[Q]]I[xs→vs](s1) ∧ [[R]]I[xs→vs](s2),
- r 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 {Po} _ {Qo}, the postcondition is: SUBTRACT(P, Po) ∗ Qo
May-Subtraction
MAY-SUBTRACT(P, Q, R) returns R’ such that [[P]]I(s1⊎s2) ∧ [[Q]]I(s1) ∧ [[R∗true]]I(s2) ⇒ [[R’]]I(s2)
- Generalization of septraction (existential magic wand)
- Used to calculate postcondition after interference.
Given precondition P and an action (Ro | Po↝ Qo), the postcondition is: MAY-SUBTRACT(P, Po, Ro) ∗ Qo
Examples
SUBTRACT(a ↦ 3 ∗ b ↦ 4, a ↦ x, {}) = Error! SUBTRACT(a ↦ 3 ∗ b ↦ 4, a ↦ x, {x}) = b ↦ 4 ∗ (x = 3) MAY-SUBTRACT(a ↦ 3, a ↦ x, emp) = (x = 3) MAY-SUBTRACT(a ↦ 3, emp, a ↦ x) = a ↦ 3 ∗ (x = 3) MAY-SUBTRACT(a ↦ 3 ∗ b ↦ 4, x ↦ y, emp) = b ↦ 4 ∗ (x = a) ∗ (y = 3) ∨ a ↦ 3 ∗ (x = b) ∗ (y = 4)
Stabilization
- Calculate the efgect of interference on an assertion:
- verapproximate P; Rely*
repeat Pold := P; for each Ri|Pi↝Qi in Rely do P := P ∨ MAY-SUBTRACT(Pold, Pi , Ri) ∗ Qi ; until (P = Pold)
Symbolic execution of memory reads
SYMB-EXEC (Rely, p1 ∨ p2, x=*E) ≝
// Do the obvious case split...
(G1, q1) := SYMB-EXEC (Rely, p1, x=*E); (G2, q2) := SYMB-EXEC (Rely, p2, x=*E); return (G1 ∪ G2 , q1 ∨ q2) SYMB-EXEC (Rely, (PL ¦ PS), x=*E) ≝
// Is it a local read?
if SUBTRACT(PL , ∃α. E↦α) = RL then return (∅, (∃αβ. x=α ∗ E[β/x]↦α ∗ RL[β/x] ¦ PS[β/x]))
// Is it a shared read?
else if SUBTRACT(PS , ∃α. E↦α) = RS then return (∅, (∃αβ. x=α ∗ E[β/x]↦α ∗ PL[β/x] ¦ RS[β/x])) else Verification-Error
R, G1 ⊢ {p1} C {q1} R, G2 ⊢ {p2} C {q2} R, G1∪G2 ⊢ {p1∨p2} C {q1∨q2} {∃α. E↦α ∗ R} x=*E {∃αβ. E[β/x]↦α ∗ x=α ∗ R[β/x]}
Symbolic execution of memory writes
SYMB-EXEC (Rely, (PL ¦ PS), *E=E’) ≝
// Is it a local write?
if SUBTRACT(PL , ∃α. E↦α) = RL then return (∅, (E↦E’ ∗ RL ¦ PS))
// Is it a shared write?
else if SUBTRACT(PS , ∃α. E↦α) = RS then
// Anything in the local state reachable from E’ becomes shared
(PL2S, PL) := REACHABLE-SPLIT (PL , E↦E’); act := (RS | E↦α ↝ E↦E’ ∗ PL2S); q := (PL ¦ E↦E’ ∗ PL2S ∗ RS);
// Abstract the action & return
return ({A-ABS(act)}, q) else Verification-Error
{∃α. E↦α ∗ R} *E=E’ {E↦E’ ∗ R} Transfer of ownership Local to shared
Dealing with the parallel composition
Fixed point calculation:
- Start with G = ∅.
Symbolically execute cmd and get new G’.
- Extend G with G’ and repeat until G’=G.
R ∪ G , G ⊢ {p} cmd {q} R, G ⊢ {p} cmd∥cmd {q}
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 Optimistic list 5 30 109.1 Lazy list 5 48 60.0 Vechev’s CAS list 3 9 24.7 Vechev’s DCAS list 2 6 0.3
Too many actions
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. (C1|P1↝Q1) ⊑ (C2|P2↝Q2)
- Exists substitution σ of the logical variables such that
P1 = σ(P2) and Q1 = σ(Q2) and C1 ⊢ σ(C2) ∗ true
- If (C1|P1↝Q1) ⊑ (C2|P2↝Q2), then [[C1|P1↝Q1]] ⊆ [[C2|P2↝Q2]].
Lossless join. JOIN(A,{b}) ≝ if ∃a∊A. b ⊑ a then A else {b} ∪ {a∊A|a⋢b} JOIN(A,{b1,...,bn}) ≝ JOIN(...(JOIN(JOIN(A, {b1}),{b2})...),{bn})
Experiments
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.
No join With lossless join
Future work
- Other uses of action inference
- Improve underlying abstract domains
- More data structures / properties
- Better performance
- Less heuristic-based ownership transfer