Verifying Optimistic Concurrency: Prophecy Variables and Backwards Reasoning

Post on 22-Feb-2016

47 views 0 download

description

Verifying Optimistic Concurrency: Prophecy Variables and Backwards Reasoning . Serdar Tasiran Koç University Istanbul, Turkey Tayfun Elmas Shaz Qadeer Ali Sezgin Koç University Microsoft Research Koç University Istanbul , Turkey Redmond, WA Istanbul, Turkey. - PowerPoint PPT Presentation

transcript

Verifying Optimistic Concurrency: Prophecy Variables and Backwards Reasoning

Serdar TasiranKoç University

Istanbul, Turkey

Tayfun Elmas Shaz Qadeer Ali Sezgin Koç University Microsoft Research Koç University

Istanbul, Turkey Redmond, WA Istanbul, Turkey

2Static Reduction Proofs for Optimistic Concurrency

– Goal: Verify programs that use optimistic concurrency• Transactional memory, non-blocking data structures, …• “A Compositional Method for Verifying Software

Transactional Memory Implementations” [MSR Tech. Report ’07]

– Optimistic concurrency: Proceed assuming non-interference• Abort, undo and/or retry if interference detected

• Approach: Static proof system QED [POPL ‘09, PADTAD ‘09]

– Challenge in applying QED to optimistic concurrency:• Same code, different reduction proofs for different futures• Need mechanism for referring to execution’s future

➡ Prophecy variables and backwards reasoning in QED

Outline

• Forward reasoning overview • Lock-protected increment• QED proof

• History variables, “assert” annotations

• Backward reasoning: Temporal dual• Optimistic concurrency example: Copy

• Why forward reasoning doesn’t work• Temporally dual approach

• Prophecy variables, “tressa” annotations• Generalized (back and forth) QED

• Examples

3

QED Proof for Blocking Concurrency

local int t;acquire (lock);t := x;t := t + 1;x := t;release (lock);

global int x;

x := 0;

||

assert (x == 2);

local int t;acquire (lock);t := x;t := t + 1;x := t;release (lock);

• Example: Lock-protected increment

4

5

Idea: Proof by reduction

acquire (lock);

t := x;

t := t + 1;

x := t;

release(lock);

R

B

B

B

L

acquire (lock);

t := x;

t := t + 1;

x := t;

release(lock);

REDUCE-SEQUENTIAL

6

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

7

Auxiliary variable: Which thread holds the lock?

inc (): int t; acquire (lock);

t = x;

t = t + 1

x = t;

release(lock);

inc (): int t; acquire (lock); a := tid;

t = x;

t = t + 1

x = t;

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

8

Annotating Actions with Assertions

acquire (lock); a := tid;

assert a == tid; t = x;

t = t + 1

assert a == tid; x = t;

assert a == tid; release(lock); a := 0;

acquire (lock); a := tid;

t = x;

t = t + 1

x = t;

release(lock); a := 0;

ABSTRACT

Invariant: (lock == true) (a != 0)

• Assertions indicate belief about non interference• Annotate actions locally with global information about execution

History Variable Annotations Make Static Mover Check Pass

9

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 α β and β α result in assertion violations.

10

Borrowing and paying back assertions

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;

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

R

B

B

B

L

Dischargesthe assertions

Invariant: (lock == true) (a != 0)

Outline• Forward reasoning overview

– Lock-protected increment– QED proof

• History variables, “assert” annotations

• Backward reasoning: Temporal dual– Optimistic concurrency example: Copy

• Why forward reasoning doesn’t work– Temporally dual approach

• Prophecy variables, “tressa” annotations– Generalized (back and forth) QED

• Examples

11

Optimistic Concurrency Example

12

Copy(fr: Obj, to: Obj){

atomic{ version := fr.ver; value := fr.val;}

atomic{ if (version == fr.ver) {to.val := value; to.ver := to.ver + 1;} }}

