A Calculus of Atomic ActionsTayfun Elmas, Shaz Qadeer and Serdar TasiranPOPL ‘09
236825 – Seminar in Distributed AlgorithmsCynthia Disenfeld
27/05/2013
*Some slides are based on those of the authors
GoalStatically verifying (partial) correctness of
shared-memory multithreaded programs
Difficult: understand thread-interaction + shared memory
Single-thread programs: pre/post conditions, loop invariants
Multithreaded programs: consider the effect of all thread-interleavings (e.g. Owicki-Gries)
Approach(Sound) program transformations
◦Abstraction◦Reduction
Invariant strengthening
OutlineMotivating examplesApproach – SoundnessReductionAbstractionBorrowing assertionsTacticsExperience / Conclusions
Motivating examples
void inc() { int t; acquire(lock); t := x; t := t+1; x := t; release(lock);}
void inc() { int t; [havoc t; x := x+1;]}
Lock-based atomic increment
Motivating examples
void inc() { int t; while(true){ t := x; if (CAS(x,t,t+1) break; }}
void inc() { int t; while(*){ t := x; assume x!=t; } t := x; [assume x==t; x := t+1];}
Lock-free atomic increment
* Transformation from Flanagan et al.[2005]
Motivating examples
void inc() { int t; while(*){ t := x; assume x!=t; } t := x; [assume x==t; x := t+1];}
void inc() { int t; while (*) { havoc t; skip; } havoc t; [assume x==t; x := t+1];}
Lock-free atomic increment
Motivating examples
void inc() { int t; while (*) { havoc t; skip; } havoc t; [assume x==t; x := t+1];}
void inc() { int t; havoc t; havoc t; [assume x==t; x := t+1];}
Lock-free atomic incrementvoid inc() { int t; [havoc t; x := x+1];}
Motivating examples
void add(int n){ while (0<n) { inc() n := n-1; }}
void add(int n){ while (0<n){ [x := x+1] n := n-1; }}
Client of incvoid add(int n){ [assert 0<=n; x := x+n; n := 0];}
OutlineMotivating examplesApproach – SoundnessReductionAbstractionBorrowing assertionsTacticsExperience / Conclusions
QED approach 1 1 2 2, , ... ,n nP I P I P I
Program InvariantFine-grained concurrencyHard to prove!Invariant = True
Coarse-grained concurrencyEasy to prove
Proof step
If the original program may fail the new program may fail
Soundness
For all
If then exists
If then exists
or
1 2I P P├ 1s I├1
1 errorP
s 2
1 errorP
s
1
1 2
P
s s2
1 2
P
s s
2
1 errorP
s
SoundnessFor each proof step
1 1 2 2, ,P I P I
2 1 2I P P├
Proof steps:•Invariant strengthening•Reduction: build more coarse-grained atomic blocks•Abstraction: add (possibly failing) behaviors
OutlineMotivating examplesApproach – SoundnessReductionAbstractionBorrowing assertionsTacticsExperience / Conclusions
Reduction
inc() { int t; acquire(lock); t := x; t := t+1; x := t; release(lock);}
main() { x := 0; inc(); || inc(); assert(x==2);}
Reduction
inc() { int t; acquire(lock); R t := x; B t := t+1; B x := t; B release(lock); L}
inc() { int t; acquire(lock); t := x; t := t+1; x := t; release(lock); }
inc() x := x+1REDUCE-SEQUENTIAL
Right Mover
Reduction
main() { x := 0; inc(); || inc(); assert(x==2);}
main() { x := 0;B x := x+1; || x := x+1; B assert(x==2);}
main() { x := 0; x := x+1; x := x+1; assert(x==2);}
INLINE-CALL REDUCE-PARALLEL
Static mover check ; ;I ├
Check using the current invariant if they access different variables, or are not enabled at the same time
Each statement consists of:when can it be applied?how is the state changed?
OutlineMotivating examplesApproach – SoundnessReductionAbstractionBorrowing assertionsTacticsExperience / Conclusions
Abstraction ruleReplace with if
I ├For all
If then exists
If then exists
or
1s I├
1 errors 1 errors
1 2s s 1 2s s
1 errors
Abstraction
void inc() { int t; while(*){ t := x; assume x!=t; } t := x; [assume x==t; x := t+1];}
void inc() { int t; while (*) { havoc t; skip; } havoc t; [assume x==t; x := t+1];}
SIMULATE
Then, we can reduce• havoc t + skip • while (*){…} havoc t
AbstractionAdding non-determinism
◦Guards if(*)◦t := x havoc t◦assume … skip
Adding behaviors that may go wrong◦x := x+1 if (x==0) fail; x := x+1◦y := y-x assert (y>x); y := y-x
OutlineMotivating examplesApproach – SoundnessReductionAbstractionBorrowing assertionsTacticsExperience / Conclusions
Example – Sorted linked listHand-over-hand lockingFind, insert, deleteLocal assertionsClass invariantAtomic easy!But… implementation with fine-
grained locking
Insert(5)
Insert(x)p := find(x); //locks pn := p.next;
t := new Node();t.val := x;t.next := n;
p.next := t;assert (p, t, n sorted); //they are linked as they
should and their values have increasing order
UNLOCK(p);
Insert(x)invariant: list is sorted
p := find(x); n := p.next;
t := new Node();t.val := x;t.next := n;
p.next := t;assert (p, t, n sorted);
UNLOCK(p);
p.val and p.next are not affected by other
threads
t.val and t.next are not affected by other
threads
Proof 1 1 2 2, , ... ,n nP I P I P I
p := find(x); n := p.next;
t := new Node();t.val := x;t.next := n;
p.next := t;assert (p, t, n sorted);
UNLOCK(p);
find appropriate pLOCK(p)n := p.next;
t := new Node(); Rt.val := x;t.next := n;
p.next := t;assert (p, t, n sorted);
UNLOCK(p); L
Proof 1 1 2 2, , ... ,n nP I P I P I
find appropriate pLOCK(p)n := p.next;
t := new Node(); Rt.val := x;t.next := n;
p.next := t;assert (p, t, n sorted);
UNLOCK(p); L
find appropriate pLOCK(p)n := p.next;
t := new Node();t.val := x;t.next := n;
p.next := t;assert (p, t, n sorted);
UNLOCK(p);
how to continue?
Apparent interferencep.next := t;
p.next := t;
n := p.next;
n := p.next;
Thread A Thread B
Apparent interferencep.next := t;
p.next := t;
n := p.next;
n := p.next;
Thread A Thread B
But: both p’s are locked!
Ruling out interference - 1assert owner[p]==Ap.next := t;
assert owner[p]==Ap.next := t;
assert owner[p]==Bn := p.next;
assert owner[p]==Bn := p.next;
Thread A Thread B
Ruling out interference - 2assert !inList[t]t.next := n;
assert !inList[t]t.next := n;
assert inList[p]n := p.next;
assert inList[p]n := p.next;
Thread A Thread B
Reduction after abstraction
find appropriate pLOCK(p)
assert inList[p]&&owner[p]==tidn := p.next;t := new Node(); t.val := x;assert !inList[t]t.next := n;assert inList[p]&&owner[p]==tidp.next := t;assert (p, t, n sorted); assert owner[p]==tidUNLOCK(p);
find appropriate pLOCK(p)
assert inList[p]&&owner[p]==tidn := p.next;t := new Node(); t.val := x;assert !inList[t]t.next := n;assert inList[p]&&owner[p]==tidp.next := t;assert (p, t, n sorted); assert owner[p]==tidUNLOCK(p);
Borrowed assertionsfind appropriate pLOCK(p)
assert inList[p]&&owner[p]==tidn := p.next;t := new Node(); t.val := x;assert !inList[t]t.next := n;assert inList[p]&&owner[p]==tidp.next := t;assert (p, t, n sorted); assert owner[p]==tidUNLOCK(p);
find appropriate pLOCK(p)
assert inList[p]&&owner[p]==tidn := p.next;t := new Node(); t.val := x;assert !inList[t]t.next := n;assert inList[p]&&owner[p]==tidp.next := t;assert (p, t, n sorted); assert owner[p]==tidUNLOCK(p);
Completing the prooffind appropriate pLOCK(p)
n := p.next;
t := new Node(); t.val := x;t.next := n;
p.next := t;assert (p, t, n sorted); UNLOCK(p);
Invariant : List is sorted
OutlineMotivating examplesApproach – SoundnessReductionAbstractionBorrowing assertionsTacticsExperience / Conclusions
TacticsHigh-level strategies multiple
rule proofs◦abstract from a read, write◦add invariants◦synchronization mechanisms
inc() { int t; acquire(lock); t := x; t := t+1; x := t; release(lock);}
Example
acquire(lock) { assume lock==false; lock := true;}
release(lock) { lock := false;}
mutex P, x1, … , xn
mutex (lock==true), x
Tactic - mutexinc() { int t; acquire(lock); a=tid; t := x; t := t+1; x := t; release(lock); a=0;}
AUX-ANNOTATE
Invariant: lock==true iff a !=0
Tactic - mutexinc() { int t; acquire(lock); a=tid; t := x; t := t+1; x := t; release(lock); a=0;}
SIMULATE
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;}
Tactic - mutexinc() { int t; acquire(lock); a=tid; R assert a==tid;t := x; B t := t+1; B assert a==tid;x := t; B assert a==tid; release(lock); a=0; L}
Tactic - mutex
REDUCE & RELAX
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; }
OutlineMotivating examplesApproach – SoundnessReductionAbstractionBorrowing assertionsTacticsExperience / Conclusions
ExperienceImplementation
◦Boogie + parallel composition◦Verification conditions for validity of
each step: Z3 SMT SolverBenchmarks without complicated
global invariantsFine-grained locking
◦Multiset◦Hand-over-hand locking
Non-blocking algorithms
Future workMore tacticsMore synchronization
mechanismsC / Spec#Larger verification problems
ConclusionsA formal and sound proof calculus for
atomicity was presented Abstraction helps applying reduction
and the other way aroundAssertions can be added and checked
only laterThe program is simplified by obtaining
coarser atomic actionsTactics can be defined to represent
different synchronization mechanisms