use core::hint;
use core::ptr::{self, NonNull};
use core::sync::atomic::{AtomicPtr, Ordering};
pub unsafe trait Node: Sized {
unsafe fn get_next(item: *mut Self) -> *mut Self;
unsafe fn set_next(item: *mut Self, ptr: *mut Self);
unsafe fn atomic_load_next(item: *mut Self, ordering: Ordering) -> *mut Self;
unsafe fn atomic_store_next(item: *mut Self, ptr: *mut Self, ordering: Ordering);
}
#[repr(transparent)]
pub struct Link<T>(AtomicPtr<T>);
impl<T> Link<T> {
#[inline]
pub const fn new() -> Self {
Self(AtomicPtr::new(ptr::null_mut()))
}
#[inline]
pub fn is_null(&self) -> bool {
self.0.load(Ordering::Relaxed).is_null()
}
#[inline]
pub fn clear(&self) {
self.0.store(ptr::null_mut(), Ordering::Relaxed);
}
}
impl<T> Default for Link<T> {
#[inline]
fn default() -> Self {
Self::new()
}
}
pub unsafe trait Linked: Sized {
unsafe fn link(item: *mut Self) -> *const Link<Self>;
}
unsafe impl<T: Linked> Node for T {
#[inline]
unsafe fn get_next(item: *mut Self) -> *mut Self {
unsafe { (*T::link(item)).0.load(Ordering::Relaxed) }
}
#[inline]
unsafe fn set_next(item: *mut Self, p: *mut Self) {
unsafe { (*T::link(item)).0.store(p, Ordering::Relaxed) }
}
#[inline]
unsafe fn atomic_load_next(item: *mut Self, ordering: Ordering) -> *mut Self {
unsafe { (*T::link(item)).0.load(ordering) }
}
#[inline]
unsafe fn atomic_store_next(item: *mut Self, p: *mut Self, ordering: Ordering) {
unsafe { (*T::link(item)).0.store(p, ordering) }
}
}
pub struct Batch<T: Node> {
pub front: *mut T,
pub last: *mut T,
pub count: usize,
}
impl<T: Node> Clone for Batch<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: Node> Copy for Batch<T> {}
impl<T: Node> Default for Batch<T> {
fn default() -> Self {
Self {
front: ptr::null_mut(),
last: ptr::null_mut(),
count: 0,
}
}
}
pub struct BatchIterator<T: Node> {
pub batch: Batch<T>,
}
impl<T: Node> BatchIterator<T> {
pub fn next(&mut self) -> *mut T {
if self.batch.count == 0 {
return ptr::null_mut();
}
let front = self.batch.front;
debug_assert!(!front.is_null()); self.batch.front = unsafe { T::get_next(front) };
self.batch.count -= 1;
front
}
}
impl<T: Node> Batch<T> {
pub fn iterator(self) -> BatchIterator<T> {
BatchIterator { batch: self }
}
}
#[cfg_attr(
any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "powerpc64",
),
repr(align(64))
)]
#[cfg_attr(
any(
target_arch = "arm",
target_arch = "mips",
target_arch = "mips64",
target_arch = "riscv64",
),
repr(align(16))
)]
#[cfg_attr(target_arch = "s390x", repr(align(128)))]
#[cfg_attr(
not(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "powerpc64",
target_arch = "arm",
target_arch = "mips",
target_arch = "mips64",
target_arch = "riscv64",
target_arch = "s390x",
)),
repr(align(32))
)]
pub struct QueuePadded<T>(pub T);
pub struct UnboundedQueue<T: Node> {
pub back: QueuePadded<AtomicPtr<T>>,
pub front: QueuePadded<AtomicPtr<T>>,
}
impl<T: Node> Default for UnboundedQueue<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Node> UnboundedQueue<T> {
#[inline]
pub const fn new() -> Self {
Self {
back: QueuePadded(AtomicPtr::new(ptr::null_mut())),
front: QueuePadded(AtomicPtr::new(ptr::null_mut())),
}
}
pub fn push(&self, item: NonNull<T>) {
self.push_batch(item, item);
}
pub fn push_batch(&self, first: NonNull<T>, last: NonNull<T>) {
let (first, last) = (first.as_ptr(), last.as_ptr());
unsafe { T::set_next(last, ptr::null_mut()) };
if cfg!(debug_assertions) {
let mut item = first;
loop {
let next_item = unsafe { T::get_next(item) };
if next_item.is_null() {
break;
}
item = next_item;
}
debug_assert!(item == last, "`last` should be reachable from `first`");
}
let old_back = self.back.0.swap(last, Ordering::AcqRel);
if !old_back.is_null() {
unsafe { T::atomic_store_next(old_back, first, Ordering::Release) };
} else {
self.front.0.store(first, Ordering::Release);
}
}
pub fn pop(&self) -> *mut T {
let mut first = self.front.0.load(Ordering::Acquire);
if first.is_null() {
return ptr::null_mut();
}
let next_item = loop {
let next_ptr = unsafe { T::atomic_load_next(first, Ordering::Acquire) };
match self.front.0.compare_exchange_weak(
first,
next_ptr,
Ordering::Release,
Ordering::Acquire,
) {
Ok(_) => break next_ptr,
Err(maybe_first) => {
if maybe_first.is_null() {
return ptr::null_mut();
}
first = maybe_first;
}
}
};
if !next_item.is_null() {
return first;
}
match self.back.0.compare_exchange(
first,
ptr::null_mut(),
Ordering::Relaxed,
Ordering::Relaxed,
) {
Err(back) => {
debug_assert!(
!back.is_null(),
"`back` should not be null while popping an item"
);
}
Ok(_) => return first,
}
let new_first = loop {
let n = unsafe { T::atomic_load_next(first, Ordering::Acquire) };
if !n.is_null() {
break n;
}
hint::spin_loop();
};
self.front.0.store(new_first, Ordering::Release);
first
}
pub fn pop_batch(&self) -> Batch<T> {
let mut batch = Batch::<T>::default();
let first = self.front.0.swap(ptr::null_mut(), Ordering::Acquire);
if first.is_null() {
return batch;
}
batch.count += 1;
let last = self.back.0.swap(ptr::null_mut(), Ordering::Relaxed);
debug_assert!(!last.is_null()); let mut next_item = first;
while next_item != last {
next_item = loop {
let n = unsafe { T::atomic_load_next(next_item, Ordering::Acquire) };
if !n.is_null() {
break n;
}
hint::spin_loop();
};
batch.count += 1;
}
batch.front = first;
batch.last = last;
batch
}
pub fn is_empty(&self) -> bool {
self.back.0.load(Ordering::Acquire).is_null()
}
}