Towards full verification of concurrent libraries Viktor Vafeiadis - - PowerPoint PPT Presentation

towards full verification of concurrent libraries
SMART_READER_LITE
LIVE PREVIEW

Towards full verification of concurrent libraries Viktor Vafeiadis - - PowerPoint PPT Presentation

Towards full verification of concurrent libraries Viktor Vafeiadis Program verification Programs considered: Small (<100 LOC) Medium (KLOC) Large (MLOC) Simple


slide-1
SLIDE 1

Towards full verification

  • f concurrent libraries

Viktor Vafeiadis

slide-2
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
SLIDE 3

Concurrent libraries?

  • Imperative data structures

... dynamically allocated

  • Highly concurrent

... 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
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

  • p. techniques

tools

RGSep deny-guarantee

slide-5
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
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
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
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
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
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
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
SLIDE 12

RGSep

Combining rely-guarantee and separation logic

slide-13
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
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
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
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
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
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
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

  • Top-level programs:

R = ∅ and p = true.

  • Libraries; the most general client:

init(); ‖? while(?) {if(?) enqueue(?) else if(?) dequeue() else length()}

slide-20
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
SLIDE 21

Linearizability

Automatically proving linearizability [Vafeiadis, CAV’09]

slide-22
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
SLIDE 23

Linearization points

The implementation is a refinement of an atomic specification.

abstract execution concrete execution

linearization point (LP)

slide-24
SLIDE 24

Linearization point of enqueue

  • Lin. Point

(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
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
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
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
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
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
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
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
SLIDE 32

Non-blocking liveness properties

Proving that non-blocking algorithms don’t block [Gotsman, Cook, Parkinson, Vafeiadis, POPL’09]

slide-33
SLIDE 33

Non-blocking liveness properties

  • Wait-freedom:

Every thread is guaranteed to complete its operation.

  • Lock-freedom:

[Provided that there is at least one thread scheduled,] some thread is guaranteed to complete its operation.

  • Obstruction-freedom:

Every thread is guaranteed to complete its operation, provided it eventually executes in isolation. (No assumptions about the scheduler.)

slide-34
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
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
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
SLIDE 37

Guarantee-strengthening

First, do action inference Then, iteratively eliminate actions

slide-38
SLIDE 38

Guarantee-strengthening

First, do action inference Then, iteratively eliminate actions □(ENQUEUE ∨ DEQUEUE ∨ ADV.TAIL)

slide-39
SLIDE 39

Guarantee-strengthening

First, do action inference Then, iteratively eliminate actions

¬□◊ENQUEUE

Terminates

¬□◊DEQUEUE ¬□◊ADV.TAIL

□(ENQUEUE ∨ DEQUEUE ∨ ADV.TAIL)

slide-40
SLIDE 40

Guarantee-strengthening

First, do action inference Then, iteratively eliminate actions

   

¬□◊ENQUEUE

Terminates

¬□◊DEQUEUE ¬□◊ADV.TAIL

□(ENQUEUE ∨ DEQUEUE ∨ ADV.TAIL)

slide-41
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
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
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
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
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