use crate::{slot::Vec, Slot, SHARD_COUNT};
use alloc::alloc::{alloc, dealloc, handle_alloc_error, realloc, Layout};
use core::{
cell::{Cell, UnsafeCell},
fmt,
marker::PhantomData,
mem::{self, ManuallyDrop},
panic::RefUnwindSafe,
ptr, slice,
sync::atomic::{
self, AtomicPtr, AtomicUsize,
Ordering::{Acquire, Relaxed, Release, SeqCst},
},
};
use std::{num::NonZeroUsize, thread};
use thread_local::ThreadLocal;
#[allow(clippy::useless_transmute)]
const INACTIVE: *mut Node = unsafe { mem::transmute(usize::MAX) };
const MIN_RETIRED_LEN: usize = 64;
pub struct CollectorHandle {
ptr: *mut Collector,
}
unsafe impl Send for CollectorHandle {}
unsafe impl Sync for CollectorHandle {}
impl Default for CollectorHandle {
fn default() -> Self {
Self::new()
}
}
impl CollectorHandle {
#[must_use]
pub fn new() -> Self {
if SHARD_COUNT.load(Relaxed) == 0 {
let num_cpus = thread::available_parallelism()
.map(NonZeroUsize::get)
.unwrap_or(1);
SHARD_COUNT.store(num_cpus.next_power_of_two(), Relaxed);
}
let ptr = Box::into_raw(Box::new(Collector {
retirement_lists: ThreadLocal::new(),
handle_count: AtomicUsize::new(1),
}));
unsafe { CollectorHandle { ptr } }
}
#[inline]
#[must_use]
pub unsafe fn pin(&self) -> Guard<'_> {
let mut is_fresh_entry = false;
let retirement_list = self.collector().retirement_lists.get_or(|| {
is_fresh_entry = true;
crate::set_shard_index();
RetirementList {
head: AtomicPtr::new(INACTIVE),
collector: ManuallyDrop::new(unsafe { ptr::read(self) }),
guard_count: Cell::new(0),
batch: UnsafeCell::new(LocalBatch::new()),
}
});
if is_fresh_entry {
atomic::fence(SeqCst);
}
retirement_list.pin()
}
#[inline]
fn collector(&self) -> &Collector {
unsafe { &*self.ptr }
}
}
impl Clone for CollectorHandle {
#[inline]
fn clone(&self) -> Self {
#[allow(clippy::cast_sign_loss)]
if self.collector().handle_count.fetch_add(1, Relaxed) > isize::MAX as usize {
std::process::abort();
}
unsafe { CollectorHandle { ptr: self.ptr } }
}
}
impl fmt::Debug for CollectorHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CollectorHandle").finish_non_exhaustive()
}
}
impl PartialEq for CollectorHandle {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.ptr == other.ptr
}
}
impl Eq for CollectorHandle {}
impl Drop for CollectorHandle {
#[inline]
fn drop(&mut self) {
if self.collector().handle_count.fetch_sub(1, Release) == 1 {
atomic::fence(Acquire);
let _ = unsafe { Box::from_raw(self.ptr) };
}
}
}
struct Collector {
retirement_lists: ThreadLocal<RetirementList>,
handle_count: AtomicUsize,
}
#[repr(align(128))]
pub(crate) struct RetirementList {
head: AtomicPtr<Node>,
collector: ManuallyDrop<CollectorHandle>,
guard_count: Cell<usize>,
batch: UnsafeCell<LocalBatch>,
}
unsafe impl Sync for RetirementList {}
impl RefUnwindSafe for RetirementList {}
impl RetirementList {
#[inline]
pub(crate) fn pin(&self) -> Guard<'_> {
let guard_count = self.guard_count.get();
self.guard_count.set(guard_count.checked_add(1).unwrap());
if guard_count == 0 {
unsafe { self.enter() };
}
unsafe { Guard::new(self) }
}
#[inline]
unsafe fn enter(&self) {
self.head.store(ptr::null_mut(), Relaxed);
atomic::fence(SeqCst);
}
#[inline]
unsafe fn defer_reclaim(
&self,
index: u32,
slots: *const u8,
reclaim: unsafe fn(u32, *const u8),
) {
let batch = unsafe { &mut *self.batch.get() };
unsafe { batch.push(index, slots, reclaim) };
if batch.len() == MIN_RETIRED_LEN {
unsafe { self.retire() };
}
}
#[inline(never)]
unsafe fn retire(&self) {
let batch = unsafe { &mut *self.batch.get() };
if batch.is_empty() {
return;
}
let mut batch = mem::take(batch);
let retired_len = batch.len();
let mut len = 0;
unsafe { batch.set_retired_len(retired_len) };
atomic::fence(SeqCst);
for retirement_list in &self.collector.collector().retirement_lists {
if retirement_list.head.load(Relaxed) == INACTIVE {
continue;
}
if len >= retired_len {
unsafe { batch.push(0, ptr::null(), |_, _| {}) };
}
let node = unsafe { batch.as_mut_slice().get_unchecked_mut(len) };
node.link.retirement_list = &retirement_list.head;
len += 1;
}
let nodes = batch.as_mut_ptr();
let batch = batch.into_raw();
atomic::fence(Acquire);
#[allow(clippy::mut_range_bound)]
'outer: for node_index in 0..len {
let node = unsafe { nodes.add(node_index) };
unsafe { (*node).batch = batch };
let list = unsafe { &*(*node).link.retirement_list };
let mut head = list.load(Relaxed);
loop {
if head == INACTIVE {
atomic::fence(Acquire);
len -= 1;
continue 'outer;
}
unsafe { (*node).link.next = head };
match list.compare_exchange_weak(head, node, Release, Relaxed) {
Ok(_) => break,
Err(new_head) => head = new_head,
}
}
}
if unsafe { (*batch).ref_count.fetch_add(len, Release) }.wrapping_add(len) == 0 {
unsafe { self.reclaim(batch) };
}
}
#[inline]
unsafe fn leave(&self) {
let head = self.head.swap(INACTIVE, Release);
if !head.is_null() {
unsafe { self.traverse(head) };
}
}
#[cold]
unsafe fn traverse(&self, mut head: *mut Node) {
atomic::fence(Acquire);
while !head.is_null() {
let batch = unsafe { (*head).batch };
let next = unsafe { (*head).link.next };
let ref_count = unsafe { (*batch).ref_count.fetch_sub(1, Release) }.wrapping_sub(1);
if ref_count == 0 {
unsafe { self.reclaim(batch) };
}
head = next;
}
}
unsafe fn reclaim(&self, batch: *mut Batch) {
atomic::fence(Acquire);
let mut batch = unsafe { LocalBatch::from_raw(batch) };
for node in batch.retired_as_mut_slice() {
unsafe { (node.reclaim)(node.index, node.slots) };
}
}
}
impl Drop for Collector {
fn drop(&mut self) {
atomic::fence(Acquire);
for retirement_list in &mut self.retirement_lists {
let batch = retirement_list.batch.get_mut();
if batch.is_empty() {
continue;
}
for node in batch.retired_as_mut_slice() {
unsafe { (node.reclaim)(node.index, node.slots) };
}
}
}
}
#[repr(C)]
struct Batch {
ref_count: AtomicUsize,
capacity: usize,
len: usize,
retired_len: usize,
nodes: [Node; 0],
}
struct Node {
link: NodeLink,
batch: *mut Batch,
index: u32,
slots: *const u8,
reclaim: unsafe fn(u32, *const u8),
}
union NodeLink {
retirement_list: *const AtomicPtr<Node>,
next: *mut Node,
}
struct LocalBatch {
ptr: *mut Batch,
}
unsafe impl Send for LocalBatch {}
unsafe impl Sync for LocalBatch {}
impl Default for LocalBatch {
fn default() -> Self {
LocalBatch::new()
}
}
impl LocalBatch {
const MIN_CAP: usize = 4;
fn new() -> Self {
let layout = layout_for_capacity(Self::MIN_CAP);
let ptr = unsafe { alloc(layout) }.cast::<Batch>();
if ptr.is_null() {
handle_alloc_error(layout);
}
unsafe {
*ptr::addr_of_mut!((*ptr).ref_count) = AtomicUsize::new(0);
*ptr::addr_of_mut!((*ptr).capacity) = Self::MIN_CAP;
*ptr::addr_of_mut!((*ptr).len) = 0;
*ptr::addr_of_mut!((*ptr).retired_len) = 0;
}
LocalBatch { ptr }
}
#[inline]
unsafe fn from_raw(ptr: *mut Batch) -> Self {
LocalBatch { ptr }
}
#[inline]
fn into_raw(self) -> *mut Batch {
ManuallyDrop::new(self).ptr
}
#[inline]
fn capacity(&self) -> usize {
unsafe { (*self.ptr).capacity }
}
#[inline]
fn len(&self) -> usize {
unsafe { (*self.ptr).len }
}
#[inline]
fn retired_len(&self) -> usize {
unsafe { (*self.ptr).retired_len }
}
#[inline]
fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
fn as_mut_ptr(&mut self) -> *mut Node {
unsafe { ptr::addr_of_mut!((*self.ptr).nodes) }.cast()
}
fn as_mut_slice(&mut self) -> &mut [Node] {
unsafe { slice::from_raw_parts_mut(self.as_mut_ptr(), self.len()) }
}
#[inline]
fn retired_as_mut_slice(&mut self) -> &mut [Node] {
unsafe { slice::from_raw_parts_mut(self.as_mut_ptr(), self.retired_len()) }
}
#[inline]
unsafe fn push(&mut self, index: u32, slots: *const u8, reclaim: unsafe fn(u32, *const u8)) {
let len = self.len();
if len == self.capacity() {
self.grow_one();
}
let node = Node {
link: NodeLink {
retirement_list: ptr::null(),
},
batch: ptr::null_mut(),
index,
slots,
reclaim,
};
unsafe { self.as_mut_ptr().add(len).write(node) };
unsafe { self.set_len(len + 1) };
}
#[inline(never)]
fn grow_one(&mut self) {
let capacity = self.capacity();
let new_capacity = capacity * 2;
let layout = layout_for_capacity(capacity);
let new_layout = layout_for_capacity(new_capacity);
let new_ptr = unsafe { realloc(self.ptr.cast(), layout, new_layout.size()) };
if new_ptr.is_null() {
handle_alloc_error(new_layout);
}
self.ptr = new_ptr.cast();
unsafe { (*self.ptr).capacity = new_capacity };
}
#[inline]
unsafe fn set_len(&mut self, len: usize) {
unsafe { (*self.ptr).len = len };
}
#[inline]
unsafe fn set_retired_len(&mut self, len: usize) {
unsafe { (*self.ptr).retired_len = len };
}
}
impl Drop for LocalBatch {
fn drop(&mut self) {
let layout = layout_for_capacity(self.capacity());
unsafe { dealloc(self.ptr.cast(), layout) };
}
}
fn layout_for_capacity(capacity: usize) -> Layout {
Layout::new::<Batch>()
.extend(Layout::array::<Node>(capacity).unwrap())
.unwrap()
.0
}
pub struct Guard<'a> {
retirement_list: &'a RetirementList,
marker: PhantomData<*const ()>,
}
impl<'a> Guard<'a> {
#[inline]
unsafe fn new(retirement_list: &'a RetirementList) -> Self {
Guard {
retirement_list,
marker: PhantomData,
}
}
#[inline]
#[must_use]
pub fn collector(&self) -> &CollectorHandle {
&self.retirement_list.collector
}
#[inline]
pub(crate) unsafe fn defer_reclaim<V>(&self, index: u32, slots: &Vec<V>) {
let slots = slots.as_ptr().cast();
let reclaim = transmute_reclaim_fp(crate::reclaim::<V>);
unsafe { self.retirement_list.defer_reclaim(index, slots, reclaim) };
}
#[inline]
pub(crate) unsafe fn defer_reclaim_invalidated<V>(&self, index: u32, slots: &Vec<V>) {
let slots = slots.as_ptr().cast();
let reclaim = transmute_reclaim_fp(crate::reclaim_invalidated::<V>);
unsafe { self.retirement_list.defer_reclaim(index, slots, reclaim) }
}
#[inline]
pub fn flush(&self) {
unsafe { self.retirement_list.retire() };
}
}
fn transmute_reclaim_fp<V>(fp: unsafe fn(u32, *const Slot<V>)) -> unsafe fn(u32, *const u8) {
unsafe { mem::transmute::<unsafe fn(u32, *const Slot<V>), unsafe fn(u32, *const u8)>(fp) }
}
impl fmt::Debug for Guard<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Guard").finish_non_exhaustive()
}
}
impl Clone for Guard<'_> {
#[inline]
fn clone(&self) -> Self {
let guard_count = self.retirement_list.guard_count.get();
self.retirement_list
.guard_count
.set(guard_count.checked_add(1).unwrap());
unsafe { Guard::new(self.retirement_list) }
}
}
impl Drop for Guard<'_> {
#[inline]
fn drop(&mut self) {
let guard_count = self.retirement_list.guard_count.get();
self.retirement_list.guard_count.set(guard_count - 1);
if guard_count == 1 {
unsafe { self.retirement_list.leave() };
}
}
}