Wrt(to: Obj, newVal: int){ atomic{ to.val := newVal; to.ver := to.ver + 1;}}

Optimistic Concurrency Example

13

Copy(fr: Obj, to: Obj){

action SS(fr): atomic{ version := fr.ver; value := fr.val;}

action ConfNWrt(fr, to): atomic{ if (version == fr.ver) {to.val := value; to.ver := to.ver + 1;} }}

Wrt(to: Obj, newVal: int){ atomic{ to.val := newVal; to.ver := to.ver + 1;}}

Optimistic Concurrency Example

14

Copy(fr: Obj, to: Obj){

action SS(fr): atomic{ version := fr.ver; value := fr.val;}

action ConfNWrt(fr, to): atomic{ if (version == fr.ver) {to.val := value; to.ver := to.ver + 1;} }}

Wrt(to: Obj, newVal: int){ atomic{ to.val := newVal; to.ver := to.ver + 1;}}

Goal: Prove that SS(fr) is a right mover

Reduction for Optimistic Concurrency

• Copy(x, to_y) running concurrently with Wrt(x)– Does SS always commute to the right of Wrt?

• Failing Copy (Wrt interferes with Copy)

T1: SS(x) ConfNWrt(x, to_y) T2: Wrt(x, val)

• Succeeding Copy (No interference)

T1: SS(x) ConfNWrt(x, to_y) T2: Wrt(x, val)

15

Reduction Argument for Optimistic Concurrency

• Failing Copy T1: SS(x) ConfNWrt(x, to_y) T2: Wrt(x, val)

• Snapshot taken but ConfNWrt does not write to to_y.

➡ Abstract SS:

action SS(fr): atomic{ version := fr.ver; value := fr.val;}

action SS_Abs(fr): atomic{ havoc version, value; }

16

Copy Example After AbstractionCopy(fr: Obj, to: Obj){

action SS_Abs(fr): atomic{ havoc version, value; }

action ConfNWrt(fr, to): atomic{ if (version == fr.ver) {to.val := value; to.ver := to.ver + 1;} }}

Wrt(to: Obj, newVal: int){ atomic{ to.val := newVal; to.ver := to.ver + 1;}}

17

Problem: Too much abstraction

• Succeeding Copy (No interference)

T1: SS_Abs(x) ConfNWrt(x, to_y) T2: Wrt(x, val)

• Arbitrary value written to to_y!• Want to abstract SS(x) to SS_Abs(x) only in executions

in which interference occurs (later).

• But, SS_Abs doesn’t yet know whether interference will occur.– Need mechanism to refer to the future of the execution.

18

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

19

p = R, G or B

Introducing a Prophecy Variableaction ConfNWrt(fr, to):

atomic{ if (version == fr.ver)

{to.val := value; to.ver := to.ver + 1;}

}

action ConfNWrt(fr, to): atomic{ if (version == fr.ver) { p =: true; to.val := value; to.ver := to.ver + 1; } else { p =: false; } }}

20

Backwards Assignment “=:”

21

• Backwards assignment p =: x; • Shorthand for atomic{ assume p == x; havoc p;}

• p should match x

• p’ can have any value• Thinking backwards in time: Assigns the value of x to p

• In the execution, up to the point the following action is taken

if (version == fr.ver) { p =: true; to.val := value; to.ver := to.ver + 1; } else { p =: false;}

p is true iff the version number check (later) succeeds.

Prophecy Variable Introduction: Soundness

22

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

τ

Abstraction Constrained by Prophecy Variable

23

action SS_P(x): atomic{ if (p) {version := x.ver; value := x.val;} else { havoc version, value;} }

24

Reduction Proof of Optimistic Concurrency

• Succeeding Copy

T1: SS(x) ConfNWrt(x, to_y)

T2: Wrt(x, val)

• SS(x) commutes to the right of Wrt(x, val)• Copy succeeds Wrt(x, val) never immediately follows SS(x)• Need to annotate SS(x) with this fact.

