use crate::platform::*;
const SENTINEL: u32 = u32::MAX;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
struct PackedState {
index: u32,
generation: u32,
}
impl PackedState {
#[inline]
#[allow(clippy::cast_possible_truncation)]
fn from_u64(val: u64) -> Self {
Self {
index: val as u32,
generation: (val >> 32) as u32,
}
}
#[inline]
fn to_u64(self) -> u64 {
u64::from(self.index) | (u64::from(self.generation) << 32)
}
}
struct StackNode<T> {
data: UnsafeCell<MaybeUninit<T>>,
next: AtomicU32,
}
unsafe impl<T: Send> Send for StackNode<T> {}
unsafe impl<T: Sync> Sync for StackNode<T> {}
pub struct LockFreeStack<T> {
nodes: Box<[StackNode<T>]>,
occupied_head: AtomicU64,
free_head: AtomicU64,
len: AtomicUsize,
}
pub const DEFAULT_STACK_CAPACITY: usize = 1024;
impl<T> LockFreeStack<T> {
#[must_use]
pub fn new() -> Self {
Self::with_capacity(DEFAULT_STACK_CAPACITY)
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
assert!(
capacity < SENTINEL as usize,
"LockFreeStack capacity must be < u32::MAX"
);
let mut nodes = Vec::with_capacity(capacity);
for i in 0..capacity {
nodes.push(StackNode {
data: UnsafeCell::new(MaybeUninit::uninit()),
next: AtomicU32::new(
u32::try_from(i).expect("invariant: i < capacity < u32::MAX (asserted above)")
+ 1,
),
});
}
if capacity > 0 {
nodes[capacity - 1].next.store(SENTINEL, Ordering::Relaxed);
}
Self {
nodes: nodes.into_boxed_slice(),
occupied_head: AtomicU64::new(
PackedState {
index: SENTINEL,
generation: 0,
}
.to_u64(),
),
free_head: AtomicU64::new(
PackedState {
index: if capacity > 0 { 0 } else { SENTINEL },
generation: 0,
}
.to_u64(),
),
len: AtomicUsize::new(0),
}
}
#[inline]
fn push_list(&self, head: &AtomicU64, index: u32) {
loop {
let current_val = head.load(Ordering::Acquire);
let state = PackedState::from_u64(current_val);
self.nodes[index as usize]
.next
.store(state.index, Ordering::Release);
let new_state = PackedState {
index,
generation: state.generation.wrapping_add(1),
};
if head
.compare_exchange_weak(
current_val,
new_state.to_u64(),
Ordering::Release,
Ordering::Relaxed,
)
.is_ok()
{
break;
}
}
}
#[inline]
fn pop_list(&self, head: &AtomicU64) -> Option<u32> {
loop {
let current_val = head.load(Ordering::Acquire);
let state = PackedState::from_u64(current_val);
if state.index == SENTINEL {
return None;
}
let next = self.nodes[state.index as usize]
.next
.load(Ordering::Acquire);
let new_state = PackedState {
index: next,
generation: state.generation.wrapping_add(1),
};
if head
.compare_exchange_weak(
current_val,
new_state.to_u64(),
Ordering::Release,
Ordering::Relaxed,
)
.is_ok()
{
return Some(state.index);
}
}
}
pub fn push(&self, item: T) -> core::result::Result<(), T> {
if let Some(index) = self.pop_list(&self.free_head) {
unsafe {
(*self.nodes[index as usize].data.get()).write(item);
}
self.push_list(&self.occupied_head, index);
self.len.fetch_add(1, Ordering::Release);
Ok(())
} else {
Err(item)
}
}
pub fn pop(&self) -> Option<T> {
if let Some(index) = self.pop_list(&self.occupied_head) {
self.len.fetch_sub(1, Ordering::Release);
let item = unsafe { (*self.nodes[index as usize].data.get()).assume_init_read() };
self.push_list(&self.free_head, index);
Some(item)
} else {
None
}
}
pub fn len(&self) -> usize {
self.len.load(Ordering::Acquire)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn capacity(&self) -> usize {
self.nodes.len()
}
}
impl<T> Default for LockFreeStack<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Drop for LockFreeStack<T> {
fn drop(&mut self) {
while self.pop().is_some() {}
}
}
unsafe impl<T: Send> Send for LockFreeStack<T> {}
unsafe impl<T: Send> Sync for LockFreeStack<T> {}
pub use moirai_utils::cache::CacheAligned;