use core::sync::atomic::compiler_fence;
use crate::sync::internal::{AtomicU8, AtomicU64, Ordering, fence};
pub(crate) const NONE: u8 = 0;
pub(crate) const PENDING: u8 = 1;
pub(crate) const TAKEN: u8 = 2;
pub(crate) const fn outside(epoch: u64) -> u64 {
(epoch << 1) | 1
}
pub(crate) const fn inside(epoch: u64) -> u64 {
epoch << 1
}
pub(crate) const fn is_outside(word: u64) -> bool {
word & 1 == 1
}
#[cfg(test)]
pub(crate) const fn epoch(word: u64) -> u64 {
word >> 1
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Back {
In,
Taken,
}
pub(crate) struct Slot {
word: AtomicU64,
claim: AtomicU8,
full_fence: bool,
}
impl Slot {
pub(crate) fn new(full_fence: bool) -> Self {
Self {
word: AtomicU64::new(inside(0)),
claim: AtomicU8::new(NONE),
full_fence,
}
}
#[inline]
pub(crate) fn go_out(&self, epoch: u64) {
self.word.store(outside(epoch), Ordering::Release);
}
#[inline]
pub(crate) fn come_back(&self, epoch: u64) -> Back {
self.word.store(inside(epoch), Ordering::Relaxed);
if self.full_fence {
fence(Ordering::SeqCst);
} else {
compiler_fence(Ordering::SeqCst);
}
if self.claim.load(Ordering::Relaxed) == NONE {
return Back::In;
}
self.contest()
}
#[cold]
#[inline(never)]
fn contest(&self) -> Back {
match self
.claim
.compare_exchange(PENDING, NONE, Ordering::Acquire, Ordering::Acquire)
{
Ok(_) => Back::In,
Err(TAKEN) => Back::Taken,
Err(_) => Back::In,
}
}
pub(crate) fn word(&self) -> u64 {
self.word.load(Ordering::Acquire)
}
pub(crate) fn claim(&self) {
self.claim.store(PENDING, Ordering::Relaxed);
}
pub(crate) fn take(&self) -> bool {
self.claim
.compare_exchange(PENDING, TAKEN, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
}
pub(crate) fn cancel(&self) {
let _ = self
.claim
.compare_exchange(PENDING, NONE, Ordering::AcqRel, Ordering::Relaxed);
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
#[test]
fn the_word_packs_its_epoch_and_side_and_unpacks_them() {
for e in [0, 1, 2, 41, u64::MAX >> 1] {
assert!(is_outside(outside(e)));
assert!(!is_outside(inside(e)));
assert_eq!(epoch(outside(e)), e);
assert_eq!(epoch(inside(e)), e);
assert_ne!(outside(e), inside(e));
}
assert_ne!(outside(1), outside(2), "a new poll changes the word");
}
#[test]
fn a_runner_with_no_claim_goes_back_in() {
for full in [false, true] {
let slot = Slot::new(full);
slot.go_out(1);
assert_eq!(slot.word(), outside(1));
assert_eq!(slot.come_back(1), Back::In);
assert_eq!(slot.word(), inside(1));
}
}
#[test]
fn a_runner_back_first_cancels_the_claim_and_the_take_then_fails() {
let slot = Slot::new(false);
slot.go_out(3);
slot.claim();
assert_eq!(slot.come_back(3), Back::In);
assert!(!slot.take(), "the runner cancelled first");
assert_eq!(slot.come_back(3), Back::In);
}
#[test]
fn a_take_before_the_runner_is_back_detaches_it() {
let slot = Slot::new(false);
slot.go_out(5);
slot.claim();
assert!(slot.take());
assert_eq!(slot.come_back(5), Back::Taken);
assert_eq!(slot.come_back(5), Back::Taken, "for good");
}
#[test]
fn a_cancel_by_the_monitor_lets_the_runner_back_in() {
let slot = Slot::new(true);
slot.go_out(7);
slot.claim();
slot.cancel();
assert_eq!(slot.come_back(7), Back::In);
assert!(!slot.take(), "nothing pending");
}
}