A Calculus of Atomic Actions Tayfun Elmas, Shaz

A Calculus of Atomic Actions
Tayfun Elmas, Shaz Qadeer and Serdar Tasiran
POPL ‘09
236825 – Seminar in Distributed Algorithms
Cynthia Disenfeld
27/05/2013
*Some slides are based on those of the authors
Goal

Statically 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
Outline
Motivating examples
 Approach – Soundness
 Reduction
 Abstraction
 Borrowing assertions
 Tactics
 Experience / Conclusions

Motivating examples
Lock-based atomic increment
void inc() {
int t;
acquire(lock);
t := x;
t := t+1;
x := t;
release(lock);
}
void inc() {
int t;
[havoc t;
x := x+1;]
}
Motivating examples
Lock-free atomic increment
void inc() {
void inc() {
int t;
int t;
while(true){
while(*){
t := x;
t := x;
if (CAS(x,t,t+1)
assume x!=t;
break;
}
}
t := x;
}
[assume x==t;
x := t+1];
}
* Transformation from Flanagan et al.[2005]
Motivating examples
Lock-free atomic increment
void inc() {
}
void inc() {
int t;
int t;
while(*){
while (*) {
t := x;
havoc t;
assume x!=t;
skip;
}
}
t := x;
havoc t;
[assume x==t;
[assume x==t;
x := t+1];
x := t+1];
}
Motivating examples
Lock-free atomic increment
void inc() {
void inc() {
int t;
void inc() {
int t;
int t;
havoc t;
[havoc t;
while (*) {
havoc t;
skip;
x := x+1];
}
havoc t;
havoc t;
[assume x==t;
[assume x==t;
x := t+1];
}
x := t+1];
}
}
Motivating examples
Client of inc
void add(int n){
void add(int n){
while (0<n) {
while (0<n){
[assert 0<=n;
inc()
[x := x+1]
x := x+n;
n := n-1;
n := n-1;
n := 0];
}
}
void add(int n){
}
}
}
Outline
Motivating examples
 Approach – Soundness
 Reduction
 Abstraction
 Borrowing assertions
 Tactics
 Experience / Conclusions

QED approach
 P1, I1 
Proof step
 P2 , I2 
Program
Fine-grained concurrency
Hard to prove!
Invariant = True
If the original program may fail
...
 Pn , I n 
Invariant
Coarse-grained concurrency
Easy to prove
the new program may fail
Soundness
I ├ P1
P2
For all s1 ├ I
If
P1
P2
s1  error then exists s1  error
P1
If
P2
s1  s2 then exists s1  s2
or
P2
s1  error
Soundness

For each proof step
 P1, I1 
I 2 ├ P1
 P2 , I2 
P2
Proof steps:
•Invariant strengthening
•Reduction: build more coarse-grained atomic blocks
•Abstraction: add (possibly failing) behaviors
Outline
Motivating examples
 Approach – Soundness
 Reduction
 Abstraction
 Borrowing assertions
 Tactics
 Experience / Conclusions

Reduction
inc() {
main() {
int t;
x := 0;
acquire(lock);
inc(); || inc();
t := x;
t := t+1;
x := t;
release(lock);
}
assert(x==2);
}
Right Mover
Reduction

inc() {
int t;
R
acquire(lock);
t := x;
B
t := x;
t := t+1;
B
t := t+1;
x := t;
B
x := t;
release(lock);

inc() {
int t;
acquire(lock);


L
}
release(lock);
}
inc()
REDUCE-SEQUENTIAL
x := x+1


Reduction
main() {
x := 0;
inc(); || inc();
assert(x==2);
}
INLINE-CALL
main() {
x := 0;
B x := x+1; || x := x+1; B
assert(x==2);
}
REDUCE-PARALLEL
main() {
x := 0;
x := x+1;
x := x+1;
assert(x==2);
}
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?
Outline
Motivating examples
 Approach – Soundness
 Reduction
 Abstraction
 Borrowing assertions
 Tactics
 Experience / Conclusions

Abstraction rule

Replace
 with  if
I ├ 
For all s1 ├ I
If


s1  error then exists s1  error


If
s1  s2 then exists s1  s2

or
s1  error
Abstraction
void inc() {
void inc() {
int t;
while(*){
int t;
while (*) {
SIMULATE
t := x;
havoc t;
assume x!=t;
skip;
}
}
t := x;
havoc t;
[assume x==t;
[assume x==t;
x := t+1];
x := t+1];
}
Then, we can reduce
• havoc t + skip
• while (*){…}  havoc t
}
Abstraction

Adding 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
Outline
Motivating examples
 Approach – Soundness
 Reduction
 Abstraction
 Borrowing assertions
 Tactics
 Experience / Conclusions

Example – Sorted linked list
Hand-over-hand locking
 Find, insert, delete
 Local assertions
 Class invariant
 Atomic  easy!
 But… implementation with fine-grained
locking

Insert(5)
Insert(x)
p := find(x); //locks p
n := 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.val and p.next are not
affected by other threads
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);
t.val and t.next are not
affected by other threads
Proof
 P1, I1 
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);
 P2 , I2 