• Similar case:

assert a == tid1; x = t1; and assert a == tid2; x = t2;

• These two actions cannot follow each other

25

Reduction Proof of Optimistic Concurrency

• Copy succeeds Wrt(x, val) never immediately follows SS(x)

• Need to annotate SS(x) with this fact.

• But SS(x) doesn’t yet know whether this is a succeeding Copy.

• Think backwards:• Walk back from the end of an execution, • When we reach s2, we know whether Copy has been successful.• Restated fact: If Copy succeeded,

at s2, the version number snapshot should be up-to-date.

s0 s2s1

SS(x)……

ConfNWrt(x)…

Putting it all together

26

Copy(fr: Obj, to: Obj){

action SS_P(fr): atomic{ if (p) {version := fr.ver; value := fr.val;} else { havoc version, value;} tressa p ==> (version >= fr.ver); }

action ConfNWrt(fr, to): atomic{ if (version == fr.ver) { p =: true; to.val := value; to.ver := to.ver + 1; } else { p =: false; } }}

tressa : Temporal Dual of assert

• tressa: Mechanism to annotate actions with assertions that can refer to prophecy variables

• assert: Discharged by reasoning about history of execution.

• tressa: Temporal dual of assert

• Example:

y := y+1; z := z-1; assume (x == 0);

27

tressa : Temporal Dual of assert

• Example: y := y+1; // x == 0 or execution blocks z := z-1; // x == 0 or execution blocks assume (x == 0);

• But

atomic{ assert x == 0; y := y+1;} atomic{ assert x == 0; z := z-1;} assume (x == 0);

does not work!

• Cannot discharge the assertions!

28

tressa : Temporal Dual of assert

• Example: y := y+1; // x == 0 or execution blocks z := z-1; // x == 0 or execution blocks assume (x == 0);

• tressa φ: Either φ holds in the post state, or execution does not terminate (blocks).

atomic{ y := y+1; tressa x == 0;} atomic{ z := z-1; tressa x == 0;} assume (x == 0);

• tressa annotations discharged by backwards reasoning within an atomic block.

• Discharged tressa φ: You cannot come back from a final state of the program and violate φ

29

30

Discharging tressa’s

inc (): int t; acquire (lock); p =: 0

tressa a == tid; t = x;

t = t + 1

tressa a == tid; x = t;

release(lock); p =: tid;

inc (): int t; acquire (lock); p =: 0;

tressa p == tid; t = x;

t = t + 1

tressa a == tid; x = t;

release(lock); p =: tid;

REDUCE & RELAX

R

B

B

B

L

Actions with assert’s and tressa’s

• Canonical action α: {assert a1; τ1; tressa p1} – τ1( s, s’) : Transition predicate– a1(s): assert predicate on the pre-state– p1(s’): tressa predicate on the post-state

• Operational semantics:– assert and tressa predicates have no effect on execution

• Execution has assert violation if any αi’s assert predicate is violated.• Execution has tressa violation if any αi’s tressa predicate is violated.• QED± preserves assert and tressa violations

31

s0 sn-1s3s2s1 sn

α0 α2α1 …αn-1

Abstraction and Mover Checks with tressa’s

{assert a1; τ1; tressa p1} ≤ {assert a2; τ2; tressa p2}

• Preserve assert violations: a2 a1

• Preserve assert violations: p2 p1

• Forward simulate or replace with assert violation: τ1 (s,s’) τ2 (s,s’) ∨ a2(s)

• Backward simulate orreplace with tressa violation: τ1 (s,s’) τ2 (s,s’) ∨ p2(s’)

• Does α commute to the right of β ? α β ≤ β α

32

abstracts

Putting it all together

33

Copy(fr: Obj, to: Obj){

action SS_P(fr): atomic{ if (p) {version := fr.ver; value := fr.val;} else { havoc version, value;} tressa p ==> (version >= fr.ver); }

action ConfNWrt(fr, to): atomic{ if (version == fr.ver) { p =: true; to.val := value; to.ver := to.ver + 1; } else { p =: false; } }}

