use super::stack::CacheAligned;
use crate::platform::*;
#[cfg(target_pointer_width = "64")]
mod pack {
pub const INDEX_MASK: usize = 0xFFFF_FFFF;
pub const GEN_SHIFT: u32 = 32;
}
#[cfg(not(target_pointer_width = "64"))]
mod pack {
pub const INDEX_MASK: usize = 0xFFFF;
pub const GEN_SHIFT: u32 = 16;
}
use pack::*;
#[inline]
fn pack(index: usize, generation: usize) -> usize {
(index & INDEX_MASK) | (generation << GEN_SHIFT)
}
#[inline]
fn unpack(val: usize) -> (usize, usize) {
(val & INDEX_MASK, val >> GEN_SHIFT)
}
pub struct SlabAllocator<T> {
entries: Box<[SlabEntry<T>]>,
next_free: AtomicUsize,
len: CacheAligned<AtomicUsize>,
}
struct SlabEntry<T> {
value: UnsafeCell<MaybeUninit<T>>,
next: AtomicUsize,
occupied: AtomicBool,
}
unsafe impl<T: Send> Send for SlabEntry<T> {}
unsafe impl<T: Send + Sync> Sync for SlabEntry<T> {}
impl<T> SlabAllocator<T> {
#[must_use]
pub fn new(capacity: usize) -> Self {
assert!(
capacity <= INDEX_MASK,
"Capacity exceeds maximum allowed for SlabAllocator"
);
let mut entries = Vec::with_capacity(capacity);
for i in 0..capacity {
entries.push(SlabEntry {
value: UnsafeCell::new(MaybeUninit::uninit()),
next: AtomicUsize::new(i + 1),
occupied: AtomicBool::new(false),
});
}
Self {
entries: entries.into_boxed_slice(),
next_free: AtomicUsize::new(pack(0, 0)),
len: CacheAligned::new(AtomicUsize::new(0)),
}
}
pub fn insert(&self, value: T) -> Option<usize> {
loop {
let packed_free = self.next_free.load(Ordering::Acquire);
let (free_idx, r#gen) = unpack(packed_free);
if free_idx >= self.entries.len() {
return None; }
let entry = &self.entries[free_idx];
let next_idx = entry.next.load(Ordering::Relaxed);
let new_packed = pack(next_idx, r#gen.wrapping_add(1));
if self
.next_free
.compare_exchange_weak(
packed_free,
new_packed,
Ordering::Release,
Ordering::Relaxed,
)
.is_ok()
{
unsafe {
(*entry.value.get()).write(value);
}
debug_assert!(
!entry.occupied.load(Ordering::Relaxed),
"Slot should be vacant before marking occupied"
);
entry.occupied.store(true, Ordering::Release);
self.len.0.fetch_add(1, Ordering::Relaxed);
return Some(free_idx);
}
}
}
pub fn remove(&self, idx: usize) -> Option<T> {
if idx >= self.entries.len() {
return None;
}
let entry = &self.entries[idx];
if !entry.occupied.swap(false, Ordering::Acquire) {
return None; }
let value = unsafe { (*entry.value.get()).assume_init_read() };
loop {
let packed_free = self.next_free.load(Ordering::Relaxed);
let (free_idx, r#gen) = unpack(packed_free);
entry.next.store(free_idx, Ordering::Relaxed);
let new_packed = pack(idx, r#gen.wrapping_add(1));
if self
.next_free
.compare_exchange_weak(
packed_free,
new_packed,
Ordering::Release,
Ordering::Relaxed,
)
.is_ok()
{
break;
}
}
self.len.0.fetch_sub(1, Ordering::Relaxed);
Some(value)
}
pub unsafe fn get(&self, idx: usize) -> Option<&T> {
unsafe {
if idx >= self.entries.len() {
return None;
}
let entry = &self.entries[idx];
if entry.occupied.load(Ordering::Acquire) {
Some(&*(*entry.value.get()).as_ptr())
} else {
None
}
}
}
pub fn len(&self) -> usize {
self.len.0.load(Ordering::Relaxed)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl<T> Drop for SlabAllocator<T> {
fn drop(&mut self) {
for entry in &mut self.entries {
if *entry.occupied.get_mut() {
unsafe {
entry.value.get_mut().assume_init_drop();
}
}
}
}
}