...
 Pn , I n 
find appropriate p
LOCK(p)
n := p.next;
t := new Node();
t.val := x;
t.next := n;
R
p.next := t;
assert (p, t, n sorted);
UNLOCK(p);
L
Proof
 P1, I1 
 P2 , I2 
find appropriate p
LOCK(p)
n := p.next;
t := new Node();
t.val := x;
t.next := n;
...
 Pn , I n 
find appropriate p
LOCK(p)
n := p.next;
R
t := new Node();
t.val := x;
t.next := n;
p.next := t;
assert (p, t, n sorted);
p.next := t;
assert (p, t, n sorted);
UNLOCK(p);
UNLOCK(p);
how to continue?
L
Apparent interference
Thread A
Thread B
p.next := t;
n := p.next;
n := p.next;
p.next := t;
Apparent interference
Thread A
Thread B
p.next := t;
n := p.next;
n := p.next;
p.next := t;
But: both p’s are locked!
Ruling out interference - 1
Thread A
Thread B
assert owner[p]==A
p.next := t;
assert owner[p]==B
n := p.next;
assert owner[p]==B
n := p.next;
assert owner[p]==A
p.next := t;
Ruling out interference - 2
Thread A
Thread B
assert !inList[t]
t.next := n;
assert inList[p]
n := p.next;
assert inList[p]
n := p.next;
assert !inList[t]
t.next := n;
Reduction after abstraction
find appropriate p
LOCK(p)
find appropriate p
LOCK(p)
assert inList[p]&&owner[p]==tid
n := p.next;
t := new Node();
t.val := x;
assert !inList[t]
t.next := n;
assert inList[p]&&owner[p]==tid
p.next := t;
assert (p, t, n sorted);
assert owner[p]==tid
UNLOCK(p);
assert inList[p]&&owner[p]==tid
n := p.next;
t := new Node();
t.val := x;
assert !inList[t]
t.next := n;
assert inList[p]&&owner[p]==tid
p.next := t;
assert (p, t, n sorted);
assert owner[p]==tid
UNLOCK(p);
Borrowed assertions
find appropriate p
LOCK(p)
find appropriate p
LOCK(p)
assert inList[p]&&owner[p]==tid
n := p.next;
t := new Node();
t.val := x;
assert !inList[t]
t.next := n;
assert inList[p]&&owner[p]==tid
p.next := t;
assert (p, t, n sorted);
assert owner[p]==tid
UNLOCK(p);
assert inList[p]&&owner[p]==tid
n := p.next;
t := new Node();
t.val := x;
assert !inList[t]
t.next := n;
assert inList[p]&&owner[p]==tid
p.next := t;
assert (p, t, n sorted);
assert owner[p]==tid
UNLOCK(p);
Completing the proof
Invariant : List is sorted
find appropriate p
LOCK(p)
n := p.next;
t := new Node();
t.val := x;
t.next := n;
p.next := t;
assert (p, t, n sorted);
UNLOCK(p);
Outline
Motivating examples
 Approach – Soundness
 Reduction
 Abstraction
 Borrowing assertions
 Tactics
 Experience / Conclusions

Tactics

High-level strategies  multiple rule
proofs
◦ abstract from a read, write
◦ add invariants
◦ synchronization mechanisms
Example
mutex P, x1, … , xn
mutex (lock==true), x
inc() {
int t;
acquire(lock);
t := x;
t := t+1;
x := t;
release(lock);
}
acquire(lock) {
assume lock==false;
lock := true;
}
release(lock) {
lock := false;
}
Tactic - mutex
inc() {
int t;
acquire(lock); a=tid;
t := x;
t := t+1;
x := t;
release(lock); a=0;
}
Invariant: lock==true iff a !=0
AUX-ANNOTATE
Tactic - mutex
inc() {
inc() {
int t;
int t;
acquire(lock); a=tid;
acquire(lock); a=tid;
t := x;
assert a==tid;t := x;
t := t+1;
t := t+1;
x := t;
assert a==tid;x := t;
release(lock); a=0;
assert a==tid;
}
release(lock); a=0;
}
SIMULATE
Tactic - mutex
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;
}
R
B
B
B
L
Tactic - mutex
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;
}
REDUCE & RELAX
Outline
Motivating examples
 Approach – Soundness
 Reduction
 Abstraction
 Borrowing assertions
 Tactics
 Experience / Conclusions

Experience

Implementation
◦ Boogie + parallel composition
◦ Verification conditions for validity of each
step: Z3 SMT Solver
Benchmarks  without complicated
global invariants
 Fine-grained locking

◦ Multiset
◦ Hand-over-hand locking

Non-blocking algorithms
Future work
More tactics
 More synchronization mechanisms
 C / Spec#
 Larger verification problems

Conclusions
A formal and sound proof calculus for
atomicity was presented
 Abstraction helps applying reduction and
the other way around
 Assertions can be added and checked
only later
 The program is simplified by obtaining
coarser atomic actions
 Tactics can be defined to represent
different synchronization mechanisms