p == false

Wrt(x) SS_P (x):havoc version, value

action SS_P(x): atomic{ if (p) {version := x.ver; value := x.val;} else { havoc version, value;} tressa p ==> (version >= ver); }

Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }

SS_P Wrt ≤ Wrt SS_P (Case 1)

34

p == false SS_P(x):havoc version, valueWrt(x)

Wrt(x) SS_P (x):havoc version, value

action SS_P(x): atomic{ if (p) {version := x.ver; value := x.val;} else { havoc version, value;} tressa p ==> (version >= ver); }

Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }

SS_P Wrt ≤ Wrt SS_P (Case 1)

35

p == true

Wrt(x)

SS_P Wrt ≤ Wrt SS_P (Case 2)action SS_P(x): atomic{ if (p) {version := x.ver; value := x.val;} else { havoc version, value;} tressa p ==> (version >= ver); }

Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }

SS_Proph(x):tressa(version>=x.ver)

36

p == true

Wrt(x)

SS_P Wrt ≤ Wrt SS_P (Case 2)action SS_P(x): atomic{ if (p) {version := x.ver; value := x.val;} else { havoc version, value;} tressa p ==> (version >= ver); }

Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }

SS_Proph(x):tressa(version>=x.ver)version < x.ver (Case 2)

37

p == true

Wrt(x)version < x.ver

SS_P Wrt ≤ Wrt SS_P (Case 2)action SS_P(x): atomic{ if (p) {version := x.ver; value := x.val;} else { havoc version, value;} tressa p ==> (version >= ver); }

Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }

SS_Proph(x):tressa(version>=x.ver)version < x.ver (Case 2)

38

p == true

Wrt(x)version < x.ver

SS_P Wrt ≤ Wrt SS_P (Case 2)action SS_P(x): atomic{ if (p) {version := x.ver; value := x.val;} else { havoc version, value;} tressa p ==> (version >= ver); }

Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }

SS_Proph(x):tressa(version>=x.ver)version < x.ver (Case 2)

SS_P (x):tressa(version>=x.ver)

39

p == true

SS_P Wrt ≤ Wrt SS_P (Case 3)action SS_P(x): atomic{ if (p) {version := x.ver; value := x.val;} else { havoc version, value;} tressa p ==> (version >= ver); }

Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }

Wrt(x)SS_Proph(x):tressa(version>=x.ver)

✔40

p == true

SS_P Wrt ≤ Wrt SS_P (Case 3)action SS_P(x): atomic{ if (p) {version := x.ver; value := x.val;} else { havoc version, value;} tressa p ==> (version >= ver); }

Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }

Wrt(x)SS_Proph(x):tressa(version>=x.ver)

version == x.ver

✔41

p == true

SS_P Wrt ≤ Wrt SS_P (Case 3)action SS_P(x): atomic{ if (p) {version := x.ver; value := x.val;} else { havoc version, value;} tressa p ==> (version >= ver); }

Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }

Wrt(x)SS_Proph(x):tressa(version>=x.ver)

version == x.ver

version+1 == x.ver✔42

p == true

SS_P Wrt ≤ Wrt SS_P (Case 3)action SS_P(x): atomic{ if (p) {version := x.ver; value := x.val;} else { havoc version, value;} tressa p ==> (version >= ver); }

Wrt(x) { atomic{ x.val := newVal; x.ver := x.ver + 1;} }

SS_Proph(x):tressa(version>=x.ver)Wrt(x)

SS_Proph(x):tressa(version>=x.ver)version == x.ver

version+1 == x.ver

43

Copy is Atomic

44

Copy(fr: Obj, to: Obj){

action SS_P(fr): atomic{ if (p) {version := fr.ver; value := fr.val;} else { havoc version, value;} tressa p ==> (version >= ver); }

action ConfNWrt(fr, to): atomic{ if (version == fr.ver) { p =: true; to.val := value; to.ver := to.ver + 1; } else { p =: false; } }}

