Reduction, abstraction, and atomicity: How much can we prove about concurrent programs using them? Serdar Tasiran Koç University Istanbul, Turkey Tayfun.

Slides:



Advertisements
Similar presentations
Advanced programming tools at Microsoft
Advertisements

Automated Theorem Proving Lecture 1. Program verification is undecidable! Given program P and specification S, does P satisfy S?
The many faces of TM Tim Harris. Granularity Distributed, large-scale atomic actions Composable shared memory data structures Leaf shared memory data.
QED: A Simplifier for Concurrent Programs Shaz Qadeer Microsoft Research Joint work with Tayfun ElmasAli SezginSerdar Tasiran.
Hongjin Liang and Xinyu Feng
Transaction Management: Concurrency Control CS634 Class 17, Apr 7, 2014 Slides based on “Database Management Systems” 3 rd ed, Ramakrishnan and Gehrke.
A Program Transformation For Faster Goal-Directed Search Akash Lal, Shaz Qadeer Microsoft Research.
50.530: Software Engineering Sun Jun SUTD. Week 13: Rely-Guarantee Reasoning.
Architecture-aware Analysis of Concurrent Software Rajeev Alur University of Pennsylvania Amir Pnueli Memorial Symposium New York University, May 2010.
Goldilocks: Efficiently Computing the Happens-Before Relation Using Locksets Tayfun Elmas 1, Shaz Qadeer 2, Serdar Tasiran 1 1 Koç University, İstanbul,
1 1 Regression Verification for Multi-Threaded Programs Sagar Chaki, SEI-Pittsburgh Arie Gurfinkel, SEI-Pittsburgh Ofer Strichman, Technion-Haifa Originally.
A Rely-Guarantee-Based Simulation for Verifying Concurrent Program Transformations Hongjin Liang, Xinyu Feng & Ming Fu Univ. of Science and Technology.
Model-based reasoning meets code verification Michael Butler 21 May 2014 WG 2.3 Meeting 55, Orlando.
A simple sequential reasoning approach for sound modular verification of mainstream multithreaded programs Wolfram Schulte & Bart Jacobs Microsoft Research.
1 Operating Systems, 122 Practical Session 5, Synchronization 1.
Parallel Processing (CS526) Spring 2012(Week 6).  A parallel algorithm is a group of partitioned tasks that work with each other to solve a large problem.
Part IV: Exploiting Purity for Atomicity. Busy Acquire atomic void busy_acquire() { while (true) { if (CAS(m,0,1)) break; } } CAS(m,0,1) (fails) (succeeds)
Atomicity in Multi-Threaded Programs Prachi Tiwari University of California, Santa Cruz CMPS 203 Programming Languages, Fall 2004.
/ PSWLAB Atomizer: A Dynamic Atomicity Checker For Multithreaded Programs By Cormac Flanagan, Stephen N. Freund 24 th April, 2008 Hong,Shin.
Verifying Concurrent Programs with Relaxed Conflict Detection Tayfun Elmas, Ismail Kuru, Serdar Taşıran, Omer Subasi Koç University Istanbul, Turkey.
Generalizing Reduction and Abstraction to Simplify Concurrent Programs: The QED Approach Shaz Qadeer Microsoft Research Redmond, WA Serdar Taşıran Serdar.
Summarizing Procedures in Concurrent Programs Shaz Qadeer Sriram K. Rajamani Jakob Rehof Microsoft Research.
1 Operational Semantics Mooly Sagiv Tel Aviv University Textbook: Semantics with Applications.
Modular Verification of Multithreaded Software Shaz Qadeer Compaq Systems Research Center Shaz Qadeer Compaq Systems Research Center Joint work with Cormac.
ESC Java. Static Analysis Spectrum Power Cost Type checking Data-flow analysis Model checking Program verification AutomatedManual ESC.
Thread-Modular Verification Shaz Qadeer Joint work with Cormac Flanagan Stephen Freund Shaz Qadeer Joint work with Cormac Flanagan Stephen Freund.
Verifying Commit-Atomicity Using Model Checking Cormac Flanagan University of California, Santa Cruz.
Operational Semantics Semantics with Applications Chapter 2 H. Nielson and F. Nielson
Part II: Atomicity for Software Model Checking. Class Account { int balance; static int MIN = 0, MAX = 100; bool synchronized deposit(int n) { int t =
Comparison Under Abstraction for Verifying Linearizability Daphna Amit Noam Rinetzky Mooly Sagiv Tom RepsEran Yahav Tel Aviv UniversityUniversity of Wisconsin.
C. FlanaganType Systems for Multithreaded Software1 Cormac Flanagan UC Santa Cruz Stephen N. Freund Williams College Shaz Qadeer Microsoft Research.
Compositional Verification of Termination-Preserving Refinement of Concurrent Programs Hongjin Liang Univ. of Science and Technology of China (USTC) Joint.
CS510 Concurrent Systems Introduction to Concurrency.
Runtime Refinement Checking of Concurrent Data Structures (the VYRD project) Serdar Tasiran Koç University, Istanbul, Turkey Shaz Qadeer Microsoft Research,
Verifying Concurrent Programs with Relaxed Conflict Detection Tayfun Elmas, Ismail Kuru, Serdar Taşıran, Omer Subasi Koç University Istanbul, Turkey.
Aditya V. Nori, Sriram K. Rajamani Microsoft Research India.
Optimistic Design 1. Guarded Methods Do something based on the fact that one or more objects have particular states  Make a set of purchases assuming.
Eran Yahav 1. Previously…  An algorithmic view  Abstract data types (ADT)  Correctness Conditions  Sequential consistency  Linearizability  Treiber’s.
Reasoning about programs March CSE 403, Winter 2011, Brun.
Concurrent Linked Lists and Linearizability Proofs Companion slides for The Art of Multiprocessor Programming by Maurice Herlihy & Nir Shavit Modified.
Automated and Modular Refinement Reasoning for Concurrent Programs Shaz Qadeer.
A Simple Optimistic skip-list Algorithm Maurice Herlihy Brown University & Sun Microsystems Laboratories Yossi Lev Brown University & Sun Microsystems.
Motivation  Parallel programming is difficult  Culprit: Non-determinism Interleaving of parallel threads But required to harness parallelism  Sequential.
Operational Semantics Mooly Sagiv Tel Aviv University Textbook: Semantics with Applications Chapter.
/ PSWLAB Thread Modular Model Checking by Cormac Flanagan and Shaz Qadeer (published in Spin’03) Hong,Shin Thread Modular Model.
A Compositional Method for Verifying Software Transactional Memory Implementations Serdar Tasiran Koç University Istanbul, Turkey Thanks: Rustan Leino,
A Calculus of Atomic Actions Tayfun Elmas, Shaz Qadeer and Serdar Tasiran POPL ‘ – Seminar in Distributed Algorithms Cynthia Disenfeld 27/05/2013.
Verifying Transactional Programs with Programmer-Defined Conflict Detection Omer Subasi, Serdar Tasiran (Koç University) Tim Harris (Microsoft Research)
Program Analysis and Verification
Operational Semantics Mooly Sagiv Tel Aviv University Sunday Scrieber 8 Monday Schrieber.
CS510 Concurrent Systems Jonathan Walpole. Introduction to Concurrency.
Simplifying Linearizability Proofs Using Reduction and Abstraction Serdar Tasiran Koc University, Istanbul, Turkey Tayfun Elmas, Ali Sezgin, Omer Subasi.
Operational Semantics Mooly Sagiv Reference: Semantics with Applications Chapter 2 H. Nielson and F. Nielson
Operational Semantics Mooly Sagiv Reference: Semantics with Applications Chapter 2 H. Nielson and F. Nielson
A Calculus of Atomic Actions Serdar Tasiran Koc University, Istanbul, Turkey Tayfun ElmasShaz Qadeer Koc University Microsoft Research.
Verifiable Programming Reason about imperative sequential programs such as Java Imperative program –defines state space defined by collection of typed.
Håkan Sundell Philippas Tsigas
Faster Data Structures in Transactional Memory using Three Paths
Graph-Based Operational Semantics
Joint work with Yong Li, Xinyu Feng, Zhong Shao and Yu Zhang
Verification of Concurrent Programs
Over-Approximating Boolean Programs with Unbounded Thread Creation
A Refinement Calculus for Promela
CSE 451: Operating Systems Autumn 2003 Lecture 7 Synchronization
CSE 451: Operating Systems Autumn 2005 Lecture 7 Synchronization
CSE 451: Operating Systems Winter 2003 Lecture 7 Synchronization
CIS 720 Lecture 3.
CIS 720 Lecture 3.
COP4020 Programming Languages
Program Analysis and Verification
Presentation transcript:

Reduction, abstraction, and atomicity: How much can we prove about concurrent programs using them? Serdar Tasiran Koç University Istanbul, Turkey Tayfun Elmas Shaz Qadeer Ali Sezgin Koç University Microsoft Research Koç University Istanbul, Turkey Redmond, WA Istanbul, Turkey

Outline QED proof system (Cartoon illustration) QED overview Half-baked part: Backward reasoning in time Prophecy variables, “tressa” annotations 2 2

3 Example: increment acquire (lock); t1 := x; t1:= t1 + 1; x := t1; release (lock); x := 0; || assert (x == 2); acquire (lock); t2 := x; t2 := t2 + 1; x := t2; release (lock); 3

4 Proof with “fine grain” actions A: x=0, L0: acquire(l); x=0, held(l,A)> L1: t1 := x; x=0, held(l,A), t1=x> L2: t1 := t1 + 1; x=0, held(l,A), t1=x+1> L3: x := t1; x=1, held(l,A)> L4: release(l) x=1, B: x=0, L0: acquire(l); x=0, held(l,B)> L1: t2 := x; x=0, held(l,B), t2=x> L2: t2 := t2 + 1; x=0, held(l,B), t2=x+1> L3: x := t2; x=1, held(l,B)> L4: release(l) x=1, || 4

5 QED Proof of Increment (Cartoon 1) inc (): acquire (lock); t1 := x; t1 := t1 + 1; x := t1; release(lock); Right mover Both mover B B Left mover inc (): acquire (lock); t1 := x; t1 := t1 + 1; x := t1; release(lock); inc (): x := x + 1; REDUCE-SEQUENTIAL 5

6 QED Proof of “increment” (Cartoon 2) Main: x := 0; inc() || inc() assert (x == 2) Main: x := 0; x := x + 1 || x := x + 1 assert (x == 2) B B INLINE-CALL REDUCE-PARALLEL Main: x := 0; x := x + 1; assert (x == 2) 6

The QED approach Soundness theorem: Starting from a state s i in I n P 1 has an assertion violation  P n has an assertion violation P 1 can go to final state s f  P n can go to final state s f or has an assertion violation. Difficult to prove Fine-grain concurrency Annotations at every interleaving point Easy to prove Larger atomic blocks Local, sequential analysis within atomic blocks 7 (P 1, I 1 ) ...  (P i, I i ) ...  (P n, I n ) ProgramInvariant 7

Outline QED idea QED overview Half-baked part: Backward reasoning in time Prophecy variables, “tressa” annotations 8 8

Programs Syntax: Gated action 9 S ::= assume e | assert e | x := e | havoc x | S ; S | if (e) then S else S | while (e) do S | proc(a, out b) | S || S | [ S ] Syntax in code examples: Semantics: A collection of threads and a global store Non-deterministically pick a thread and execute one atomic step Failed assert makes the thread and program go wrong A distinguished state “error” Failed assume blocks the executing thread

10 Gated actions x = x + 1; Transition: Two-store relation Gate: Assertion on pre-state 10

11 Gated actions – examples assert (x != 0); y = y / x; x = x + 1; assert (x != 0); y = y / x; assume (x != 0); y = y / x; Transition: Two-store relation Gate: Assertion on pre-state 11

12 Verifying the program Proof succeeds when all executions of starting from states in satisfy all assertions. Sufficient condition: For all actions in the program, Actions “large enough” to establish assertions within themselves x := 0; x := x + 1; assert (x == 2) 12

Rule 1: Strengthen invariant I,PI’,P I’  I All statements in P must preserve I’. 13

Rule 2: Abstract program I,PI,P’ P’ : Atomic statement [ S ] in P replaced with [ S’ ] Atomic statement [S’] abstracts statement [S] 14

15 Abstracting Actions  If for all : error s1s1 s1s1 1. If then s1s1 2. If then s2s2 s1s1 s2s2 or error s1s1 s1s1      –Going wrong more often is sound for assertion checking abstracted by 15

16 Flavors of Abstraction if (x == 1) y := y + 1; if ( * ) y := y + 1; Adding non-determinism Adding assertions t := x; havoc t; assume x != t; skip; assert (lock_owner == tid); x := t + 1; 16

Rule 3: Reduce program [ S1; S2] [ S1 ] ; [ S2 ] [ S1 ] || [ S2 ] I,PI,P’ 17

S1S1 S2S2 S3S3 acquirey S1S1 T2T2 S3S3 y S1S1 T2T2 S3S3 releasex S1S1 S2S2 S3S3 x Right and left movers (Lipton 1975) 18

19 Static mover check Right mover: Commutes to the right of any other action run by a different thread Static right-mover check for  : For every action  in program: (run by different thread)     19

20 Static mover check Static right-mover check between  and  : Simple cases –Mover check passes:  and  access different variables  and  disable each other –Fails:  writes to a variable and  reads it  and  both write to a variable, writes do not commute   20

21 Reduction  ;  ;  ...   1   2 ...   n   ;  ...  right-mover: For each execution: Exist equivalent executions:...     1   2 ...   n      1     2 ...   n      1   2 ...     n   ...  ;  21

22 Static mover check: a subtlety Static right-mover check between  and  : Consider such that No execution reaching s 1 executes  followed by  Do not need to do mover check for state pairs starting with s 1   s1s1 error s1s1  22

23 Increment: Proof by reduction acquire (lock); t1 := x; t1 := t1 + 1; x := t1; release(lock); R B B B L acquire (lock); t1 := x; t1 := t1 + 1; x := t1; release(lock); REDUCE-SEQUENTIAL 23

24 Static mover check fails: Apparent conflict acquire (lock); t1 := x; t1 := t1 + 1; x := t1; release(lock); acquire (lock); t2 := x; t2 := t2 + 1; x := t2; release(lock); Static mover check is local, fails! Individual actions do not locally contain the information: “Whenever this action executes, this thread holds the lock” Annotate action with local assertion: Express belief about non-interference 24

25 Auxiliary variable: Which thread holds the lock? inc (): acquire (lock); t1 = x; t1 = t1 + 1 x = t1; release(lock); inc (): acquire (lock); a := tid; t2 = x; t2 = t2 + 1 x = t2; release(lock); a := 0; AUX-ANNOTATE New invariant: (lock == true)  (a != 0) Auxiliary variable a is a history variable Summarizes relevant part of execution history 25

26 Annotating Actions with Assertions acquire (lock); a := tid; assert a == tid; t1 = x; t1 = t1+ 1 assert a == tid; x = t1; assert a == tid; release(lock); a := 0; acquire (lock); a := tid; t1= x; t1 = t1 + 1 x = t1; release(lock); a := 0; ABSTRACT Invariant: (lock == true)  (a != 0) Assertions indicate belief about non interference Annotate actions locally with global information about execution 26

History Variable Annotations Make Static Mover Check Pass 27 Thread 1 acquire (lock); a := tid1; assert a == tid1; t1 := x; t1 := t1 + 1 assert a == tid1; x := t1; assert a == tid1; release(lock); a := 0; R B B B L Thread 2 acquire (lock); a := tid2; assert a == tid2; t2 := x; t2 := t2 + 1 assert a == tid2; x := t2; assert a == tid2; release(lock); a := 0; assert a == tid1; x := t1; and assert a == tid2; x := t2; commute α  β β  α Because both α  β and β  α result in assertion violations. 27

28 Borrowing and paying back assertions inc (): acquire (lock); a := tid; assert a == tid; t1 = x; t1 = t1 + 1 assert a == tid; x = t1; assert a == tid; release(lock); a := 0; inc (): acquire (lock); a := tid; assert a == tid; t1 = x; t1 = t1 + 1 assert a == tid; x = t1; assert a == tid; release(lock); a := 0; REDUCE-SEQUENTIAL, DISCHARGE ASSERTIONS R B B B L Discharges the assertions Invariant: (lock == true)  (a != 0) 28

29 : Example: Ruling out apparent interference assert !possiblyInList[t1]; t1.next := n1; assert possiblyInList[p2]; n2 := p2.next; possiblyInList[t] : False when a newly created node assigned to t. Set to true when p.next := t for some p. Remains true afterwards. assert possiblyInList[p2]; n2 := p2.next; assert !possiblyInList[t1]; t1.next := n1;   p2 and t1 refer to the same node: LHS and RHS lead to assertion violations. Otherwise, no conflict. 29

Increment with CAS 30 t1 := x; s1 := CAS(x,t1,t1+1); t2 := x; s2 := CAS(x,t2,t2+1); || havoc t1; s1 := CAS(x,t1,t1+1); [ if (*) { s1:=false; } else { x:=x+1; s1:= true; } ] 30

QED-verified examples Fine-grained locking Linked-list with hand-over-hand locking [Herlihy-Shavit 08] Two-lock queue [Michael-Scott 96] Non-blocking algorithms Bakery [Lamport 74] Non-blocking stack [Treiber 86] Obstruction-free deque [Herlihy et al. 03] Non-blocking stack [Michael 04] Writer mode of non-blocking readers/writer lock [Krieger et al. 93] Non-blocking queue [Michael-Scott 96] Synchronous queue [Scherer-Lea-Scott 06] 31

Outline QED proof system QED overview Half-baked part: Backward reasoning in time Prophecy variables, “tressa” annotations 32

33 Static Reduction Proofs and the Future –Challenge in some QED proofs Different reduction proof needed for different execution futures –Example: Optimistic concurrency Proceed assuming non-interference Abort, undo and/or retry if interference detected ➡ Prophecy variables and backwards reasoning in QED

Example: Funny Set procedure Insert(x: data) returns success: bool; { havoc i; assume 0<=i<n; // Start from arbitrary // array slot cnt := 0; success := false; while ( cnt<n && !success) { if (q[i]==-1) { q[i] := x; success := true; } else if (q[i]== x) { success := true; } else { i := (i+1) mod n; cnt := cnt+1; } } 34

Set Lookup procedure Lookup(x: data) returns found: bool; { found := false; i := 0; while (i<n && !found) { found := (q[i] == x); i := i+1; } return found; } 35

Set Lookup procedure Lookup(x: data) returns found: bool; { [ found := false; i := 0; ] while (*) { [ assume(i<n && !found); found := (q[i] == x); i := i+1; ] } [ return found;] } 36 chk(i,x):

Case 1: Lookup returns false chk(0,x) chk(1,x) chk(2,x)... chk(k-1,x)... chk(n-1,x) 37 chk(i,x): no x in slot q[i] Intuition: chk(i,x) should be a left mover for all i. q[i]:= x may come after chk(i,x) So, chk(i,x) not a right mover. No q[i]:= x can come before chk(i,x) Looks like a left mover.

Case 2: Lookup returns true chk(0,x) chk(1,x) chk(2,x)... chk(k-1,x) q[k] := x chk(k,x) 38 chk(k,x): Slot q[i] has x Cannot be a left-mover Does not commute to the left of q[i]:= x Is a right mover (no deletes) Need all earlier chk(i,x) to be right movers. Dilemma: What is the mover type of chk(i,x) ?

Code Duplication procedure Lookup(x: data) returns found: bool; { [ found := false; i := 0; ] while (*) { chkL(i,x);// Left // mover } assume !found; [ return found;] } 39 { [ found := false; i := 0; ] while (*) { chkR(i,x);// Right // mover } assume found; [ return found;] } ☐

Failing Lookup: chk(i,x) 40 assume(q[i]==-1); q[i]:= y; found := (q[i] == x); is not simulated by then From an initial state with q[i] == -1 and y == x –LHS yields found == true –RHS yields found == false chkL(i,x): is not a left mover. –Annotate chkL to say “I am part of a failing execution of Lookup.” found := (q[i] == x); assume(q[i]==-1); q[i]:= y; chk(i,x)

Failing Lookup: chk(i,x) { [ found := false; i := 0; ] while (*) { chkL(i,x);// Left // mover } assume !found; [ return found;] } 41 –We would like to say “This copy of the action only occurs in executions in which found is false.” –[chk(i,x); assert !found;] does not work. –Cannot discharge assertion. –Prefix of execution does not guarantee !found.

tressa: Temporal dual of assert { [ found := false; i := 0; ] while (*) { chkL(i,x);// Left // mover } assume !found; [ return found;] } 42 Postfix of execution justifies !found Code split has produced artificial executions that block when they get to assume !found Annotate chkL(i,x) to say: “Unless !found is true, this is an artificial execution that blocks before Lookup returns.” [ chkL(i,x); tressa(!found);]

tressa (pictorial) semantics Tressa violation in this execution fragment if  (s) is false. 43 …… [ … tressa  ] All future events  refers to have happened. s …

Abstraction and Mover Checks with tressa’s [assert a 1 ; τ 1 ; tressa p 1 ] [assert a 2 ; τ 2 ; tressa p 2 ] Preserve assert violations: a 2 a 1 Preserve tressa violations: p 2 p 1 Forward simulate or replace with assert violation: τ 1 (s,s’) τ 2 (s,s’) ∨ a 2 (s) Backward simulate or replace with tressa violation: τ 1 (s,s’) τ 2 (s,s’) ∨ p 2 (s’) Does α commute to the right of β ? α  β β  α 44

Failing Lookup { [ found := false; i := 0; ] while (*) { chkL(i,x);// Left // mover } assume !found; [ return found;] } 45 { [ found := false; i := 0; ] while (*) { [ chkL(i,x); tressa(!found);] } assume !found; [ return found;] }

Failing Lookup 46 q[i]:= y; found :=(q[i] == x); tressa !found q[i]:= y; simulated by then found :=(q[i] == x); tressa !found In mover checks, can ignore scenarios where, on the LHS the tressa is violated Only worry about (s1,s3) if s3 satisfies !found –Then x != y –Simulation holds! s1 s2 q[i]:= y; found :=(q[i] == x); tressa !found s3

Discharging tressa’s: Backwards Reasoning 47 while (*) { [ chkL(i,x); tressa(!found);] } assume !found; found := \Exists i: 0<=i<n && q[i]==x; tressa(!found); assume !found; tressa’s discharged by backwards reasoning within an atomic block.

Discharging tressa’s: Backwards Reasoning assume ’s are like assignments in the reverse direction in time: [havoc x; x := 2;] A set of transitions (x, x’) where x is arbitrary and x’ is 2. [assume x == 2; havoc x;] A set of transitions (x, x’) where x’ is arbitrary and x is 2. Denoted x =: 2 “Reverse assignment” 48

49 Prophecy Variables acquire (lock); p =: 0 t = x; t = t + 1 x = t; release(lock); p =: tid; acquire (lock); p =: 0 t = x; tressa p == tid; t = t + 1 x = t; tressa p == tid; release(lock); p =: tid; R B B B L

50 Prophecy variables and tressas acquire (lock); p =: 0; t = x; tressa p == tid; t = t + 1 x = t; tressa p == tid; release(lock); p =: tid; acquire (lock); p =: 0 t = x; tressa p == tid; t = t + 1 x = t; tressa p == tid; release(lock); p =: tid; R B B B L

Prophecy Variables Prophecy variable: Auxiliary variable, encodes future non-determinism –Allows actions to refer to future locally –Can use in annotations, abstraction. Different reduction proofs for different futures Concurrent systems: Non-determinism due to thread interleaving 51 p = R, G or B

Prophecy Variable Introduction: Soundness 52 –Annotating action α(s,s’) with prophecy variable p α(s,s’) becomes β(s,p, s’,p’) –Must satisfy p’. p. β(s,p, s’,p’) (History variables: h. h’. β(s,h, s’,h’) ) Backwards assignment satisfies this –Soundness: Every state of every execution can be annotated with a value of p. τ

tressas as partial specifications ReadPair(a: int, b: int) returns (s: bool, da: Obj, db: Obj) { var va: int, vb: int; [va := m[a].v; da := m[a].d; ] [vb := m[b].v; db := m[b].d; ] s := true; tressa ( exit(s) ==> da == m[a].d && db == m[b].d) ); [ if (va < m[a].v) { s:= false; } ] [ if (vb < m[b].v) { s:= false; } ] } // exit(s) =: s when procedure returns. procedure Write(a: int, d: Obj) { [ m[a].d := d; m[a].v := m[a].v+1; ] } 53

tressas + prophecy variables + abstraction ReadPair(a: int, b: int) returns (s: bool, da: Obj, db: Obj) { var va: int, vb: int; [va := m[a].v; da := m[a].d; if (exit(s)) havoc va, da; tressa ( exit(s) ==> va == m[a].v ); ] [vb := m[b].v; db := m[b].d; if (exit(s)) havoc vb, db; tressa ( exit(s) ==> vb == m[b].v ); ] s := true; [ if (va < m[a].v) { s:= false; } ] [ if (vb < m[b].v) { s:= false; } ] } // exit(s) =: s when procedure returns. 54