SLIDE 1 Towards full verification
Viktor Vafeiadis
SLIDE 2 Program verification
- Programs considered:
- Properties proved:
- Proof automation:
Manual Semi-automatic Automatic Basic safety Full correctness Small (<100 LOC) Medium (KLOC) Large (MLOC) Simple Complicated functional correctness termination no crashes no memory leaks
SLIDE 3 Concurrent libraries?
- Imperative data structures
... dynamically allocated
... shared memory concurrency ... with intended race conditions
- Simple interfaces (stacks, queues, finite maps)
- No higher-order functions
Small (<100 LOC) Medium (KLOC) Large (MLOC) Simple Complicated Basic safety Full correctness linearizability lock-freedom no crashes no memory leaks
SLIDE 4 correctness safety
My program verification work
ECOOP’10 PPoPP’06 SAS’07 ESOP’09 CONCUR’07 POPL’10 VMCAI’09 APLAS’09 POPL’09 VMCAI’10 CAV’10
manual semi-automatic automatic
linearizability
lock-freedom
program logics case studies
tools
RGSep deny-guarantee
SLIDE 5 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; }
SLIDE 6 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
SLIDE 7
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 (n == 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!
SLIDE 8 Length (first attempt)
length() { num = 0; curr = Q→head→next; while (curr ≠ null) { num++; curr = curr→next; } return num; }
X 1 3 4
null
2 head tail
SLIDE 9
length() { num = 0; while (true) { t = Q→tail; n = tail→next; if (n == null) break; CAS(&Q→tail,t,n); } curr = Q→head→next; while (curr ≠ null) { num++; if (curr == t) break; curr = curr→next; } return num; }
Length (second attempt)
Read Q→tail, and ensure that Q→tail→next == null
SLIDE 10
length() { num = 0; do { h = Q→head; while (true) { t = Q→tail; n = tail→next; if (n == null) break; CAS(&Q→tail,t,n); } } while (h ≠ Q→head); curr = h→next; while (curr ≠ null) { num++; if (curr == t) break; curr = curr→next; } return num; }
Length (third attempt)
Get a snapshot of Q→head and Q→tail and ensure that Q→tail→next==null.
SLIDE 11
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
CAV ’10
POPL ’09 VMCAI ’10
SLIDE 12
RGSep
Combining rely-guarantee and separation logic
SLIDE 13 Whence RGSep?
Separation logic [Reynolds, O’Hearn ~’01]:
- Good at describing heap-allocated data structures
- Local reasoning: proofs mention only the footprint.
∃h,t. Q↦head:h,tail:t ∗ lseg(h,t) ∗ t↦next:null Rely-guarantee [Jones, ’83]:
- Good at reasoning about concurrency
- Describes interference between threads:
how the state evolves null
head tail
SLIDE 14 Local & shared assertions
Logically divide the state into:
- Local: only one thread can access it
- Shared: any thread can access it.
Example: enqueue just before the loop: RGSep assertions: p, q ::= (Plocal ¦ Pshared) | p1 ∨ p2 | ∃x. p
normal separation logic assertions (about the local & shared state respectively)
null
head tail
null
local state shared state
SLIDE 15 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
SLIDE 16 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
SLIDE 17 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 (n == 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
SLIDE 18
The semantics of actions
[[P↝Q]] ≝ {(s1, s2) | ∃I,s,s′,sctx. s1=s⊎sctx ∧ [[P]]I(s) ∧ s2=s′⊎sctx ∧ [[Q]]I(s′) }
R & G are sets of such actions:
[[{a1,...an}]] ≝ ([[a1]] ∪ ... ∪ [[an]])*
precondition postcondition
SLIDE 19 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
R = ∅ and p = true.
- Libraries; the most general client:
init(); ‖? while(?) {if(?) enqueue(?) else if(?) dequeue() else length()}
SLIDE 20 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
SLIDE 21
Linearizability
Automatically proving linearizability [Vafeiadis, CAV’09]
SLIDE 22 Linearizability & forward simulation
Linearizability: The implementation (of every method) is a refinement
- f an atomic specification.
Standard proof technique: forward simulation Abstract (spec) Concrete (impl)
Sconc S’conc Sabs S’abs
SLIDE 23
Linearization points
The implementation is a refinement of an atomic specification.
abstract execution concrete execution
linearization point (LP)
SLIDE 24 Linearization point of enqueue
(provided CAS succeeds)
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 (n == null) { if (CAS(&t→next,n,m)) break; } else { CAS(&Q→tail,t,n); } } CAS(&Q→tail,t,n); }
SLIDE 25 Proof search for the LP ?
- For each execution path of each method,
choose a candidate LP
- Check whether it is a valid LP
Does this work ?
SLIDE 26 Proof search for the LP ?
- For each execution path of each method,
choose a candidate LP
- Check whether it is a valid LP
Does this work ? Not quite.
- 1. LPs can be conditional
- 2. LPs can be in the code of another thread
SLIDE 27 LP of dequeue, when it returns EMPTY
LP provided
this test fails, and the h==t test succeeds the n==null test succeeds
Condition:
¬prophecy(Q→tail ≠ t)
∧ h == t ∧ n == null 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; } } }
SLIDE 28 Key observation
- Method executions that logically modify the
state have a simple LP.
- Method executions that do not logically
modify the state often have a complex LP.
So: Treat these two cases differently.
- Search for LPs of executions that logically
modify the state;
- Do a non-constructive proof for executions
that do not logically modify the state.
SLIDE 29 Enhanced LP validation
Construct a “pure LP checker”: ... asserts CAN_RETURN(r) only if (1) the abs.method can return r & (2) have no other efgects
- 1. At the entry to the function:
lin := false (& ensure no CAN_RETURNs in pre)
- 2. At the candidate efgectful LPs:
assert(¬lin); lin := true; abs_method()
- 3. Add pure checkers at every program point.
- 4. At the return points, check
(¬lin ∧ CAN_RETURN(Result_of_method) ) ∨ ( lin ∧ Result_of_method = AbsResult )
SLIDE 30
Example
if (AbsQ == empty) { AbsResult = EMPTY; } else { AbsResult = head(AbsQ); AbsQ = behead(AbsQ); }
deq_pure(r) = (AbsQ==empty ∧ r==EMPTY) assume (∀r. deq_pure(r) ⇒ CAN_RETURN(r))
assume (AbsQ≠empty ∨ CAN_RETURN(EMPTY))
[derived from the spec]
dequeue spec
purity condition pure lin. checker which simplifies to
SLIDE 31 Experiments (linearizability)
Algorithm Lines Ops Efg Pure Time (s) DCAS stack 76 2 2 1 0.8 Treiber stack 76 2 2 1 0.8 Extended Treiber stack 190 8 5 6 2.8 M&S two-lock queue 85 2 2 1 5.3 M&S non-blocking queue 106 2 2 1 58.0 DGLM non-blocking queue 106 2 2 1 59.4 Extended M&S nb queue 164 4 3 3 87.3
Lines of code: exclude comments, but include declarations & specs Ops: number of methods Efg: number of efgectful methods Pure: number of methods with a pure execution path
SLIDE 32
Non-blocking liveness properties
Proving that non-blocking algorithms don’t block [Gotsman, Cook, Parkinson, Vafeiadis, POPL’09]
SLIDE 33 Non-blocking liveness properties
Every thread is guaranteed to complete its operation.
[Provided that there is at least one thread scheduled,] some thread is guaranteed to complete its operation.
Every thread is guaranteed to complete its operation, provided it eventually executes in isolation. (No assumptions about the scheduler.)
SLIDE 34
Lock-freedom reduces to termination
A library is lock-free ifg init(); ‖n { if(?) enqueue(?) else if(?) dequeue() else length() } terminates when n(=the number of threads) is bounded.
SLIDE 35 Reminder: 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 (n == 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
SLIDE 36 Guarantee-strengthening
I don’t execute ENQUEUE or DEQUEUE infinitely often I don’t execute ENQUEUE, DEQUEUE, or ADV.TAIL infinitely often I don’t execute ENQUEUE or DEQUEUE infinitely often I don’t execute ENQUEUE, DEQUEUE, or ADV.TAIL infinitely often
I terminate I terminate
SLIDE 37
Guarantee-strengthening
First, do action inference Then, iteratively eliminate actions
SLIDE 38
Guarantee-strengthening
First, do action inference Then, iteratively eliminate actions □(ENQUEUE ∨ DEQUEUE ∨ ADV.TAIL)
SLIDE 39 Guarantee-strengthening
First, do action inference Then, iteratively eliminate actions
¬□◊ENQUEUE
Terminates
¬□◊DEQUEUE ¬□◊ADV.TAIL
□(ENQUEUE ∨ DEQUEUE ∨ ADV.TAIL)
SLIDE 40 Guarantee-strengthening
First, do action inference Then, iteratively eliminate actions
¬□◊ENQUEUE
Terminates
¬□◊DEQUEUE ¬□◊ADV.TAIL
□(ENQUEUE ∨ DEQUEUE ∨ ADV.TAIL)
SLIDE 41 Guarantee-strengthening
First, do action inference Then, iteratively eliminate actions
¬□◊ENQUEUE
Terminates
¬□◊DEQUEUE ¬□◊ADV.TAIL ¬□◊ENQUEUE ¬□◊DEQUEUE ¬□◊ADV.TAIL
Terminates
□(ENQUEUE ∨ DEQUEUE ∨ ADV.TAIL)
SLIDE 42 Guarantee-strengthening
First, do action inference Then, iteratively eliminate actions
¬□◊ENQUEUE
Terminates
¬□◊DEQUEUE ¬□◊ADV.TAIL ¬□◊ENQUEUE ¬□◊DEQUEUE ¬□◊ADV.TAIL
Terminates
□(ENQUEUE ∨ DEQUEUE ∨ ADV.TAIL)
SLIDE 43 Guarantee-strengthening
First, do action inference Then, iteratively eliminate actions
¬□◊ENQUEUE
Terminates
¬□◊DEQUEUE ¬□◊ADV.TAIL ¬□◊ENQUEUE ¬□◊DEQUEUE ¬□◊ADV.TAIL
Terminates
¬□◊ENQUEUE ¬□◊DEQUEUE ¬□◊ADV.TAIL
Terminates
□(ENQUEUE ∨ DEQUEUE ∨ ADV.TAIL)
SLIDE 44 Guarantee-strengthening
First, do action inference Then, iteratively eliminate actions
¬□◊ENQUEUE
Terminates
¬□◊DEQUEUE ¬□◊ADV.TAIL ¬□◊ENQUEUE ¬□◊DEQUEUE ¬□◊ADV.TAIL
Terminates
¬□◊ENQUEUE ¬□◊DEQUEUE ¬□◊ADV.TAIL
Terminates
□(ENQUEUE ∨ DEQUEUE ∨ ADV.TAIL)
SLIDE 45 Future work
Improve underlying abstract domains
- More data structures / properties
- Better performance
Enable formal reasoning for
- Relaxed memory models
- Memory management for conc. algorithms
- Non-linearizable libraries
- Library performance
- Larger concurrent systems