use core::panic;
use std::alloc::{Layout, alloc, dealloc};
use std::any::type_name;
use std::mem::MaybeUninit;
use std::pin::Pin;
use std::ptr::NonNull;
use std::{mem, thread};
use crate::DropPolicy;
#[derive(Debug)]
pub(crate) struct PinnedSlab<T, const CAPACITY: usize> {
first_entry_ptr: NonNull<Entry<T>>,
next_free_index: usize,
count: usize,
drop_policy: DropPolicy,
}
#[derive(Debug)]
enum Entry<T> {
Occupied { value: MaybeUninit<T> },
Vacant { next_free_index: usize },
}
impl<T, const CAPACITY: usize> PinnedSlab<T, CAPACITY> {
#[must_use]
pub(crate) fn new(drop_policy: DropPolicy) -> Self {
assert!(CAPACITY > 0, "PinnedSlab must have non-zero capacity",);
assert!(
size_of::<T>() > 0,
"PinnedSlab must have non-zero item size"
);
assert!(
CAPACITY < usize::MAX,
"PinnedSlab capacity must be less than usize::MAX"
);
let ptr = NonNull::new(unsafe { alloc(Self::layout()).cast::<Entry<T>>() }).expect(
"we do not intend to handle allocation failure as a real possibility - OOM is panic",
);
ensure_virtual_pages_mapped_to_physical_pages::<Entry<T>, CAPACITY>(ptr);
for index in 0..CAPACITY {
let entry = unsafe { ptr.add(index) };
unsafe {
entry.as_ptr().write(Entry::Vacant {
next_free_index: index
.checked_add(1)
.expect("guarded by capacity < usize::MAX above"),
});
}
}
Self {
first_entry_ptr: ptr,
next_free_index: 0,
count: 0,
drop_policy,
}
}
#[must_use]
fn layout() -> Layout {
Layout::array::<Entry<T>>(CAPACITY).expect("simple flat array layout must be calculable")
}
#[must_use]
#[cfg_attr(test, mutants::skip)] pub(crate) fn len(&self) -> usize {
self.count
}
#[must_use]
pub(crate) fn is_empty(&self) -> bool {
self.count == 0
}
#[must_use]
pub(crate) fn is_full(&self) -> bool {
self.next_free_index >= CAPACITY
}
fn entry(&self, index: usize) -> &Entry<T> {
let entry_ptr = self.entry_ptr(index);
unsafe { entry_ptr.as_ref() }
}
#[expect(clippy::needless_pass_by_ref_mut, reason = "false positive")]
fn entry_mut(&mut self, index: usize) -> &mut Entry<T> {
let mut entry_ptr = self.entry_ptr(index);
unsafe { entry_ptr.as_mut() }
}
fn entry_ptr(&self, index: usize) -> NonNull<Entry<T>> {
debug_assert!(
index < CAPACITY,
"entry {index} index out of bounds in slab of {}",
type_name::<T>()
);
unsafe { self.first_entry_ptr.add(index) }
}
#[must_use]
pub(crate) fn get(&self, index: usize) -> Pin<&T> {
match self.entry(index) {
Entry::Occupied { value } => {
let init_ref = unsafe { value.assume_init_ref() };
unsafe { Pin::new_unchecked(init_ref) }
}
Entry::Vacant { .. } => panic!(
"get({index}) entry was vacant in slab of {}",
type_name::<T>()
),
}
}
#[must_use]
pub(crate) fn get_mut(&mut self, index: usize) -> Pin<&mut T> {
match self.entry_mut(index) {
Entry::Occupied { value } => {
let init_mut = unsafe { value.assume_init_mut() };
unsafe { Pin::new_unchecked(init_mut) }
}
Entry::Vacant { .. } => panic!(
"get_mut({index}) entry was vacant in slab of {}",
type_name::<T>()
),
}
}
#[must_use]
pub(crate) fn begin_insert<'s, 'i>(&'s mut self) -> PinnedSlabInserter<'i, T, CAPACITY>
where
's: 'i,
{
#[cfg(debug_assertions)]
self.integrity_check();
assert!(
!self.is_full(),
"cannot insert into a full slab of {}",
type_name::<T>()
);
let next_free_index = self.next_free_index;
PinnedSlabInserter {
slab: self,
index: next_free_index,
}
}
#[must_use]
#[allow(
dead_code,
reason = "not used for now but likely will be if we expose parent publicly"
)]
pub(crate) fn insert(&mut self, value: T) -> usize {
let inserter = self.begin_insert();
let index = inserter.index();
inserter.insert(value);
index
}
pub(crate) fn remove(&mut self, index: usize) {
let next_free_index = self.next_free_index;
{
let entry = self.entry_mut(index);
match entry {
Entry::Occupied { value } => {
unsafe {
value.assume_init_drop();
}
}
Entry::Vacant { .. } => panic!(
"remove({index}) entry was vacant in slab of {}",
type_name::<T>()
),
}
*entry = Entry::Vacant { next_free_index };
}
self.next_free_index = index;
self.count = self.count.wrapping_sub(1);
}
pub(crate) fn iter(&self) -> PinnedSlabIterator<'_, T, CAPACITY> {
PinnedSlabIterator::new(self)
}
#[cfg_attr(test, mutants::skip)] #[cfg(debug_assertions)]
pub(crate) fn integrity_check(&self) {
let mut observed_is_vacant: [Option<bool>; CAPACITY] = [None; CAPACITY];
let mut observed_next_free_index: [Option<usize>; CAPACITY] = [None; CAPACITY];
let mut observed_occupied_count: usize = 0;
for index in 0..CAPACITY {
match self.entry(index) {
Entry::Occupied { .. } => {
*observed_is_vacant
.get_mut(index)
.expect("guarded by loop range") = Some(false);
observed_occupied_count = observed_occupied_count
.checked_add(1)
.expect("guarded by capacity < usize::MAX in slab ctor");
}
Entry::Vacant { next_free_index } => {
*observed_is_vacant
.get_mut(index)
.expect("guarded by loop range") = Some(true);
*observed_next_free_index
.get_mut(index)
.expect("guarded by loop range") = Some(*next_free_index);
}
}
}
assert!(
matches!(
observed_is_vacant.get(self.next_free_index),
None | Some(Some(true))
),
"self.next_free_index points to an occupied slot {} in slab of {}",
self.next_free_index,
type_name::<T>()
);
assert!(
self.count == observed_occupied_count,
"self.count {} does not match the observed occupied count {} in slab of {}",
self.count,
observed_occupied_count,
type_name::<T>()
);
for index in 0..CAPACITY {
if !observed_is_vacant
.get(index)
.expect("guarded by loop range")
.unwrap()
{
continue;
}
let next_free_index = observed_next_free_index
.get(index)
.expect("guarded by loop range")
.unwrap();
if next_free_index == CAPACITY {
continue;
}
assert!(
next_free_index <= CAPACITY,
"entry {} is vacant but has an out-of-bounds next_free_index beyond COUNT {} in slab of {}",
index,
next_free_index,
type_name::<T>()
);
assert!(
observed_is_vacant
.get(next_free_index)
.expect("guarded by previous assertion")
.unwrap(),
"entry {} is vacant but its next_free_index {} points to an occupied slot in slab of {}",
index,
next_free_index,
type_name::<T>()
);
}
}
}
impl<T, const CAPACITY: usize> Drop for PinnedSlab<T, CAPACITY> {
fn drop(&mut self) {
let was_empty = self.is_empty();
for index in 0..CAPACITY {
let entry = self.entry_mut(index);
if let Entry::Occupied { value } = entry {
unsafe {
value.assume_init_drop();
}
}
*entry = Entry::Vacant {
next_free_index: usize::MAX,
};
}
unsafe {
dealloc(self.first_entry_ptr.as_ptr().cast(), Self::layout());
}
if self.drop_policy == DropPolicy::MustNotDropItems && !thread::panicking() {
assert!(
was_empty,
"dropped a non-empty slab of {} with a policy that says it must be empty when dropped",
type_name::<T>()
);
}
}
}
unsafe impl<T: Send, const CAPACITY: usize> Send for PinnedSlab<T, CAPACITY> {}
#[derive(Debug)]
pub(crate) struct PinnedSlabInserter<'s, T, const CAPACITY: usize> {
slab: &'s mut PinnedSlab<T, CAPACITY>,
index: usize,
}
impl<'s, T, const CAPACITY: usize> PinnedSlabInserter<'s, T, CAPACITY> {
#[must_use]
pub(crate) fn index(&self) -> usize {
self.index
}
pub(crate) fn insert<'v>(self, value: T) -> Pin<&'v T>
where
's: 'v,
{
self.insert_mut(value).into_ref()
}
pub(crate) fn insert_mut<'v>(self, value: T) -> Pin<&'v mut T>
where
's: 'v,
{
unsafe {
self.insert_with_mut(|uninit| {
uninit.write(value);
})
}
}
#[allow(dead_code, reason = "not used yet but provides the non-mut variant")]
pub(crate) unsafe fn insert_with<'v>(self, f: impl FnOnce(&mut MaybeUninit<T>)) -> Pin<&'v T>
where
's: 'v,
{
unsafe { self.insert_with_mut(f).into_ref() }
}
pub(crate) unsafe fn insert_with_mut<'v>(
self,
f: impl FnOnce(&mut MaybeUninit<T>),
) -> Pin<&'v mut T>
where
's: 'v,
{
let mut entry_ptr = self.slab.entry_ptr(self.index);
let entry = unsafe { entry_ptr.as_mut() };
let previous_entry = mem::replace(
entry,
Entry::Occupied {
value: MaybeUninit::uninit(),
},
);
self.slab.next_free_index = match previous_entry {
Entry::Vacant { next_free_index } => next_free_index,
Entry::Occupied { .. } => panic!(
"entry {} was not vacant when we inserted into it in slab of {}",
self.index,
type_name::<T>()
),
};
let value = match entry {
Entry::Occupied { value } => {
f(value);
value
}
Entry::Vacant { .. } => panic!(
"entry {} was not occupied after we inserted into it in slab of {}",
self.index,
type_name::<T>()
),
};
let init_mut = unsafe { value.assume_init_mut() };
let pinned_ref: Pin<&'v mut T> = unsafe { Pin::new_unchecked(init_mut) };
self.slab.count = self.slab.count.wrapping_add(1);
pinned_ref
}
}
#[derive(Debug)]
#[must_use]
pub(crate) struct PinnedSlabIterator<'s, T, const CAPACITY: usize> {
slab: &'s PinnedSlab<T, CAPACITY>,
current_index: usize,
}
impl<'s, T, const CAPACITY: usize> PinnedSlabIterator<'s, T, CAPACITY> {
fn new(slab: &'s PinnedSlab<T, CAPACITY>) -> Self {
Self {
slab,
current_index: 0,
}
}
}
impl<'s, T, const CAPACITY: usize> Iterator for PinnedSlabIterator<'s, T, CAPACITY> {
type Item = Pin<&'s T>;
fn next(&mut self) -> Option<Self::Item> {
while self.current_index < CAPACITY {
let entry_index = self.current_index;
self.current_index = self
.current_index
.checked_add(1)
.expect("guarded by capacity < usize::MAX in slab ctor");
let entry = self.slab.entry(entry_index);
if let Entry::Occupied { .. } = entry {
return Some(self.slab.get(entry_index));
}
}
None
}
}
#[cfg_attr(test, mutants::skip)] fn ensure_virtual_pages_mapped_to_physical_pages<T, const COUNT: usize>(ptr: NonNull<T>) {
if size_of::<T>() < 4096 {
return;
}
unsafe {
ptr.write_bytes(0x3F, COUNT);
}
}
#[cfg(test)]
mod tests {
#![allow(
clippy::indexing_slicing,
clippy::cast_possible_truncation,
reason = "we do not need to worry about these things when writing test code"
)]
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::thread;
use super::*;
#[test]
fn smoke_test() {
let mut slab = PinnedSlab::<u32, 3>::new(DropPolicy::MayDropItems);
let index_a = slab.insert(42);
let index_b = slab.insert(43);
let index_c = slab.insert(44);
assert_eq!(*slab.get(index_a), 42);
assert_eq!(*slab.get(index_b), 43);
assert_eq!(*slab.get(index_c), 44);
assert_eq!(slab.len(), 3);
slab.remove(index_b);
assert_eq!(slab.len(), 2);
let index_d = slab.insert(45);
assert_eq!(*slab.get(index_a), 42);
assert_eq!(*slab.get(index_c), 44);
assert_eq!(*slab.get(index_d), 45);
assert!(slab.is_full());
}
#[test]
#[should_panic]
fn panic_when_full() {
let mut slab = PinnedSlab::<u32, 3>::new(DropPolicy::MayDropItems);
_ = slab.insert(42);
_ = slab.insert(43);
_ = slab.insert(44);
_ = slab.insert(45);
}
#[test]
#[should_panic]
fn panic_when_oob_get() {
let mut slab = PinnedSlab::<u32, 3>::new(DropPolicy::MayDropItems);
_ = slab.insert(42);
_ = slab.get(1234);
}
#[test]
fn begin_insert_returns_correct_index() {
let mut slab = PinnedSlab::<u32, 3>::new(DropPolicy::MayDropItems);
let inserter = slab.begin_insert();
assert_eq!(inserter.index(), 0);
inserter.insert(10);
assert_eq!(*slab.get(0), 10);
let inserter = slab.begin_insert();
assert_eq!(inserter.index(), 1);
inserter.insert(11);
assert_eq!(*slab.get(1), 11);
let inserter = slab.begin_insert();
assert_eq!(inserter.index(), 2);
inserter.insert(12);
assert_eq!(*slab.get(2), 12);
}
#[test]
fn abandoned_inserter_is_noop() {
let mut slab = PinnedSlab::<u32, 3>::new(DropPolicy::MayDropItems);
let inserter = slab.begin_insert();
assert_eq!(inserter.index(), 0);
let inserter = slab.begin_insert();
assert_eq!(inserter.index(), 0);
inserter.insert(20);
assert_eq!(*slab.get(0), 20);
_ = slab.insert(123);
_ = slab.insert(456);
}
#[test]
fn remove_makes_room() {
let mut slab = PinnedSlab::<u32, 3>::new(DropPolicy::MayDropItems);
let a = slab.insert(42);
let b = slab.insert(43);
let c = slab.insert(44);
slab.remove(b);
let d = slab.insert(45);
assert_eq!(*slab.get(a), 42);
assert_eq!(*slab.get(c), 44);
assert_eq!(*slab.get(d), 45);
}
#[test]
#[should_panic]
fn remove_vacant_panics() {
let mut slab = PinnedSlab::<u32, 3>::new(DropPolicy::MayDropItems);
slab.remove(1);
}
#[test]
#[should_panic]
fn get_vacant_panics() {
let slab = PinnedSlab::<u32, 3>::new(DropPolicy::MayDropItems);
_ = slab.get(1);
}
#[test]
#[should_panic]
fn get_mut_vacant_panics() {
let mut slab = PinnedSlab::<u32, 3>::new(DropPolicy::MayDropItems);
_ = slab.get_mut(1);
}
#[test]
fn in_refcell_works_fine() {
let slab = RefCell::new(PinnedSlab::<u32, 3>::new(DropPolicy::MayDropItems));
{
let mut slab = slab.borrow_mut();
let a = slab.insert(42);
let b = slab.insert(43);
let c = slab.insert(44);
assert_eq!(*slab.get(a), 42);
assert_eq!(*slab.get(b), 43);
assert_eq!(*slab.get(c), 44);
slab.remove(b);
let d = slab.insert(45);
assert_eq!(*slab.get(a), 42);
assert_eq!(*slab.get(c), 44);
assert_eq!(*slab.get(d), 45);
}
{
let slab = slab.borrow();
assert_eq!(*slab.get(0), 42);
assert!(slab.is_full());
}
}
#[test]
fn calls_drop_on_remove() {
struct Droppable {
dropped: Rc<Cell<bool>>,
}
impl Drop for Droppable {
fn drop(&mut self) {
self.dropped.set(true);
}
}
let dropped = Rc::new(Cell::new(false));
let mut slab = PinnedSlab::<Droppable, 3>::new(DropPolicy::MayDropItems);
let a = slab.insert(Droppable {
dropped: Rc::clone(&dropped),
});
slab.remove(a);
assert!(dropped.get());
}
#[test]
fn multithreaded_via_mutex() {
let slab = Arc::new(Mutex::new(PinnedSlab::<u32, 3>::new(
DropPolicy::MayDropItems,
)));
let a;
let b;
let c;
{
let mut slab = slab.lock().unwrap();
a = slab.insert(42);
b = slab.insert(43);
c = slab.insert(44);
assert_eq!(*slab.get(a), 42);
assert_eq!(*slab.get(b), 43);
assert_eq!(*slab.get(c), 44);
}
let slab_clone = Arc::clone(&slab);
thread::spawn(move || {
let mut slab = slab_clone.lock().unwrap();
slab.remove(b);
let d = slab.insert(45);
assert_eq!(*slab.get(a), 42);
assert_eq!(*slab.get(c), 44);
assert_eq!(*slab.get(d), 45);
});
let slab = slab.lock().unwrap();
assert!(slab.is_full());
}
#[test]
#[should_panic]
fn drop_item_with_forbidden_to_drop_policy_panics() {
let mut slab = PinnedSlab::<u32, 3>::new(DropPolicy::MustNotDropItems);
_ = slab.insert(123);
}
#[test]
fn drop_itemless_with_forbidden_to_drop_policy_ok() {
drop(PinnedSlab::<u32, 3>::new(DropPolicy::MustNotDropItems));
}
#[test]
#[should_panic]
fn zst_is_panic() {
drop(PinnedSlab::<(), 3>::new(DropPolicy::MayDropItems));
}
#[test]
#[should_panic]
fn zero_capacity_is_panic() {
drop(PinnedSlab::<usize, 0>::new(DropPolicy::MayDropItems));
}
#[test]
fn insert_with_mut_works() {
let mut slab = PinnedSlab::<u32, 3>::new(DropPolicy::MayDropItems);
let inserter = slab.begin_insert();
let index = inserter.index();
let value_ref = unsafe {
inserter.insert_with_mut(|uninit| {
uninit.write(42);
})
};
assert_eq!(*value_ref, 42);
assert_eq!(*slab.get(index), 42);
assert_eq!(slab.len(), 1);
}
#[test]
fn insert_with_allows_complex_initialization() {
struct ComplexType {
field1: u32,
field2: String,
}
let mut slab = PinnedSlab::<ComplexType, 3>::new(DropPolicy::MayDropItems);
let inserter = slab.begin_insert();
let index = inserter.index();
let value_ref = unsafe {
inserter.insert_with_mut(|uninit| {
uninit.write(ComplexType {
field1: 123,
field2: String::from("hello"),
});
})
};
assert_eq!(value_ref.field1, 123);
assert_eq!(value_ref.field2, "hello");
let retrieved = slab.get(index);
assert_eq!(retrieved.field1, 123);
assert_eq!(retrieved.field2, "hello");
}
#[test]
fn insert_with_returns_shared_ref() {
let mut slab = PinnedSlab::<u32, 3>::new(DropPolicy::MayDropItems);
let inserter = slab.begin_insert();
let index = inserter.index();
let value_ref = unsafe {
inserter.insert_with(|uninit| {
uninit.write(789);
})
};
assert_eq!(*value_ref, 789);
assert_eq!(*slab.get(index), 789);
assert_eq!(slab.len(), 1);
}
#[test]
fn insert_with_partial_initialization() {
use std::mem::MaybeUninit;
#[allow(
dead_code,
reason = "memory field is used for demonstration but not accessed in test"
)]
struct HalfFull {
value: usize,
memory: [MaybeUninit<u8>; 16],
}
fn initialize_half_full(uninit: &mut MaybeUninit<HalfFull>) {
let ptr = uninit.as_mut_ptr();
let value_ptr = unsafe { &raw mut (*ptr).value };
unsafe {
value_ptr.write(42);
}
}
let mut slab = PinnedSlab::<HalfFull, 3>::new(DropPolicy::MayDropItems);
let inserter = slab.begin_insert();
let index = inserter.index();
let value_ref = unsafe {
inserter.insert_with(|uninit| {
initialize_half_full(uninit);
})
};
assert_eq!(value_ref.value, 42);
let retrieved = slab.get(index);
assert_eq!(retrieved.value, 42);
assert_eq!(slab.len(), 1);
}
#[test]
fn iter_empty_slab() {
let slab = PinnedSlab::<u32, 3>::new(DropPolicy::MayDropItems);
let mut iter = slab.iter();
assert!(iter.next().is_none());
}
#[test]
fn iter_single_item() {
let mut slab = PinnedSlab::<String, 3>::new(DropPolicy::MayDropItems);
let _index = slab.insert("hello".to_string());
let items: Vec<_> = slab.iter().collect();
assert_eq!(items.len(), 1);
assert_eq!(&*items[0], "hello");
}
#[test]
fn iter_multiple_items() {
let mut slab = PinnedSlab::<i32, 5>::new(DropPolicy::MayDropItems);
let _idx1 = slab.insert(10);
let _idx2 = slab.insert(20);
let _idx3 = slab.insert(30);
let items: Vec<_> = slab.iter().map(|item| *item).collect();
assert_eq!(items.len(), 3);
assert_eq!(items[0], 10);
assert_eq!(items[1], 20);
assert_eq!(items[2], 30);
}
#[test]
fn iter_with_gaps() {
let mut slab = PinnedSlab::<u64, 5>::new(DropPolicy::MayDropItems);
let _idx1 = slab.insert(100);
let idx2 = slab.insert(200);
let idx3 = slab.insert(300);
let _idx4 = slab.insert(400);
slab.remove(idx2);
slab.remove(idx3);
let items: Vec<_> = slab.iter().map(|item| *item).collect();
assert_eq!(items.len(), 2);
assert_eq!(items[0], 100);
assert_eq!(items[1], 400);
}
#[test]
fn iter_full_slab() {
let mut slab = PinnedSlab::<usize, 3>::new(DropPolicy::MayDropItems);
let _idx1 = slab.insert(1);
let _idx2 = slab.insert(2);
let _idx3 = slab.insert(3);
assert!(slab.is_full());
let items: Vec<_> = slab.iter().map(|item| *item).collect();
assert_eq!(items.len(), 3);
assert_eq!(items[0], 1);
assert_eq!(items[1], 2);
assert_eq!(items[2], 3);
}
#[test]
fn iter_multiple_iterators() {
let mut slab = PinnedSlab::<u8, 3>::new(DropPolicy::MayDropItems);
let _idx1 = slab.insert(1);
let _idx2 = slab.insert(2);
let iter1 = slab.iter();
let iter2 = slab.iter();
let items1: Vec<_> = iter1.map(|item| *item).collect();
let items2: Vec<_> = iter2.map(|item| *item).collect();
assert_eq!(items1, items2);
assert_eq!(items1, vec![1, 2]);
}
#[test]
fn large_item() {
let mut slab = PinnedSlab::<[u8; 10240], 123>::new(DropPolicy::MayDropItems);
let index = slab.insert([88u8; 10240]);
slab.remove(index);
}
}