R

Proving Optimistic Concurrency in QED

• tressa’s allow one to express the following fact locally– If action b immediately follows action a, then this execution will eventually block

• Optimistic concurrency proof template:– Introduce prophecy variables– Backwards assign them when non-determinism is resolved– Annotate earlier actions with tressa’s– Perform a case split in the proof based on possible futures– When blocks are large enough, discharge tressa’s by reasoning

backwards in time.

45

Example 2: ReadPairprocedure ReadPair(a: int, b: int)returns (s: bool, da: Obj, db: Obj){ var va: int, vb: int;

1: atomic { va := m[a].v; da := m[a].d; }2: atomic { vb := m[b].v; db := m[b].d; }3: s := true;4: atomic { if (va < m[a].v) { s:= false; } }5: atomic { if (vb < m[b].v) { s:= false; } }6: if (!s) { da := nil; db := nil; }}

procedure Write(a: int, d: Obj){ atomic { m[a].d := d; m[a].v := m[a].v+1; }}

46

Example 3: Multiset (Insert/Lookup)procedure Lookup(x: data) returns result: bool;{ f := false; i := 0;

while (i<n && !f) { f := (q[i] == x); i := i+1; } result = f;}

procedure Insert(x: data) returns result: bool;{ havoc i; assume i<n; cnt := 0; result := false;

while (cnt<n && !f) { if (q[i]==-1) {q[i] := x; result := true; } else if (q[i]== x) { result := true; } else { i := (i+1) mod n; cnt := cnt+1; } }}

47

Example 3: Multiset (Insert/Lookup)procedure Lookup(x: data) returns result: bool;{ … while (i<n && !f) { f := (q[i] == x); i := i+1; } result = f;}procedure Insert(x: data) returns result: bool; { ... }

• Lookup’s that return true commit when they find x.• Lookup’s that return false commit when they read q[0].• Different reduction proofs needed for the two cases• Replace Lookup(x) with

if (*) { Lookup(x); assume result} else { Lookup(x); assume !result}

• Use return value as prophecy variable• Perform different reduction proofs on the two branches

48

Summary

• QED: Proof system for concurrent software – Iteration of abstraction and reduction surprisingly powerful– Intricate algorithms verified

• Reduction proofs for optimistic concurrency– Actions need to refer to the future: prophecy variables– Annotate actions locally with info about future: tressa

• QED + prophecy variables, tressa’s, forward & backward reasoning– Generalized definition of simulation– Soundness proof

• Proved examples making use of optimistic concurrency• Future work: Verify transactional memory algorithms,

non-blocking data structures.

49

50

51

Multiset

• Multiset data structureM = { 2, 3, 3, 9, 8, 8, 5 }

• Represented by M[1..n]– elt: The element– vld: Is it in the set?

52

M 9

✔8

✔6

✗8

✔5

✔3

✗3

✔3

✔2

✔elt

vld

LookUp (x)for i = 1 to n acq (M[i]); if (M[i].elt==x && M[i].vld) rel (M[i]); return true; else rel (M[i]); return false;

FindSlot and InsertPair

FindSlot (x)r = -1; i = 0;while(i < N && r == -1) { acq (A[i]); if (M[i].elt == null) { M[i].elt= x; rel (M[i]); r = i; } else { rel (M[i]);

} i = i + 1;}return r;

InsertPair (x, y)

i = FindSlot (x);if (i == -1) { return failure;}j = FindSlot (y);if (j == -1) { M[i].elt= null; return failure;}acq (M[i])acq (M[j]) M[i].vld = true; M[j].vld = true;rel (M[i]);rel (M[j]);return success;

Rewriting the “if” statement54

FindSlot (x)r = -1; i = 0;while(i < N && r == -1) { acq (A[i]); if (M[i].elt == null) { M[i].elt= x; rel (M[i]); r = i; } else { rel (M[i]);

} i = i + 1;}return r;

