blob: fde2ffc6db5c242a77ef5783e8a8ff3cbd23c35d [file] [log] [blame]
---------------------------- MODULE qspinlock --------------------------------
\*
\* Linux queued spinlocks model (modified to avoid the cmpxchg loops)
\*
EXTENDS Naturals, Sequences, FiniteSets, TLC
CONSTANTS CPUS,
MAX_NODES,
PENDING_LOOPS
ASSUME MAX_NODES \in Nat \ {0}
NoCPU == CHOOSE cpu : cpu \notin CPUS
THREADS == CPUS \X (1..MAX_NODES)
(* --algorithm qspinlock {
variables
\* One qspinlock per node (a.k.a. priority level)
qspinlock = [l \in 1..MAX_NODES |-> LockVal(FALSE, FALSE, 0, NoCPU)];
\* One mcs_lock per thread
mcs_lock = [t \in THREADS |->
[next |-> NODE_ZERO,
locked |-> FALSE,
count |-> 0]];
\* priority level
priority = [p \in CPUS |-> 1];
ret_trylock = [p \in THREADS |-> FALSE];
define {
ProcessEnabled(self) == self[2] >= priority[self[1]]
LockVal(l, p, i, c) == [locked |-> l,
pending |-> p,
tail_idx |-> i,
tail_cpu |-> c]
ZERO_VAL == LockVal(FALSE, FALSE, 0, NoCPU)
LOCKED_VAL == LockVal(TRUE, FALSE, 0, NoCPU)
PENDING_VAL == LockVal(FALSE, TRUE, 0, NoCPU)
NEG_LOCKED_MASK(val) == val.pending \/ val.tail_idx # 0 \/ val.tail_cpu # NoCPU
TAIL_MASK(val) == val.tail_idx # 0 \/ val.tail_cpu # NoCPU
NODE_ZERO == << NoCPU, 0 >>
\* lock addresses
Lock(self) == self[2]
McsNode(p, i) == << p, i >>
QLockType == [locked: BOOLEAN,
pending: BOOLEAN,
tail_idx: Nat,
tail_cpu: CPUS \cup {NoCPU}]
McsLockType == [next: THREADS \cup {NODE_ZERO},
locked: BOOLEAN,
count: Nat]
TypeInv == /\ mcs_lock \in [THREADS -> McsLockType]
/\ qspinlock \in [1..MAX_NODES -> QLockType]
ExclInv == \A t1, t2 \in THREADS : t1[1] # t2[1] /\ t1[2] = t2[2] =>
~((pc[t1] = "cs") /\ (pc[t2] = "cs"))
LockAny == \E t \in THREADS : pc[t] = "sl1" ~> pc[t] = "cs"
LockAll == \A p \in CPUS : \E n \in 1..MAX_NODES :
pc[<< p, n >>] = "sl1" ~> pc[<< p, n >>] = "cs"
\* Symmetry optimisations
Perms == Permutations(CPUS)
}
macro priority_enter(level) {
level := priority[self[1]];
priority[self[1]] := self[2];
}
macro priority_exit(level) {
priority[self[1]] := level;
}
\* atomic cmpxchg
macro cmpxchg(v, o, n, ret) {
ret := v;
if (v = o)
v := n;
}
procedure spin_lock(lock)
variable val;
{
sl1: cmpxchg(qspinlock[lock], ZERO_VAL, LOCKED_VAL, val);
sl2: if (val = ZERO_VAL)
return;
sl3: call spin_lock_slowpath(lock, val);
return;
}
procedure spin_unlock(lock)
{
su1: qspinlock[lock].locked := FALSE;
return;
}
procedure spin_trylock(lock)
{
stl1: if (qspinlock[lock] = ZERO_VAL) {
qspinlock[lock] := LOCKED_VAL;
ret_trylock[self] := TRUE;
} else {
ret_trylock[self] := FALSE;
};
return;
}
procedure spin_lock_slowpath(lock, val)
variables old, next, prev, node, idx, cnt = PENDING_LOOPS;
{
sp1: \* wait for in-progress pending->locked hand-overs (bounded)
if (val = PENDING_VAL) {
sp1_1: while (qspinlock[lock] = PENDING_VAL /\ cnt # 0)
cnt := cnt - 1;
val := qspinlock[lock];
};
sp1_2: \* if we observe any contention, queue
if (NEG_LOCKED_MASK(val))
goto queue;
sp2: \* trylock \/ pending
\* atomic_fetch_or
val := qspinlock[lock];
qspinlock[lock].pending := TRUE;
sp3: if (~NEG_LOCKED_MASK(val)) {
\* we're pending, wait for the owner to go away
\* smp_cond_load_acquire(!(VAL & LOCKED_MASK))
if (val.locked)
await ~qspinlock[lock].locked;
\* take ownership and clear pending
\* clear_pending_set_locked
qspinlock[lock].pending := FALSE || qspinlock[lock].locked := TRUE;
return;
};
\* if pending was clear but there are waiters, undo the setting of
\* pending before queuing
sp4: if (~val.pending)
qspinlock[lock].pending := FALSE;
queue:
node := McsNode(self[1], 1);
idx := mcs_lock[node].count + 1;
mcs_lock[node].count := mcs_lock[node].count + 1;
sp11: node := McsNode(self[1], idx);
mcs_lock[node].locked := FALSE || mcs_lock[node].next := NODE_ZERO;
call spin_trylock(lock);
sp13: if (ret_trylock[self])
goto release;
sp14: \* xchg_tail
old := LockVal(FALSE, FALSE, qspinlock[lock].tail_idx, qspinlock[lock].tail_cpu);
qspinlock[lock].tail_idx := idx || qspinlock[lock].tail_cpu := self[1];
next := NODE_ZERO;
sp15: if (TAIL_MASK(old)) {
prev := McsNode(old.tail_cpu, old.tail_idx);
mcs_lock[prev].next := node;
sp16: \* mcs_spin_lock_contended
await mcs_lock[node].locked;
next := mcs_lock[node].next;
};
sp18: \* smp_cond_load_acquire(!(VAL & LOCKED_PENDING_MASK))
await ~qspinlock[lock].locked /\ ~qspinlock[lock].pending;
val := qspinlock[lock];
locked: \* claim the lock
if (val.tail_idx = idx /\ val.tail_cpu = self[1]) {
cmpxchg(qspinlock[lock], val, LOCKED_VAL, old);
if (old = val)
goto release; \* no contention
};
\* either somebody is queued behind us or pending is set
sp21: qspinlock[lock].locked := TRUE;
\* contended path; wait for next if not observed yet, release
if (next = NODE_ZERO) {
await mcs_lock[node].next # NODE_ZERO;
next := mcs_lock[node].next;
};
\* mcs_spin_unlock_contended
mcs_lock[next].locked := TRUE;
release:
mcs_lock[McsNode(self[1], 1)].count := mcs_lock[McsNode(self[1], 1)].count - 1;
return;
}
fair process (thread \in THREADS)
variable priority_level;
{
t1: while (TRUE) {
priority_enter(priority_level);
call spin_lock(Lock(self));
cs: skip;
call spin_unlock(Lock(self));
t2: priority_exit(priority_level);
}
}
} *)
\* BEGIN TRANSLATION
\* Procedure variable val of procedure spin_lock at line 88 col 18 changed to val_
\* Parameter lock of procedure spin_lock at line 87 col 21 changed to lock_
\* Parameter lock of procedure spin_unlock at line 97 col 23 changed to lock_s
\* Parameter lock of procedure spin_trylock at line 103 col 24 changed to lock_sp
CONSTANT defaultInitValue
VARIABLES qspinlock, mcs_lock, priority, ret_trylock, pc, stack
(* define statement *)
ProcessEnabled(self) == self[2] >= priority[self[1]]
LockVal(l, p, i, c) == [locked |-> l,
pending |-> p,
tail_idx |-> i,
tail_cpu |-> c]
ZERO_VAL == LockVal(FALSE, FALSE, 0, NoCPU)
LOCKED_VAL == LockVal(TRUE, FALSE, 0, NoCPU)
PENDING_VAL == LockVal(FALSE, TRUE, 0, NoCPU)
NEG_LOCKED_MASK(val) == val.pending \/ val.tail_idx # 0 \/ val.tail_cpu # NoCPU
TAIL_MASK(val) == val.tail_idx # 0 \/ val.tail_cpu # NoCPU
NODE_ZERO == << NoCPU, 0 >>
Lock(self) == self[2]
McsNode(p, i) == << p, i >>
QLockType == [locked: BOOLEAN,
pending: BOOLEAN,
tail_idx: Nat,
tail_cpu: CPUS \cup {NoCPU}]
McsLockType == [next: THREADS \cup {NODE_ZERO},
locked: BOOLEAN,
count: Nat]
TypeInv == /\ mcs_lock \in [THREADS -> McsLockType]
/\ qspinlock \in [1..MAX_NODES -> QLockType]
ExclInv == \A t1, t2 \in THREADS : t1[1] # t2[1] /\ t1[2] = t2[2] =>
~((pc[t1] = "cs") /\ (pc[t2] = "cs"))
LockAny == \E t \in THREADS : pc[t] = "sl1" ~> pc[t] = "cs"
LockAll == \A p \in CPUS : \E n \in 1..MAX_NODES :
pc[<< p, n >>] = "sl1" ~> pc[<< p, n >>] = "cs"
Perms == Permutations(CPUS)
VARIABLES lock_, val_, lock_s, lock_sp, lock, val, old, next, prev, node, idx,
cnt, priority_level
vars == << qspinlock, mcs_lock, priority, ret_trylock, pc, stack, lock_, val_,
lock_s, lock_sp, lock, val, old, next, prev, node, idx, cnt,
priority_level >>
ProcSet == (THREADS)
Init == (* Global variables *)
/\ qspinlock = [l \in 1..MAX_NODES |-> LockVal(FALSE, FALSE, 0, NoCPU)]
/\ mcs_lock = [t \in THREADS |->
[next |-> NODE_ZERO,
locked |-> FALSE,
count |-> 0]]
/\ priority = [p \in CPUS |-> 1]
/\ ret_trylock = [p \in THREADS |-> FALSE]
(* Procedure spin_lock *)
/\ lock_ = [ self \in ProcSet |-> defaultInitValue]
/\ val_ = [ self \in ProcSet |-> defaultInitValue]
(* Procedure spin_unlock *)
/\ lock_s = [ self \in ProcSet |-> defaultInitValue]
(* Procedure spin_trylock *)
/\ lock_sp = [ self \in ProcSet |-> defaultInitValue]
(* Procedure spin_lock_slowpath *)
/\ lock = [ self \in ProcSet |-> defaultInitValue]
/\ val = [ self \in ProcSet |-> defaultInitValue]
/\ old = [ self \in ProcSet |-> defaultInitValue]
/\ next = [ self \in ProcSet |-> defaultInitValue]
/\ prev = [ self \in ProcSet |-> defaultInitValue]
/\ node = [ self \in ProcSet |-> defaultInitValue]
/\ idx = [ self \in ProcSet |-> defaultInitValue]
/\ cnt = [ self \in ProcSet |-> PENDING_LOOPS]
(* Process thread *)
/\ priority_level = [self \in THREADS |-> defaultInitValue]
/\ stack = [self \in ProcSet |-> << >>]
/\ pc = [self \in ProcSet |-> "t1"]
sl1(self) == /\ pc[self] = "sl1" /\ ProcessEnabled(self)
/\ val_' = [val_ EXCEPT ![self] = qspinlock[lock_[self]]]
/\ IF (qspinlock[lock_[self]]) = ZERO_VAL
THEN /\ qspinlock' = [qspinlock EXCEPT ![lock_[self]] = LOCKED_VAL]
ELSE /\ TRUE
/\ UNCHANGED qspinlock
/\ pc' = [pc EXCEPT ![self] = "sl2"]
/\ UNCHANGED << mcs_lock, priority, ret_trylock, stack, lock_,
lock_s, lock_sp, lock, val, old, next, prev, node,
idx, cnt, priority_level >>
sl2(self) == /\ pc[self] = "sl2" /\ ProcessEnabled(self)
/\ IF val_[self] = ZERO_VAL
THEN /\ pc' = [pc EXCEPT ![self] = Head(stack[self]).pc]
/\ val_' = [val_ EXCEPT ![self] = Head(stack[self]).val_]
/\ lock_' = [lock_ EXCEPT ![self] = Head(stack[self]).lock_]
/\ stack' = [stack EXCEPT ![self] = Tail(stack[self])]
ELSE /\ pc' = [pc EXCEPT ![self] = "sl3"]
/\ UNCHANGED << stack, lock_, val_ >>
/\ UNCHANGED << qspinlock, mcs_lock, priority, ret_trylock,
lock_s, lock_sp, lock, val, old, next, prev, node,
idx, cnt, priority_level >>
sl3(self) == /\ pc[self] = "sl3" /\ ProcessEnabled(self)
/\ /\ lock' = [lock EXCEPT ![self] = lock_[self]]
/\ stack' = [stack EXCEPT ![self] = << [ procedure |-> "spin_lock_slowpath",
pc |-> Head(stack[self]).pc,
old |-> old[self],
next |-> next[self],
prev |-> prev[self],
node |-> node[self],
idx |-> idx[self],
cnt |-> cnt[self],
lock |-> lock[self],
val |-> val[self] ] >>
\o Tail(stack[self])]
/\ val' = [val EXCEPT ![self] = val_[self]]
/\ val_' = [val_ EXCEPT ![self] = Head(stack[self]).val_]
/\ old' = [old EXCEPT ![self] = defaultInitValue]
/\ next' = [next EXCEPT ![self] = defaultInitValue]
/\ prev' = [prev EXCEPT ![self] = defaultInitValue]
/\ node' = [node EXCEPT ![self] = defaultInitValue]
/\ idx' = [idx EXCEPT ![self] = defaultInitValue]
/\ cnt' = [cnt EXCEPT ![self] = PENDING_LOOPS]
/\ pc' = [pc EXCEPT ![self] = "sp1"]
/\ UNCHANGED << qspinlock, mcs_lock, priority, ret_trylock, lock_,
lock_s, lock_sp, priority_level >>
spin_lock(self) == sl1(self) \/ sl2(self) \/ sl3(self)
su1(self) == /\ pc[self] = "su1" /\ ProcessEnabled(self)
/\ qspinlock' = [qspinlock EXCEPT ![lock_s[self]].locked = FALSE]
/\ pc' = [pc EXCEPT ![self] = Head(stack[self]).pc]
/\ lock_s' = [lock_s EXCEPT ![self] = Head(stack[self]).lock_s]
/\ stack' = [stack EXCEPT ![self] = Tail(stack[self])]
/\ UNCHANGED << mcs_lock, priority, ret_trylock, lock_, val_,
lock_sp, lock, val, old, next, prev, node, idx,
cnt, priority_level >>
spin_unlock(self) == su1(self)
stl1(self) == /\ pc[self] = "stl1" /\ ProcessEnabled(self)
/\ IF qspinlock[lock_sp[self]] = ZERO_VAL
THEN /\ qspinlock' = [qspinlock EXCEPT ![lock_sp[self]] = LOCKED_VAL]
/\ ret_trylock' = [ret_trylock EXCEPT ![self] = TRUE]
ELSE /\ ret_trylock' = [ret_trylock EXCEPT ![self] = FALSE]
/\ UNCHANGED qspinlock
/\ pc' = [pc EXCEPT ![self] = Head(stack[self]).pc]
/\ lock_sp' = [lock_sp EXCEPT ![self] = Head(stack[self]).lock_sp]
/\ stack' = [stack EXCEPT ![self] = Tail(stack[self])]
/\ UNCHANGED << mcs_lock, priority, lock_, val_, lock_s, lock,
val, old, next, prev, node, idx, cnt,
priority_level >>
spin_trylock(self) == stl1(self)
sp1(self) == /\ pc[self] = "sp1" /\ ProcessEnabled(self)
/\ IF val[self] = PENDING_VAL
THEN /\ pc' = [pc EXCEPT ![self] = "sp1_1"]
ELSE /\ pc' = [pc EXCEPT ![self] = "sp1_2"]
/\ UNCHANGED << qspinlock, mcs_lock, priority, ret_trylock, stack,
lock_, val_, lock_s, lock_sp, lock, val, old,
next, prev, node, idx, cnt, priority_level >>
sp1_1(self) == /\ pc[self] = "sp1_1" /\ ProcessEnabled(self)
/\ IF qspinlock[lock[self]] = PENDING_VAL /\ cnt[self] # 0
THEN /\ cnt' = [cnt EXCEPT ![self] = cnt[self] - 1]
/\ pc' = [pc EXCEPT ![self] = "sp1_1"]
/\ val' = val
ELSE /\ val' = [val EXCEPT ![self] = qspinlock[lock[self]]]
/\ pc' = [pc EXCEPT ![self] = "sp1_2"]
/\ cnt' = cnt
/\ UNCHANGED << qspinlock, mcs_lock, priority, ret_trylock,
stack, lock_, val_, lock_s, lock_sp, lock, old,
next, prev, node, idx, priority_level >>
sp1_2(self) == /\ pc[self] = "sp1_2" /\ ProcessEnabled(self)
/\ IF NEG_LOCKED_MASK(val[self])
THEN /\ pc' = [pc EXCEPT ![self] = "queue"]
ELSE /\ pc' = [pc EXCEPT ![self] = "sp2"]
/\ UNCHANGED << qspinlock, mcs_lock, priority, ret_trylock,
stack, lock_, val_, lock_s, lock_sp, lock, val,
old, next, prev, node, idx, cnt, priority_level >>
sp2(self) == /\ pc[self] = "sp2" /\ ProcessEnabled(self)
/\ val' = [val EXCEPT ![self] = qspinlock[lock[self]]]
/\ qspinlock' = [qspinlock EXCEPT ![lock[self]].pending = TRUE]
/\ pc' = [pc EXCEPT ![self] = "sp3"]
/\ UNCHANGED << mcs_lock, priority, ret_trylock, stack, lock_,
val_, lock_s, lock_sp, lock, old, next, prev,
node, idx, cnt, priority_level >>
sp3(self) == /\ pc[self] = "sp3" /\ ProcessEnabled(self)
/\ IF ~NEG_LOCKED_MASK(val[self])
THEN /\ IF val[self].locked
THEN /\ ~qspinlock[lock[self]].locked
ELSE /\ TRUE
/\ qspinlock' = [qspinlock EXCEPT ![lock[self]].pending = FALSE,
![lock[self]].locked = TRUE]
/\ pc' = [pc EXCEPT ![self] = Head(stack[self]).pc]
/\ old' = [old EXCEPT ![self] = Head(stack[self]).old]
/\ next' = [next EXCEPT ![self] = Head(stack[self]).next]
/\ prev' = [prev EXCEPT ![self] = Head(stack[self]).prev]
/\ node' = [node EXCEPT ![self] = Head(stack[self]).node]
/\ idx' = [idx EXCEPT ![self] = Head(stack[self]).idx]
/\ cnt' = [cnt EXCEPT ![self] = Head(stack[self]).cnt]
/\ lock' = [lock EXCEPT ![self] = Head(stack[self]).lock]
/\ val' = [val EXCEPT ![self] = Head(stack[self]).val]
/\ stack' = [stack EXCEPT ![self] = Tail(stack[self])]
ELSE /\ pc' = [pc EXCEPT ![self] = "sp4"]
/\ UNCHANGED << qspinlock, stack, lock, val, old, next,
prev, node, idx, cnt >>
/\ UNCHANGED << mcs_lock, priority, ret_trylock, lock_, val_,
lock_s, lock_sp, priority_level >>
sp4(self) == /\ pc[self] = "sp4" /\ ProcessEnabled(self)
/\ IF ~val[self].pending
THEN /\ qspinlock' = [qspinlock EXCEPT ![lock[self]].pending = FALSE]
ELSE /\ TRUE
/\ UNCHANGED qspinlock
/\ pc' = [pc EXCEPT ![self] = "queue"]
/\ UNCHANGED << mcs_lock, priority, ret_trylock, stack, lock_,
val_, lock_s, lock_sp, lock, val, old, next, prev,
node, idx, cnt, priority_level >>
queue(self) == /\ pc[self] = "queue" /\ ProcessEnabled(self)
/\ node' = [node EXCEPT ![self] = McsNode(self[1], 1)]
/\ idx' = [idx EXCEPT ![self] = mcs_lock[node'[self]].count + 1]
/\ mcs_lock' = [mcs_lock EXCEPT ![node'[self]].count = mcs_lock[node'[self]].count + 1]
/\ pc' = [pc EXCEPT ![self] = "sp11"]
/\ UNCHANGED << qspinlock, priority, ret_trylock, stack, lock_,
val_, lock_s, lock_sp, lock, val, old, next,
prev, cnt, priority_level >>
sp11(self) == /\ pc[self] = "sp11" /\ ProcessEnabled(self)
/\ node' = [node EXCEPT ![self] = McsNode(self[1], idx[self])]
/\ mcs_lock' = [mcs_lock EXCEPT ![node'[self]].locked = FALSE,
![node'[self]].next = NODE_ZERO]
/\ /\ lock_sp' = [lock_sp EXCEPT ![self] = lock[self]]
/\ stack' = [stack EXCEPT ![self] = << [ procedure |-> "spin_trylock",
pc |-> "sp13",
lock_sp |-> lock_sp[self] ] >>
\o stack[self]]
/\ pc' = [pc EXCEPT ![self] = "stl1"]
/\ UNCHANGED << qspinlock, priority, ret_trylock, lock_, val_,
lock_s, lock, val, old, next, prev, idx, cnt,
priority_level >>
sp13(self) == /\ pc[self] = "sp13" /\ ProcessEnabled(self)
/\ IF ret_trylock[self]
THEN /\ pc' = [pc EXCEPT ![self] = "release"]
ELSE /\ pc' = [pc EXCEPT ![self] = "sp14"]
/\ UNCHANGED << qspinlock, mcs_lock, priority, ret_trylock,
stack, lock_, val_, lock_s, lock_sp, lock, val,
old, next, prev, node, idx, cnt, priority_level >>
sp14(self) == /\ pc[self] = "sp14" /\ ProcessEnabled(self)
/\ old' = [old EXCEPT ![self] = LockVal(FALSE, FALSE, qspinlock[lock[self]].tail_idx, qspinlock[lock[self]].tail_cpu)]
/\ qspinlock' = [qspinlock EXCEPT ![lock[self]].tail_idx = idx[self],
![lock[self]].tail_cpu = self[1]]
/\ next' = [next EXCEPT ![self] = NODE_ZERO]
/\ pc' = [pc EXCEPT ![self] = "sp15"]
/\ UNCHANGED << mcs_lock, priority, ret_trylock, stack, lock_,
val_, lock_s, lock_sp, lock, val, prev, node,
idx, cnt, priority_level >>
sp15(self) == /\ pc[self] = "sp15" /\ ProcessEnabled(self)
/\ IF TAIL_MASK(old[self])
THEN /\ prev' = [prev EXCEPT ![self] = McsNode(old[self].tail_cpu, old[self].tail_idx)]
/\ mcs_lock' = [mcs_lock EXCEPT ![prev'[self]].next = node[self]]
/\ pc' = [pc EXCEPT ![self] = "sp16"]
ELSE /\ pc' = [pc EXCEPT ![self] = "sp18"]
/\ UNCHANGED << mcs_lock, prev >>
/\ UNCHANGED << qspinlock, priority, ret_trylock, stack, lock_,
val_, lock_s, lock_sp, lock, val, old, next,
node, idx, cnt, priority_level >>
sp16(self) == /\ pc[self] = "sp16" /\ ProcessEnabled(self)
/\ mcs_lock[node[self]].locked
/\ next' = [next EXCEPT ![self] = mcs_lock[node[self]].next]
/\ pc' = [pc EXCEPT ![self] = "sp18"]
/\ UNCHANGED << qspinlock, mcs_lock, priority, ret_trylock,
stack, lock_, val_, lock_s, lock_sp, lock, val,
old, prev, node, idx, cnt, priority_level >>
sp18(self) == /\ pc[self] = "sp18" /\ ProcessEnabled(self)
/\ ~qspinlock[lock[self]].locked /\ ~qspinlock[lock[self]].pending
/\ val' = [val EXCEPT ![self] = qspinlock[lock[self]]]
/\ pc' = [pc EXCEPT ![self] = "locked"]
/\ UNCHANGED << qspinlock, mcs_lock, priority, ret_trylock,
stack, lock_, val_, lock_s, lock_sp, lock, old,
next, prev, node, idx, cnt, priority_level >>
locked(self) == /\ pc[self] = "locked" /\ ProcessEnabled(self)
/\ IF val[self].tail_idx = idx[self] /\ val[self].tail_cpu = self[1]
THEN /\ old' = [old EXCEPT ![self] = qspinlock[lock[self]]]
/\ IF (qspinlock[lock[self]]) = val[self]
THEN /\ qspinlock' = [qspinlock EXCEPT ![lock[self]] = LOCKED_VAL]
ELSE /\ TRUE
/\ UNCHANGED qspinlock
/\ IF old'[self] = val[self]
THEN /\ pc' = [pc EXCEPT ![self] = "release"]
ELSE /\ pc' = [pc EXCEPT ![self] = "sp21"]
ELSE /\ pc' = [pc EXCEPT ![self] = "sp21"]
/\ UNCHANGED << qspinlock, old >>
/\ UNCHANGED << mcs_lock, priority, ret_trylock, stack, lock_,
val_, lock_s, lock_sp, lock, val, next, prev,
node, idx, cnt, priority_level >>
sp21(self) == /\ pc[self] = "sp21" /\ ProcessEnabled(self)
/\ qspinlock' = [qspinlock EXCEPT ![lock[self]].locked = TRUE]
/\ IF next[self] = NODE_ZERO
THEN /\ mcs_lock[node[self]].next # NODE_ZERO
/\ next' = [next EXCEPT ![self] = mcs_lock[node[self]].next]
ELSE /\ TRUE
/\ next' = next
/\ mcs_lock' = [mcs_lock EXCEPT ![next'[self]].locked = TRUE]
/\ pc' = [pc EXCEPT ![self] = "release"]
/\ UNCHANGED << priority, ret_trylock, stack, lock_, val_,
lock_s, lock_sp, lock, val, old, prev, node, idx,
cnt, priority_level >>
release(self) == /\ pc[self] = "release" /\ ProcessEnabled(self)
/\ mcs_lock' = [mcs_lock EXCEPT ![McsNode(self[1], 1)].count = mcs_lock[McsNode(self[1], 1)].count - 1]
/\ pc' = [pc EXCEPT ![self] = Head(stack[self]).pc]
/\ old' = [old EXCEPT ![self] = Head(stack[self]).old]
/\ next' = [next EXCEPT ![self] = Head(stack[self]).next]
/\ prev' = [prev EXCEPT ![self] = Head(stack[self]).prev]
/\ node' = [node EXCEPT ![self] = Head(stack[self]).node]
/\ idx' = [idx EXCEPT ![self] = Head(stack[self]).idx]
/\ cnt' = [cnt EXCEPT ![self] = Head(stack[self]).cnt]
/\ lock' = [lock EXCEPT ![self] = Head(stack[self]).lock]
/\ val' = [val EXCEPT ![self] = Head(stack[self]).val]
/\ stack' = [stack EXCEPT ![self] = Tail(stack[self])]
/\ UNCHANGED << qspinlock, priority, ret_trylock, lock_, val_,
lock_s, lock_sp, priority_level >>
spin_lock_slowpath(self) == sp1(self) \/ sp1_1(self) \/ sp1_2(self)
\/ sp2(self) \/ sp3(self) \/ sp4(self)
\/ queue(self) \/ sp11(self) \/ sp13(self)
\/ sp14(self) \/ sp15(self) \/ sp16(self)
\/ sp18(self) \/ locked(self) \/ sp21(self)
\/ release(self)
t1(self) == /\ pc[self] = "t1" /\ ProcessEnabled(self)
/\ priority_level' = [priority_level EXCEPT ![self] = priority[self[1]]]
/\ priority' = [priority EXCEPT ![self[1]] = self[2]]
/\ /\ lock_' = [lock_ EXCEPT ![self] = Lock(self)]
/\ stack' = [stack EXCEPT ![self] = << [ procedure |-> "spin_lock",
pc |-> "cs",
val_ |-> val_[self],
lock_ |-> lock_[self] ] >>
\o stack[self]]
/\ val_' = [val_ EXCEPT ![self] = defaultInitValue]
/\ pc' = [pc EXCEPT ![self] = "sl1"]
/\ UNCHANGED << qspinlock, mcs_lock, ret_trylock, lock_s, lock_sp,
lock, val, old, next, prev, node, idx, cnt >>
cs(self) == /\ pc[self] = "cs" /\ ProcessEnabled(self)
/\ TRUE
/\ /\ lock_s' = [lock_s EXCEPT ![self] = Lock(self)]
/\ stack' = [stack EXCEPT ![self] = << [ procedure |-> "spin_unlock",
pc |-> "t2",
lock_s |-> lock_s[self] ] >>
\o stack[self]]
/\ pc' = [pc EXCEPT ![self] = "su1"]
/\ UNCHANGED << qspinlock, mcs_lock, priority, ret_trylock, lock_,
val_, lock_sp, lock, val, old, next, prev, node,
idx, cnt, priority_level >>
t2(self) == /\ pc[self] = "t2" /\ ProcessEnabled(self)
/\ priority' = [priority EXCEPT ![self[1]] = priority_level[self]]
/\ pc' = [pc EXCEPT ![self] = "t1"]
/\ UNCHANGED << qspinlock, mcs_lock, ret_trylock, stack, lock_,
val_, lock_s, lock_sp, lock, val, old, next, prev,
node, idx, cnt, priority_level >>
thread(self) == t1(self) \/ cs(self) \/ t2(self)
Next == (\E self \in ProcSet: \/ spin_lock(self) \/ spin_unlock(self)
\/ spin_trylock(self)
\/ spin_lock_slowpath(self))
\/ (\E self \in THREADS: thread(self))
Spec == /\ Init /\ [][Next]_vars
/\ \A self \in THREADS : /\ WF_vars(thread(self))
/\ WF_vars(spin_lock(self))
/\ WF_vars(spin_unlock(self))
/\ WF_vars(spin_trylock(self))
/\ WF_vars(spin_lock_slowpath(self))
\* END TRANSLATION
=============================================================================