#![allow(unsafe_code)]
use crate::sync::internal::{AtomicBool, Ordering, UnsafeCell};
#[cfg(not(all(loom, test)))]
mod radix;
#[cfg(not(all(loom, test)))]
mod ring;
#[cfg(not(all(loom, test)))]
pub(super) use self::radix::Radix;
pub(super) enum Popped<T> {
Value(T),
Stashed(T),
Nothing,
}
pub(super) struct Stash<T> {
value: UnsafeCell<Option<T>>,
held: AtomicBool,
}
impl<T> Stash<T> {
pub(super) fn new() -> Self {
Self {
value: UnsafeCell::new(None),
held: AtomicBool::new(false),
}
}
pub(super) unsafe fn put(&self, value: T) {
self.value.with_mut(|slot| unsafe { *slot = Some(value) });
self.held.store(true, Ordering::Release);
}
pub(super) unsafe fn take(&self) -> Option<T> {
self.held.store(false, Ordering::Release);
self.value.with_mut(|slot| unsafe { (*slot).take() })
}
pub(super) fn is_held(&self) -> bool {
self.held.load(Ordering::Acquire)
}
}
pub(super) trait MessageQueue<T> {
fn push(&self, value: T);
unsafe fn try_pop(&self) -> Popped<T>;
unsafe fn put_back(&self, value: T);
fn len(&self) -> usize;
}
#[cfg(not(all(loom, test)))]
pub(super) enum Bounded<T> {
Ring(ring::Ring<T>),
Radix(Radix<T>),
}
#[cfg(not(all(loom, test)))]
impl<T> Bounded<T> {
pub(super) fn with_capacity(capacity: usize) -> Self {
if (capacity as u64) <= ring::MAX_CELLS {
Self::Ring(ring::Ring::new(capacity))
} else {
Self::Radix(Radix::new())
}
}
}
#[cfg(not(all(loom, test)))]
impl<T> MessageQueue<T> for Bounded<T> {
#[inline]
fn push(&self, value: T) {
match self {
Self::Ring(ring) => ring.push(value),
Self::Radix(radix) => radix.push(value),
}
}
#[inline(always)]
unsafe fn try_pop(&self) -> Popped<T> {
match self {
Self::Ring(ring) => unsafe { ring.try_pop() },
Self::Radix(radix) => unsafe { radix.try_pop() },
}
}
unsafe fn put_back(&self, value: T) {
match self {
Self::Ring(ring) => unsafe { ring.put_back(value) },
Self::Radix(radix) => unsafe { radix.put_back(value) },
}
}
fn len(&self) -> usize {
match self {
Self::Ring(ring) => ring.len(),
Self::Radix(radix) => radix.len(),
}
}
}
#[cfg(not(all(loom, test)))]
impl<T> MessageQueue<T> for Radix<T> {
#[inline]
fn push(&self, value: T) {
Radix::push(self, value);
}
#[inline]
unsafe fn try_pop(&self) -> Popped<T> {
unsafe { Radix::try_pop(self) }
}
unsafe fn put_back(&self, value: T) {
unsafe { Radix::put_back(self, value) }
}
fn len(&self) -> usize {
Radix::len(self)
}
}
#[cfg(all(loom, test))]
const LOOM_CELLS: usize = 8;
#[cfg(all(loom, test))]
struct LoomCell<T> {
ready: crate::sync::internal::AtomicBool,
claimed: std::sync::atomic::AtomicBool,
value: crate::sync::internal::UnsafeCell<Option<T>>,
}
#[cfg(all(loom, test))]
pub(super) struct Queue<T> {
tail: crate::sync::internal::AtomicU64,
head: crate::sync::internal::AtomicU64,
cells: [LoomCell<T>; LOOM_CELLS],
stash: Stash<T>,
}
#[cfg(all(loom, test))]
unsafe impl<T: Send> Send for Queue<T> {}
#[cfg(all(loom, test))]
unsafe impl<T: Send> Sync for Queue<T> {}
#[cfg(all(loom, test))]
impl<T> Queue<T> {
pub(super) fn new() -> Self {
Self {
tail: crate::sync::internal::AtomicU64::new(0),
head: crate::sync::internal::AtomicU64::new(0),
cells: core::array::from_fn(|_| LoomCell {
ready: crate::sync::internal::AtomicBool::new(false),
claimed: std::sync::atomic::AtomicBool::new(false),
value: crate::sync::internal::UnsafeCell::new(None),
}),
stash: Stash::new(),
}
}
pub(super) fn push(&self, value: T) {
use crate::sync::internal::Ordering;
let pos = self.tail.fetch_add(1, Ordering::SeqCst) as usize;
assert!(
pos < LOOM_CELLS,
"a loom model sent more than {LOOM_CELLS} values"
);
let cell = &self.cells[pos];
cell.claimed
.store(true, std::sync::atomic::Ordering::Relaxed);
cell.value.with_mut(|v| unsafe { *v = Some(value) });
cell.ready.store(true, Ordering::SeqCst);
}
pub(super) unsafe fn try_pop(&self) -> Popped<T> {
use crate::sync::internal::Ordering;
if let Some(value) = unsafe { self.stash.take() } {
return Popped::Stashed(value);
}
let pos = self.head.load(Ordering::Relaxed) as usize;
let Some(cell) = self.cells.get(pos) else {
return Popped::Nothing;
};
if !cell.ready.load(Ordering::SeqCst) {
if cell.claimed.load(std::sync::atomic::Ordering::Relaxed) {
crate::sync::loom_model::busy_look();
}
return Popped::Nothing;
}
let value = cell.value.with_mut(|v| unsafe { (*v).take() });
self.head.store(pos as u64 + 1, Ordering::Relaxed);
value.map_or(Popped::Nothing, Popped::Value)
}
pub(super) unsafe fn put_back(&self, value: T) {
unsafe { self.stash.put(value) };
}
pub(super) fn len(&self) -> usize {
use crate::sync::internal::Ordering;
let front = self.head.load(Ordering::Acquire);
let back = self.tail.load(Ordering::Acquire);
back.saturating_sub(front) as usize + usize::from(self.stash.is_held())
}
}
#[cfg(all(loom, test))]
pub(super) type Radix<T> = Queue<T>;
#[cfg(all(loom, test))]
pub(super) type Bounded<T> = Queue<T>;
#[cfg(all(loom, test))]
impl<T> Queue<T> {
pub(super) fn with_capacity(_capacity: usize) -> Self {
Self::new()
}
}
#[cfg(all(loom, test))]
impl<T> MessageQueue<T> for Queue<T> {
fn push(&self, value: T) {
Queue::push(self, value);
}
unsafe fn try_pop(&self) -> Popped<T> {
unsafe { Queue::try_pop(self) }
}
unsafe fn put_back(&self, value: T) {
unsafe { Queue::put_back(self, value) }
}
fn len(&self) -> usize {
Queue::len(self)
}
}