FindSlot (x)r = -1; i = 0;while(i < N && r == -1) { acq (A[i]); assume (M[i].elt == null); M[i].elt= x; rel (M[i]); r = i;

acq (A[i]); assume (M[i] != null); rel (M[i]); i = i + 1;}return r;

mutex (M[i].lock == tid)55

FindSlot (x)r = -1; i = 0;while(i < N && r == -1) { acq (A[i]); assume (M[i].elt == null); M[i].elt= x; rel (M[i]); r = i;

acq (A[i]); assume (M[i] != null); rel (M[i]); i = i + 1;}return r;

□REDUCE

FindSlot (x)r = -1; i = 0;while(i < N && r == -1) { acq (A[i]); assume (M[i].elt == null); M[i].elt= x; rel (M[i]); r = i;

acq (A[i]); assume (M[i] != null); rel (M[i]); i = i + 1;}return r;

Read abstraction56

FindSlot (x)r = -1; i = 0;while(i < N && r == -1) { acq (A[i]); assume (M[i].elt == null); M[i].elt= x; rel (M[i]); r = i;

acq (A[i]); assume (M[i] != null); rel (M[i]); i = i + 1;}return r;

FindSlot (x)r = -1; i = 0;while(i < N && r == -1) { acq (A[i]); assume (M[i].elt == null); M[i].elt= x; rel (M[i]); r = i;

acq (A[i]); skip rel (M[i]); i = i + 1;}return r;

□SIMULATE

“Collapse”57

FindSlot (x)r = -1; i = 0;while(i < N && r == -1) { acq (A[i]); assume (M[i].elt == null); M[i].elt= x; rel (M[i]); r = i;

acq (A[i]); skip; rel (M[i]); i = i + 1;}return r;

FindSlot (x)r = -1; i = 0;while(i < N && r == -1) { acq (A[i]); assume (M[i].elt == null); M[i].elt= x; rel (M[i]); r = i;

skip

i = i + 1;}return r;

Reducing the loop (Peel out last iteration)58

FindSlot (x)r = -1; i = 0;while(i < N && r == -1) {

skip; i = i + 1; } havoc i; assume (0 <= i < N) && r ==-1;acq (A[i]); assume (M[i].elt == null); M[i].elt= x;rel (M[i]);r = i;

skip;return r;

R

FindSlot (x)i = 0;

havoc i;assume (0 <= i < N); assume (M[i].elt == null); M[i].elt = x; return i;

return -1;

59

InsertPair (x, y)

i = FindSlot (x);if (i == -1) return failure;

j = FindSlot (y);if (j == -1) M[i].elt= null; return failure;

acq (M[i])acq (M[j]) M[i].vld = true; M[j].vld = true;rel (M[i]);rel (M[j]);return success;

InsertPair (x, y)

i = FindSlot (x);if (i == -1) return failure;res[i] := tid;

j = FindSlot (y);if (j == -1) M[i].elt= null; return failure;res[j] := tid;

acq (M[i])acq (M[j]) M[i].vld = true; M[j].vld = true;rel (M[i]);rel (M[j]); return success;

R

R

Reduce60

InsertPair (x, y)

i = FindSlot (x);if (i == -1) return failure;res[i] = tid;

j = FindSlot (y);if (j == 0) M[i].elt= null; return failure;res[j] = tid;

acq (M[i])acq (M[j]) M[i].vld = true; M[j].vld = true;rel (M[i]);rel (M[j]); return success;

R

R

InsertPair (x, y)

i = FindSlot (x);if (i == -1) return failure;res[i] = tid;

j = FindSlot (y);if (j == 0) M[i].elt= null; return failure;res[j] = tid;

acq (M[i])acq (M[j]) M[i].vld = true; M[j].vld = true;rel (M[i]);rel (M[j]); return success;

REDUCE-SEQ.