blob: 3023b206efb7d9357ea24c85f01891824a46f68e [file] [log] [blame]
------------------------------ MODULE spinbit ------------------------------
EXTENDS TLC, Integers, Sequences, FiniteSets
CONSTANT NumThreads
CONSTANT NumAcquires
CONSTANT NumSpins \* performance tunable "spin"
CONSTANT WakeAny \* reduce state space: wake any of the sleepers
CONSTANT NULL \* [ model value ]
ASSUME NumThreads >= 1
ASSUME NumAcquires >= 0
ASSUME NumSpins >= 0
ASSUME WakeAny \in BOOLEAN
Threads == 1..NumThreads
(* --algorithm spinbit
variables
\* logical owner
owner = NULL;
\* lock state, stored in l.key word
l_key_locked = FALSE; \* bit 0
l_key_sleeping = FALSE; \* bit 1
l_key_spinning = FALSE; \* bit 63
\* local variables
v_locked = [t \in Threads |-> FALSE];
v_sleeping = [t \in Threads |-> FALSE];
v_spinning = [t \in Threads |-> FALSE];
v8_locked = [t \in Threads |-> FALSE];
v8_sleeping = [t \in Threads |-> FALSE];
weSpin = [t \in Threads |-> FALSE];
i = [t \in Threads |-> 0];
\* OS state
sleepers = {};
\*
acquisitions = 0;
\* performance tunables
spin = NumSpins;
define
TypeInvariant ==
/\ owner \in Threads \union {NULL}
/\ l_key_locked \in BOOLEAN
/\ l_key_sleeping \in BOOLEAN
/\ l_key_spinning \in BOOLEAN
/\ \A t \in Threads: v_locked[t] \in BOOLEAN
/\ \A t \in Threads: v_sleeping[t] \in BOOLEAN
/\ \A t \in Threads: v_spinning[t] \in BOOLEAN
/\ \A t \in Threads: v8_locked[t] \in BOOLEAN
/\ \A t \in Threads: v8_sleeping[t] \in BOOLEAN
/\ \A t \in Threads: weSpin[t] \in BOOLEAN
/\ \A t \in Threads: i[t] \in Nat
/\ sleepers \subseteq Threads
/\ acquisitions \in Nat
NoLostWakeups ==
(Cardinality(sleepers) > 0) ~> (Cardinality(sleepers) = 0)
HaveAcquisitions ==
<>[](acquisitions >= NumAcquires)
end define;
\* This is a lock, so if a process dies while holding it the rest of the simulation
\* won't be able to make progress. We need "fair process" so the threads don't die.
fair process thread \in Threads
begin
NonCriticalSection:
if acquisitions >= NumAcquires then
goto Done;
end if;
SpeculativeGrab:
\* v8 = atomic.Xchg8(k8, mutexLocked)
v8_locked[self] := l_key_locked;
v8_sleeping[self] := l_key_sleeping;
l_key_locked := TRUE;
l_key_sleeping := FALSE;
SpeculativeGrabCheck:
if v8_locked[self] = FALSE then
if v8_sleeping[self] = TRUE then
\* atomic.Or8(k8, mutexSleeping)
l_key_sleeping := TRUE;
end if;
goto Locked;
else
\* var v uintptr = mutexLocked
v_locked[self] := TRUE;
v_sleeping[self] := FALSE;
v_spinning[self] := FALSE;
weSpin[self] := FALSE;
end if;
EnterSlowPath:
i[self] := 0;
TryAcquire:
if v_locked[self] = TRUE then
goto SetSleepBit
else
if weSpin[self] then
\* next := (v &^ mutexSpinning) | mutexLocked | mutexSleeping
\* if atomic.Casuintptr(&l.key, v, next) {
if v_locked[self] = l_key_locked
/\ v_sleeping[self] = l_key_sleeping
/\ v_spinning[self] = l_key_spinning then
l_key_locked := TRUE;
l_key_sleeping := TRUE;
l_key_spinning := FALSE;
goto Locked;
end if;
else
\* prev8 := atomic.Xchg8(k8, mutexLocked|mutexSleeping)
\* if prev8&mutexLocked == 0 {
if l_key_locked = FALSE then
l_key_locked := TRUE;
l_key_sleeping := TRUE;
goto Locked;
else
l_key_locked := TRUE;
l_key_sleeping := TRUE;
end if;
end if;
end if;
TryAcquireLoop:
\* v = atomic.Loaduintptr(&l.key)
v_locked[self] := l_key_locked;
v_sleeping[self] := l_key_sleeping;
v_spinning[self] := l_key_spinning;
goto TryAcquire;
SetSleepBit:
\* atomic.Or8(k8, mutexSleeping)
if v_sleeping[self] = FALSE then
l_key_sleeping := TRUE;
v_sleeping[self] := TRUE;
end if;
SetSpinBit:
\* if !weSpin && atomic.Xchg8(key8Upper(&l.key), mutexSpinning>>((goarch.PtrSize-1)*8)) == 0 {
if weSpin[self] = FALSE then
weSpin[self] := (l_key_spinning = FALSE);
l_key_spinning := TRUE;
end if;
DoSpin:
if weSpin[self] = TRUE then
if i[self] < spin then
\* procyield(active_spin_cnt)
\* v = atomic.Loaduintptr(&l.key)
v_locked[self] := l_key_locked;
v_sleeping[self] := l_key_sleeping;
v_spinning[self] := l_key_spinning;
goto TryAcquire;
else
weSpin[self] := FALSE;
\* atomic.Xchg8(key8Upper(&l.key), 0)
l_key_spinning := FALSE;
end if;
end if;
Sleep:
\* v = atomic.Loaduintptr(&l.key)
v_locked[self] := l_key_locked;
v_sleeping[self] := l_key_sleeping;
v_spinning[self] := l_key_spinning;
Futex:
if v_locked[self] = FALSE then
goto TryAcquire;
else
\* futexsleep(k32, uint32(v|mutexSleeping), -1)
if l_key_locked = v_locked[self]
/\ l_key_sleeping = TRUE then
sleepers := sleepers \union {self};
end if;
end if;
Sleeping:
await self \notin sleepers;
i[self] := 0;
\* v = atomic.Loaduintptr(&l.key)
v_locked[self] := l_key_locked;
v_sleeping[self] := l_key_sleeping;
v_spinning[self] := l_key_spinning;
goto TryAcquire;
Locked:
owner := self;
CriticalSection:
acquisitions := acquisitions + 1;
Unlock:
owner := NULL;
\* prev8 := atomic.Xchg8(key8(&l.key), 0)
v8_locked[self] := l_key_locked;
v8_sleeping[self] := l_key_sleeping;
l_key_locked := FALSE;
l_key_sleeping := FALSE;
if v8_sleeping[self] = TRUE then
goto Wakeup;
else
goto NonCriticalSection;
end if;
Wakeup:
\* v := atomic.Loaduintptr(&l.key)
v_locked[self] := l_key_locked;
v_sleeping[self] := l_key_sleeping;
v_spinning[self] := l_key_spinning;
Wake2:
if v_spinning[self] = FALSE then
if Cardinality(sleepers) > 0 then
if WakeAny then
with other = CHOOSE other \in sleepers: TRUE do
sleepers := sleepers \ {other};
end with;
else
with other \in sleepers do
sleepers := sleepers \ {other};
end with;
end if;
end if;
end if;
goto NonCriticalSection;
end process;
end algorithm; *)
\* BEGIN TRANSLATION (chksum(pcal) = "a993096" /\ chksum(tla) = "41bbe278")
VARIABLES owner, l_key_locked, l_key_sleeping, l_key_spinning, v_locked,
v_sleeping, v_spinning, v8_locked, v8_sleeping, weSpin, i, sleepers,
acquisitions, spin, pc
(* define statement *)
TypeInvariant ==
/\ owner \in Threads \union {NULL}
/\ l_key_locked \in BOOLEAN
/\ l_key_sleeping \in BOOLEAN
/\ l_key_spinning \in BOOLEAN
/\ \A t \in Threads: v_locked[t] \in BOOLEAN
/\ \A t \in Threads: v_sleeping[t] \in BOOLEAN
/\ \A t \in Threads: v_spinning[t] \in BOOLEAN
/\ \A t \in Threads: v8_locked[t] \in BOOLEAN
/\ \A t \in Threads: v8_sleeping[t] \in BOOLEAN
/\ \A t \in Threads: weSpin[t] \in BOOLEAN
/\ \A t \in Threads: i[t] \in Nat
/\ sleepers \subseteq Threads
/\ acquisitions \in Nat
NoLostWakeups ==
(Cardinality(sleepers) > 0) ~> (Cardinality(sleepers) = 0)
HaveAcquisitions ==
<>[](acquisitions >= NumAcquires)
vars == << owner, l_key_locked, l_key_sleeping, l_key_spinning, v_locked,
v_sleeping, v_spinning, v8_locked, v8_sleeping, weSpin, i,
sleepers, acquisitions, spin, pc >>
ProcSet == (Threads)
Init == (* Global variables *)
/\ owner = NULL
/\ l_key_locked = FALSE
/\ l_key_sleeping = FALSE
/\ l_key_spinning = FALSE
/\ v_locked = [t \in Threads |-> FALSE]
/\ v_sleeping = [t \in Threads |-> FALSE]
/\ v_spinning = [t \in Threads |-> FALSE]
/\ v8_locked = [t \in Threads |-> FALSE]
/\ v8_sleeping = [t \in Threads |-> FALSE]
/\ weSpin = [t \in Threads |-> FALSE]
/\ i = [t \in Threads |-> 0]
/\ sleepers = {}
/\ acquisitions = 0
/\ spin = NumSpins
/\ pc = [self \in ProcSet |-> "NonCriticalSection"]
NonCriticalSection(self) == /\ pc[self] = "NonCriticalSection"
/\ IF acquisitions >= NumAcquires
THEN /\ pc' = [pc EXCEPT ![self] = "Done"]
ELSE /\ pc' = [pc EXCEPT ![self] = "SpeculativeGrab"]
/\ UNCHANGED << owner, l_key_locked,
l_key_sleeping, l_key_spinning,
v_locked, v_sleeping, v_spinning,
v8_locked, v8_sleeping, weSpin, i,
sleepers, acquisitions, spin >>
SpeculativeGrab(self) == /\ pc[self] = "SpeculativeGrab"
/\ v8_locked' = [v8_locked EXCEPT ![self] = l_key_locked]
/\ v8_sleeping' = [v8_sleeping EXCEPT ![self] = l_key_sleeping]
/\ l_key_locked' = TRUE
/\ l_key_sleeping' = FALSE
/\ pc' = [pc EXCEPT ![self] = "SpeculativeGrabCheck"]
/\ UNCHANGED << owner, l_key_spinning, v_locked,
v_sleeping, v_spinning, weSpin, i,
sleepers, acquisitions, spin >>
SpeculativeGrabCheck(self) == /\ pc[self] = "SpeculativeGrabCheck"
/\ IF v8_locked[self] = FALSE
THEN /\ IF v8_sleeping[self] = TRUE
THEN /\ l_key_sleeping' = TRUE
ELSE /\ TRUE
/\ UNCHANGED l_key_sleeping
/\ pc' = [pc EXCEPT ![self] = "Locked"]
/\ UNCHANGED << v_locked, v_sleeping,
v_spinning, weSpin >>
ELSE /\ v_locked' = [v_locked EXCEPT ![self] = TRUE]
/\ v_sleeping' = [v_sleeping EXCEPT ![self] = FALSE]
/\ v_spinning' = [v_spinning EXCEPT ![self] = FALSE]
/\ weSpin' = [weSpin EXCEPT ![self] = FALSE]
/\ pc' = [pc EXCEPT ![self] = "EnterSlowPath"]
/\ UNCHANGED l_key_sleeping
/\ UNCHANGED << owner, l_key_locked,
l_key_spinning, v8_locked,
v8_sleeping, i, sleepers,
acquisitions, spin >>
EnterSlowPath(self) == /\ pc[self] = "EnterSlowPath"
/\ i' = [i EXCEPT ![self] = 0]
/\ pc' = [pc EXCEPT ![self] = "TryAcquire"]
/\ UNCHANGED << owner, l_key_locked, l_key_sleeping,
l_key_spinning, v_locked, v_sleeping,
v_spinning, v8_locked, v8_sleeping,
weSpin, sleepers, acquisitions, spin >>
TryAcquire(self) == /\ pc[self] = "TryAcquire"
/\ IF v_locked[self] = TRUE
THEN /\ pc' = [pc EXCEPT ![self] = "SetSleepBit"]
/\ UNCHANGED << l_key_locked, l_key_sleeping,
l_key_spinning >>
ELSE /\ IF weSpin[self]
THEN /\ IF v_locked[self] = l_key_locked
/\ v_sleeping[self] = l_key_sleeping
/\ v_spinning[self] = l_key_spinning
THEN /\ l_key_locked' = TRUE
/\ l_key_sleeping' = TRUE
/\ l_key_spinning' = FALSE
/\ pc' = [pc EXCEPT ![self] = "Locked"]
ELSE /\ pc' = [pc EXCEPT ![self] = "TryAcquireLoop"]
/\ UNCHANGED << l_key_locked,
l_key_sleeping,
l_key_spinning >>
ELSE /\ IF l_key_locked = FALSE
THEN /\ l_key_locked' = TRUE
/\ l_key_sleeping' = TRUE
/\ pc' = [pc EXCEPT ![self] = "Locked"]
ELSE /\ l_key_locked' = TRUE
/\ l_key_sleeping' = TRUE
/\ pc' = [pc EXCEPT ![self] = "TryAcquireLoop"]
/\ UNCHANGED l_key_spinning
/\ UNCHANGED << owner, v_locked, v_sleeping, v_spinning,
v8_locked, v8_sleeping, weSpin, i,
sleepers, acquisitions, spin >>
TryAcquireLoop(self) == /\ pc[self] = "TryAcquireLoop"
/\ v_locked' = [v_locked EXCEPT ![self] = l_key_locked]
/\ v_sleeping' = [v_sleeping EXCEPT ![self] = l_key_sleeping]
/\ v_spinning' = [v_spinning EXCEPT ![self] = l_key_spinning]
/\ pc' = [pc EXCEPT ![self] = "TryAcquire"]
/\ UNCHANGED << owner, l_key_locked, l_key_sleeping,
l_key_spinning, v8_locked, v8_sleeping,
weSpin, i, sleepers, acquisitions,
spin >>
SetSleepBit(self) == /\ pc[self] = "SetSleepBit"
/\ IF v_sleeping[self] = FALSE
THEN /\ l_key_sleeping' = TRUE
/\ v_sleeping' = [v_sleeping EXCEPT ![self] = TRUE]
ELSE /\ TRUE
/\ UNCHANGED << l_key_sleeping, v_sleeping >>
/\ pc' = [pc EXCEPT ![self] = "SetSpinBit"]
/\ UNCHANGED << owner, l_key_locked, l_key_spinning,
v_locked, v_spinning, v8_locked,
v8_sleeping, weSpin, i, sleepers,
acquisitions, spin >>
SetSpinBit(self) == /\ pc[self] = "SetSpinBit"
/\ IF weSpin[self] = FALSE
THEN /\ weSpin' = [weSpin EXCEPT ![self] = (l_key_spinning = FALSE)]
/\ l_key_spinning' = TRUE
ELSE /\ TRUE
/\ UNCHANGED << l_key_spinning, weSpin >>
/\ pc' = [pc EXCEPT ![self] = "DoSpin"]
/\ UNCHANGED << owner, l_key_locked, l_key_sleeping,
v_locked, v_sleeping, v_spinning,
v8_locked, v8_sleeping, i, sleepers,
acquisitions, spin >>
DoSpin(self) == /\ pc[self] = "DoSpin"
/\ IF weSpin[self] = TRUE
THEN /\ IF i[self] < spin
THEN /\ v_locked' = [v_locked EXCEPT ![self] = l_key_locked]
/\ v_sleeping' = [v_sleeping EXCEPT ![self] = l_key_sleeping]
/\ v_spinning' = [v_spinning EXCEPT ![self] = l_key_spinning]
/\ pc' = [pc EXCEPT ![self] = "TryAcquire"]
/\ UNCHANGED << l_key_spinning, weSpin >>
ELSE /\ weSpin' = [weSpin EXCEPT ![self] = FALSE]
/\ l_key_spinning' = FALSE
/\ pc' = [pc EXCEPT ![self] = "Sleep"]
/\ UNCHANGED << v_locked, v_sleeping,
v_spinning >>
ELSE /\ pc' = [pc EXCEPT ![self] = "Sleep"]
/\ UNCHANGED << l_key_spinning, v_locked,
v_sleeping, v_spinning, weSpin >>
/\ UNCHANGED << owner, l_key_locked, l_key_sleeping, v8_locked,
v8_sleeping, i, sleepers, acquisitions, spin >>
Sleep(self) == /\ pc[self] = "Sleep"
/\ v_locked' = [v_locked EXCEPT ![self] = l_key_locked]
/\ v_sleeping' = [v_sleeping EXCEPT ![self] = l_key_sleeping]
/\ v_spinning' = [v_spinning EXCEPT ![self] = l_key_spinning]
/\ pc' = [pc EXCEPT ![self] = "Futex"]
/\ UNCHANGED << owner, l_key_locked, l_key_sleeping,
l_key_spinning, v8_locked, v8_sleeping, weSpin,
i, sleepers, acquisitions, spin >>
Futex(self) == /\ pc[self] = "Futex"
/\ IF v_locked[self] = FALSE
THEN /\ pc' = [pc EXCEPT ![self] = "TryAcquire"]
/\ UNCHANGED sleepers
ELSE /\ IF l_key_locked = v_locked[self]
/\ l_key_sleeping = TRUE
THEN /\ sleepers' = (sleepers \union {self})
ELSE /\ TRUE
/\ UNCHANGED sleepers
/\ pc' = [pc EXCEPT ![self] = "Sleeping"]
/\ UNCHANGED << owner, l_key_locked, l_key_sleeping,
l_key_spinning, v_locked, v_sleeping,
v_spinning, v8_locked, v8_sleeping, weSpin, i,
acquisitions, spin >>
Sleeping(self) == /\ pc[self] = "Sleeping"
/\ self \notin sleepers
/\ i' = [i EXCEPT ![self] = 0]
/\ v_locked' = [v_locked EXCEPT ![self] = l_key_locked]
/\ v_sleeping' = [v_sleeping EXCEPT ![self] = l_key_sleeping]
/\ v_spinning' = [v_spinning EXCEPT ![self] = l_key_spinning]
/\ pc' = [pc EXCEPT ![self] = "TryAcquire"]
/\ UNCHANGED << owner, l_key_locked, l_key_sleeping,
l_key_spinning, v8_locked, v8_sleeping,
weSpin, sleepers, acquisitions, spin >>
Locked(self) == /\ pc[self] = "Locked"
/\ owner' = self
/\ pc' = [pc EXCEPT ![self] = "CriticalSection"]
/\ UNCHANGED << l_key_locked, l_key_sleeping, l_key_spinning,
v_locked, v_sleeping, v_spinning, v8_locked,
v8_sleeping, weSpin, i, sleepers, acquisitions,
spin >>
CriticalSection(self) == /\ pc[self] = "CriticalSection"
/\ acquisitions' = acquisitions + 1
/\ pc' = [pc EXCEPT ![self] = "Unlock"]
/\ UNCHANGED << owner, l_key_locked, l_key_sleeping,
l_key_spinning, v_locked, v_sleeping,
v_spinning, v8_locked, v8_sleeping,
weSpin, i, sleepers, spin >>
Unlock(self) == /\ pc[self] = "Unlock"
/\ owner' = NULL
/\ v8_locked' = [v8_locked EXCEPT ![self] = l_key_locked]
/\ v8_sleeping' = [v8_sleeping EXCEPT ![self] = l_key_sleeping]
/\ l_key_locked' = FALSE
/\ l_key_sleeping' = FALSE
/\ IF v8_sleeping'[self] = TRUE
THEN /\ pc' = [pc EXCEPT ![self] = "Wakeup"]
ELSE /\ pc' = [pc EXCEPT ![self] = "NonCriticalSection"]
/\ UNCHANGED << l_key_spinning, v_locked, v_sleeping,
v_spinning, weSpin, i, sleepers, acquisitions,
spin >>
Wakeup(self) == /\ pc[self] = "Wakeup"
/\ v_locked' = [v_locked EXCEPT ![self] = l_key_locked]
/\ v_sleeping' = [v_sleeping EXCEPT ![self] = l_key_sleeping]
/\ v_spinning' = [v_spinning EXCEPT ![self] = l_key_spinning]
/\ pc' = [pc EXCEPT ![self] = "Wake2"]
/\ UNCHANGED << owner, l_key_locked, l_key_sleeping,
l_key_spinning, v8_locked, v8_sleeping, weSpin,
i, sleepers, acquisitions, spin >>
Wake2(self) == /\ pc[self] = "Wake2"
/\ IF v_spinning[self] = FALSE
THEN /\ IF Cardinality(sleepers) > 0
THEN /\ IF WakeAny
THEN /\ LET other == CHOOSE other \in sleepers: TRUE IN
sleepers' = sleepers \ {other}
ELSE /\ \E other \in sleepers:
sleepers' = sleepers \ {other}
ELSE /\ TRUE
/\ UNCHANGED sleepers
ELSE /\ TRUE
/\ UNCHANGED sleepers
/\ pc' = [pc EXCEPT ![self] = "NonCriticalSection"]
/\ UNCHANGED << owner, l_key_locked, l_key_sleeping,
l_key_spinning, v_locked, v_sleeping,
v_spinning, v8_locked, v8_sleeping, weSpin, i,
acquisitions, spin >>
thread(self) == NonCriticalSection(self) \/ SpeculativeGrab(self)
\/ SpeculativeGrabCheck(self) \/ EnterSlowPath(self)
\/ TryAcquire(self) \/ TryAcquireLoop(self)
\/ SetSleepBit(self) \/ SetSpinBit(self) \/ DoSpin(self)
\/ Sleep(self) \/ Futex(self) \/ Sleeping(self)
\/ Locked(self) \/ CriticalSection(self) \/ Unlock(self)
\/ Wakeup(self) \/ Wake2(self)
(* Allow infinite stuttering to prevent deadlock on termination. *)
Terminating == /\ \A self \in ProcSet: pc[self] = "Done"
/\ UNCHANGED vars
Next == (\E self \in Threads: thread(self))
\/ Terminating
Spec == /\ Init /\ [][Next]_vars
/\ \A self \in Threads : WF_vars(thread(self))
Termination == <>(\A self \in ProcSet: pc[self] = "Done")
\* END TRANSLATION
=============================================================================
\* Modification History
\* Last modified Tue Oct 01 13:18:42 PDT 2024 by rhysh
\* Created Tue Sep 17 12:10:43 PDT 2024 by rhysh