Joint work with Peter Müller ETH Zurich Jan Smans KU Leuven Special thanks to Mike Barnett VMCAI Madrid Spain 18 January 2010 Verifying Concurrent Programs with Chalice Concurrent programs ID: 935483
Download Presentation The PPT/PDF document "K. Rustan M. Leino RiSE ," is the property of its rightful owner. Permission is granted to download and print the materials on this web site for personal, non-commercial use only, and to display it on your personal computer provided you do not modify the materials and that you retain all copyright notices contained in the materials. By downloading content from our website, you accept the terms of this agreement.
Slide1
K. Rustan M. Leino
RiSE, Joint work with:Peter Müller (ETH Zurich)Jan Smans (KU Leuven)Special thanks to Mike Barnett
VMCAI, Madrid, Spain, 18 January 2010
Verifying Concurrent Programs with Chalice
Slide2Concurrent programsInterleaving of thread executionsUnbounded number of: threads, locks, …We need some basis for doing the reasoningA way of thinking!
Slide3ChaliceExperimental language with focus on:Shared-memory concurrencyStatic verificationKey featuresMemory access governed by a model of permissionsSharing via locks with monitor invariantsCopy-free non-blocking channelsDeadlock checking, dynamic lock re-orderingOther featuresClasses; Mutual exclusion and readers/writers locks; Fractional permissions; Two-state monitor invariants; Asynchronous method calls; Memory leak checking; Logic predicates and functions; Ghost and prophecy variables
Slide4Dealing with memory (the heap)Access to a memory location requires permissionPermissions are held by activation recordsSyntax for talking about permission to y: acc(y)
Slide5Incdemo
Slide6Transfer of permissionsmethod Main(){
var c := new Counter; call c.Inc();
}
method
Inc()
requires acc
(y);
ensures
acc
(y);
{
y := y + 1;
}
acc
(
c.y
)
Slide7The two halves of a callcall == fork + join is semantically like … but is compiled to more efficient codecall
x,y := o.M(E, F);fork tk
:= o.M
(E, F);
join
x,y :=
tk;
Slide8Parallel Incdemo
Slide9Passing permissions to threadsclass XYZ {
var x: int; var y:
int
;
var
z: int
;
method Main()
{
var
c :=
new
XYZ;
fork
c.A
();
fork
c.B
();
}
…
}
method
A()
requires
acc(x);{ x := x + 1;}
method
B()
requires
acc
(y) &&
acc
(z);
{
y := y + z;
}
Slide10Read permissionsacc(y) write permission to yrd(y) read permission to yAt any one time, at most one thread can have write permission to a location
Slide11Passing permissions to threadsclass Fib {
var x: int; var y:
int
;
var
z: int
;
method Main()
{
var
c :=
new
Fib;
fork
c.A
();
fork
c.B
();
}
…
}
method
A()
requires
rd(x) && acc(y){
y := x + 21;
}
method
B()
requires
rd
(x) &&
acc
(z)
{
z := x + 34;
}
Slide12Fractional permissionsacc(y) 100% permission to yacc(y, p) p% permission to yrd(y) read permission to yWrite access requires 100%Read access requires >0% = +
acc(y)
acc
(y,69)
acc
(y,31)
rd
(y)
acc
(y,
)
Slide13Shared stateWhat if two threads want write access to the same location?method A() …{
y := y + 21;}method B() …{
y := y + 34;}
class
Fib
{
var y:
int;
method
Main()
{
var
c :=
new
Fib;
fork
c.A
();
fork
c.B
();
}
…
}
acc
(
c.y
)
?
Slide14Monitorsmethod A() …{
acquire this; y := y + 21; release
this
;}
method
B() …
{
acquire
this;
y := y + 34;
release
this
;
}
class
Fib
{
var
y:
int
;
invariant
acc
(y
);
method
Main()
{ var c := new Fib;
share
c;
fork
c.A
();
fork
c.B
();
}
…
}
acc
(
c.y
)
acc
(y)
Slide15Locks and permissionsThe conceptsholding a lock, andhaving permissions are orthogonal to one anotherIn particular:Holding a lock does not imply any right to read or modify shared variablesTheir connection is:Acquiring a lock obtains some permissionsReleasing a lock gives up some permissions
Slide16Monitor invariantsLike other specifications, monitors can hold both permissions and conditionsExample: invariant acc(y) && 0 ≤ y
acc
(y)
Slide17Owicki Gries counter[Chalice encoding by Bart Jacobs]demo
Slide18Abstractionclass MyClass {
var x,y: int; predicate
Valid {
acc(
c.x) &&
acc(
c.y) && x ≤
y }
…
}
Slide19Abstractionclass MyClass {
var x,y: int; predicate
Valid {
acc(
c.x) &&
acc(
c.y) && x ≤
y }
method New()
returns
(c:
MyClass
)
ensures
c.Valid
;
{
…
}
method
Mutate()
requires
c.Valid
;
ensures
c.Valid
; { … }}
Slide20Abstractionclass MyClass {
var x,y: int; predicate
Valid {
acc(
c.x) &&
acc(
c.y) && x ≤
y } method
New() returns
(c:
MyClass
)
ensures
c.Valid
;
{
var
c :=
new
MyClass
{ x := 3, y := 5 };
fold
c.Valid
;
}
method Mutate() requires c.Valid; ensures
c.Valid
;
{
unfold
c.Valid
;
c.y
:=
c.y
+ 3;
fold
c.Valid
;
}
}
acc
(
c.x
)
acc
(
c.y
)
x ≤ y
c.Valid
Slide21Abstractionclass MyClass {
var x,y: int; predicate
Valid {
acc(
c.x) &&
acc(
c.y) && x ≤
y } method
New() returns
(c:
MyClass
)
ensures
c.Valid
;
{
var
c :=
new
MyClass
{ x := 3, y := 5 };
fold
c.Valid
;
}
method Mutate() requires c.Valid; ensures
c.Valid
;
{
unfold
c.Valid
;
c.y
:=
c.y
+ 3;
fold
c.Valid
;
}
}
acc
(
c.x
)
acc
(
c.y
)
x ≤ y
c.Valid
c.Valid
Slide22Channelschannel Ch(c: Cell, z: int
) where acc(c.y) && c.y ≤ z;
Channelschannel Ch(c: Cell, z: int
) where acc(c.y) && c.y ≤ z;
class Cell {
var
x,y:
int;
method Producer(
ch: Ch
)
{
var
c :=
new
C { x := 0, y := 0 };
send
ch
(c, 5);
}
method
Consumer(
ch
:
Ch
)
{
receive
c,z
:= ch; … }}
acc
(
c.y
)
acc
(
c.x
)
Slide24Channelschannel Ch(c: Cell, z: int
) where acc(c.y) && c.y ≤ z;
class Cell {
var
x,y:
int;
method Producer(
ch: Ch
)
{
var
c :=
new
C { x := 0, y := 0 };
send
ch
(c, 5);
}
method
Consumer(
ch
:
Ch
)
{
receive
c,z
:= ch; … }}
acc
(
c.y
)
c.y
≤ z
Slide25Preventing deadlocksDeadlocks are prevented by making sure no such cycle can ever occurThe program partially order locksThe program is checked to acquire locks in strict ascending orderA deadlock is the situation where a nonempty set (cycle) of threads each waits for a resource (e.g., lock) that is held by another thread in the set
Slide26Wait orderWait order is a dense partial order(Mu, <<) with a bottom element << is the strict version of <<The wait level of an object o is stored in a mutable ghost field o.muAccessing o.mu requires appropriate permissions, as for other fields
Slide27Example: Avoiding deadlocksWith these preconditions, both methods verifyThe conjunction of the preconditions is false, so the methods can never be invoked at the same timemethod
M(){ acquire
a;
acquire
b;
…}
method
N()
{
acquire
b;
acquire
a;
…
}
requires
rd
(a.mu
);
requires
rd
(b.mu);
requires
rd
(a.mu)
requires
rd
(b.mu)
requires
waitlevel
<<
b.mu;
requires
b.mu << a.mu;
requires
waitlevel
<<
a.mu;
requires
a.mu << b.mu;
Slide28Setting the wait orderRecall, the wait level of an object o is stored in the ghost field o.muInitially, the .mu field is The .mu field is set by the share statement: picks some wait level strictly between
L and H, and sets o.mu to that levelProvided L << H and neither denotes an extreme element, such a wait level exists, since the order is denseChanging o.mu requires acc(o.mu), as usual
share
o
between L
and H;
Slide29Formal semantics of ChaliceGiven as translation to BoogieChalice
Z3
Boogie
Boogie is an intermediate verification language
Slide30Encoding the heapChalice: o.fBoogie: Heap[ o, f ]where Heap is declared to be a map fromobjects and field names to valuesTo encode permissions, use another map: Mask
Slide31Encoding a call call M() = Exhale[[ P ]]; Inhale[[ Q ]]
method M() requires P; ensures Q;
Slide32Exhale and InhaleDefined by structural inductionFor expression P without permission predicatesExhale P ≡ assert PInhale P ≡ assume PExhale acc(o.f, p)
≡ assert p ≤ Mask[o,f]; Mask[o,f] := Mask[o,f] – p;Inhale acc(o.f, p) ≡ if (Mask[o,f] == 0) {
havoc Heap[o,f]; }
Mask[o,f] := Mask[o,f] + p;
Slide33Examplecall M()=
assert 100 ≤ Mask[this,y];Mask[this,y] := Mask[this,y] – 100;oldH := Heap;if (Mask[this,y] = 0) { havoc Heap[this,y]; }Mask[this,y] := Mask[this,y] + 100;
assume Heap[this,y] = oldH
[this,y] * oldH [this,y
];
class
Cell {
var y:
int;
method
Square()
requires
acc
(y);
ensures
acc
(y) && y =
old
(y*y
);
Slide34Boogie translation demodemo
Slide35An alternative to oldLogical constant: Let the verifier figure out K!method
Square(c: Cell) requires acc(c.y);
ensures
acc(
c.y) &&
c.y ==
old(c.y
*c.y);
method
Square(c: Cell,
ghost
K:
int
)
requires
acc
(
c.y
) && K ==
c.y
;
ensures
acc
(
c.y
) &&
c.y
== K*K;
call
Square(c,
c.y);call Square(c, *);
Slide36Square only reads c.ymethod Square(c: Cell
) returns (r: int) requires
acc(
c.y, ε
);
ensures
acc(
c.y, ε
) && r == c.y
*
c.y
;
method
Square(c: Cell)
returns
(r:
int
)
requires
acc
(
c.y
);
ensures
acc
(
c.y
) && r ==
c.y
*
c.y;method Square(c: Cell, ghost K: perm
)
returns
(r:
int
)
requires
acc
(
c.y
, K);
ensures
acc
(
c.y
, K) && r ==
c.y
*
c.y
;
ε
loses procedural abstraction
Slide37Language questionsA better notation? rd(c.y)? Does that pick one K for each method activation?Can these 3 kinds of “parameters” (real, ghost, permission) be treated more uniformly?
Which kinds should be indicated explicitly by the programmer and which should be figured out by the compiler?method Square(c: Cell, ghost K: perm
)
returns
(r:
int)
requires
acc(c.y
, K);
ensures
acc
(
c.y
, K) && r ==
c.y
*
c.y
;
Slide38Chalice summaryPermissions guide what memory locations are allowed to be accessedActivation records can hold permissionsPermissions can also be stored in various “boxes” (monitors, predicates, channels)Permissions can be transferred between activation records and boxesLocks grant mutually exclusive access to monitors
Slide39Try it for yourselfChalice (and Boogie) available as open source:http://boogie.codeplex.com Tutorial and other papers available from:http://research.microsoft.com/~leino