use core::cell::Cell;
use core::fmt;
use core::mem;
use alloc::boxed::Box;
use alloc::vec::Vec;
use crate::bounded::Slab as BoundedSlab;
use crate::shared::{Slot, SlotCell};
pub struct Claim<'a, T> {
slot_ptr: *mut SlotCell<T>,
slab: &'a Slab<T>,
chunk_idx: usize,
}
impl<T> Claim<'_, T> {
#[inline]
pub fn write(self, value: T) -> Slot<T> {
let slot_ptr = self.slot_ptr;
unsafe {
(*slot_ptr).write_value(value);
}
mem::forget(self);
unsafe { Slot::from_ptr(slot_ptr) }
}
#[inline]
pub(crate) fn into_ptr(self) -> (*mut SlotCell<T>, usize) {
let ptr = self.slot_ptr;
let chunk_idx = self.chunk_idx;
mem::forget(self);
(ptr, chunk_idx)
}
}
impl<T> fmt::Debug for Claim<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Claim")
.field("slot_ptr", &self.slot_ptr)
.field("chunk_idx", &self.chunk_idx)
.finish()
}
}
impl<T> Drop for Claim<'_, T> {
fn drop(&mut self) {
let chunk = self.slab.chunk(self.chunk_idx);
let chunk_slab = &*chunk.inner;
let free_head = chunk_slab.free_head.get();
let was_full = free_head.is_null();
unsafe {
(*self.slot_ptr).set_next_free(free_head);
}
chunk_slab.free_head.set(self.slot_ptr);
if was_full {
chunk.next_with_space.set(self.slab.head_with_space.get());
self.slab.head_with_space.set(self.chunk_idx);
}
}
}
const CHUNK_NONE: usize = usize::MAX;
struct ChunkEntry<T> {
inner: Box<BoundedSlab<T>>,
next_with_space: Cell<usize>,
}
pub struct Slab<T> {
chunks: core::cell::UnsafeCell<Vec<ChunkEntry<T>>>,
chunk_capacity: Cell<usize>,
head_with_space: Cell<usize>,
}
impl<T> Slab<T> {
#[inline]
pub unsafe fn with_chunk_capacity(chunk_capacity: usize) -> Self {
unsafe { Builder::new().chunk_capacity(chunk_capacity).build() }
}
#[inline]
pub fn capacity(&self) -> usize {
self.chunks().len() * self.chunk_capacity.get()
}
#[inline]
pub fn chunk_capacity(&self) -> usize {
self.chunk_capacity.get()
}
#[inline]
fn chunks(&self) -> &Vec<ChunkEntry<T>> {
unsafe { &*self.chunks.get() }
}
#[inline]
#[allow(clippy::mut_from_ref)]
fn chunks_mut(&self) -> &mut Vec<ChunkEntry<T>> {
unsafe { &mut *self.chunks.get() }
}
fn chunk(&self, chunk_idx: usize) -> &ChunkEntry<T> {
let chunks = self.chunks();
debug_assert!(chunk_idx < chunks.len());
unsafe { chunks.get_unchecked(chunk_idx) }
}
#[inline]
pub fn chunk_count(&self) -> usize {
self.chunks().len()
}
#[doc(hidden)]
pub fn contains_ptr(&self, ptr: *const ()) -> bool {
let chunks = self.chunks();
for chunk in chunks {
let chunk_slab = &*chunk.inner;
if chunk_slab.contains_ptr(ptr) {
return true;
}
}
false
}
pub fn reserve_chunks(&self, count: usize) {
let current = self.chunks().len();
for _ in current..count {
self.grow();
}
}
fn grow(&self) {
let chunks = self.chunks_mut();
let chunk_idx = chunks.len();
let inner = Box::new(unsafe { BoundedSlab::with_capacity(self.chunk_capacity.get()) });
let entry = ChunkEntry {
inner,
next_with_space: Cell::new(self.head_with_space.get()),
};
chunks.push(entry);
self.head_with_space.set(chunk_idx);
}
#[inline]
pub fn claim(&self) -> Claim<'_, T> {
let (slot_ptr, chunk_idx) = self.claim_ptr();
Claim {
slot_ptr,
slab: self,
chunk_idx,
}
}
#[doc(hidden)]
#[inline]
pub(crate) fn claim_ptr(&self) -> (*mut SlotCell<T>, usize) {
if self.head_with_space.get() == CHUNK_NONE {
self.grow();
}
let chunk_idx = self.head_with_space.get();
let chunk = self.chunk(chunk_idx);
let chunk_slab = &*chunk.inner;
let slot_ptr = chunk_slab.free_head.get();
debug_assert!(!slot_ptr.is_null(), "chunk on freelist has no free slots");
let next_free = unsafe { (*slot_ptr).get_next_free() };
chunk_slab.free_head.set(next_free);
if next_free.is_null() {
self.head_with_space.set(chunk.next_with_space.get());
}
(slot_ptr, chunk_idx)
}
#[inline]
pub fn alloc(&self, value: T) -> Slot<T> {
self.claim().write(value)
}
#[inline]
#[allow(clippy::needless_pass_by_value)]
pub fn free(&self, slot: Slot<T>) {
let slot_ptr = slot.into_raw();
debug_assert!(
self.contains_ptr(slot_ptr as *const ()),
"slot was not allocated from this slab"
);
unsafe {
(*slot_ptr).drop_value_in_place();
self.free_ptr(slot_ptr);
}
}
#[inline]
#[allow(clippy::needless_pass_by_value)]
pub fn take(&self, slot: Slot<T>) -> T {
let slot_ptr = slot.into_raw();
debug_assert!(
self.contains_ptr(slot_ptr as *const ()),
"slot was not allocated from this slab"
);
unsafe {
let value = (*slot_ptr).read_value();
self.free_ptr(slot_ptr);
value
}
}
#[doc(hidden)]
pub(crate) unsafe fn free_ptr_in_chunk(&self, slot_ptr: *mut SlotCell<T>, chunk_idx: usize) {
let chunk = self.chunk(chunk_idx);
let chunk_slab = &*chunk.inner;
let free_head = chunk_slab.free_head.get();
let was_full = free_head.is_null();
unsafe {
(*slot_ptr).set_next_free(free_head);
}
chunk_slab.free_head.set(slot_ptr);
if was_full {
chunk.next_with_space.set(self.head_with_space.get());
self.head_with_space.set(chunk_idx);
}
}
#[doc(hidden)]
pub(crate) unsafe fn free_ptr(&self, slot_ptr: *mut SlotCell<T>) {
let chunks = self.chunks();
let cap = self.chunk_capacity.get();
for (chunk_idx, chunk) in chunks.iter().enumerate() {
let chunk_slab = &*chunk.inner;
let base = chunk_slab.slots_ptr();
let end = base.wrapping_add(cap);
if slot_ptr >= base && slot_ptr < end {
let free_head = chunk_slab.free_head.get();
let was_full = free_head.is_null();
unsafe {
(*slot_ptr).set_next_free(free_head);
}
chunk_slab.free_head.set(slot_ptr);
if was_full {
chunk.next_with_space.set(self.head_with_space.get());
self.head_with_space.set(chunk_idx);
}
return;
}
}
unreachable!("free_ptr: slot_ptr not found in any chunk");
}
}
pub struct Builder {
chunk_capacity: usize,
initial_chunks: usize,
}
impl Builder {
#[inline]
pub fn new() -> Self {
Self {
chunk_capacity: 256,
initial_chunks: 0,
}
}
#[inline]
pub fn chunk_capacity(mut self, cap: usize) -> Self {
self.chunk_capacity = cap;
self
}
#[inline]
pub fn initial_chunks(mut self, n: usize) -> Self {
self.initial_chunks = n;
self
}
#[inline]
pub unsafe fn build<T>(self) -> Slab<T> {
assert!(self.chunk_capacity > 0, "chunk_capacity must be non-zero");
let slab = Slab {
chunks: core::cell::UnsafeCell::new(Vec::new()),
chunk_capacity: Cell::new(self.chunk_capacity),
head_with_space: Cell::new(CHUNK_NONE),
};
for _ in 0..self.initial_chunks {
slab.grow();
}
slab
}
}
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for Builder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Builder")
.field("chunk_capacity", &self.chunk_capacity)
.field("initial_chunks", &self.initial_chunks)
.finish()
}
}
impl<T> fmt::Debug for Slab<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Slab")
.field("capacity", &self.capacity())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::borrow::{Borrow, BorrowMut};
#[test]
fn slab_basic() {
let slab = unsafe { Slab::<u64>::with_chunk_capacity(16) };
let slot = slab.alloc(42);
assert_eq!(*slot, 42);
slab.free(slot);
}
#[test]
fn slab_grows() {
let slab = unsafe { Slab::<u64>::with_chunk_capacity(4) };
let mut slots = Vec::new();
for i in 0..10 {
slots.push(slab.alloc(i));
}
assert!(slab.capacity() >= 10);
for slot in slots {
slab.free(slot);
}
}
#[test]
fn slot_deref_mut() {
let slab = unsafe { Slab::<String>::with_chunk_capacity(16) };
let mut slot = slab.alloc("hello".to_string());
slot.push_str(" world");
assert_eq!(&*slot, "hello world");
slab.free(slot);
}
#[test]
fn slot_dealloc_take() {
let slab = unsafe { Slab::<String>::with_chunk_capacity(16) };
let slot = slab.alloc("hello".to_string());
let value = slab.take(slot);
assert_eq!(value, "hello");
}
#[test]
fn chunk_freelist_maintenance() {
let slab = unsafe { Slab::<u64>::with_chunk_capacity(2) };
let s1 = slab.alloc(1);
let s2 = slab.alloc(2);
let s3 = slab.alloc(3);
slab.free(s1);
let s4 = slab.alloc(4);
slab.free(s2);
slab.free(s3);
slab.free(s4);
}
#[test]
fn slot_size() {
assert_eq!(std::mem::size_of::<Slot<u64>>(), 8);
}
#[test]
fn borrow_traits() {
let slab = unsafe { Slab::<u64>::with_chunk_capacity(16) };
let mut slot = slab.alloc(42);
let borrowed: &u64 = slot.borrow();
assert_eq!(*borrowed, 42);
let borrowed_mut: &mut u64 = slot.borrow_mut();
*borrowed_mut = 100;
assert_eq!(*slot, 100);
slab.free(slot);
}
#[test]
fn builder_defaults() {
let slab = unsafe { Builder::new().build::<u64>() };
assert_eq!(slab.chunk_capacity(), 256);
assert_eq!(slab.chunk_count(), 0);
let slot = slab.alloc(42);
assert_eq!(*slot, 42);
slab.free(slot);
}
#[test]
fn builder_custom_chunk_capacity() {
let slab = unsafe { Builder::new().chunk_capacity(64).build::<u64>() };
assert_eq!(slab.chunk_capacity(), 64);
let slot = slab.alloc(1);
assert_eq!(slab.capacity(), 64);
slab.free(slot);
}
#[test]
fn builder_initial_chunks() {
let slab = unsafe {
Builder::new()
.chunk_capacity(32)
.initial_chunks(4)
.build::<u64>()
};
assert_eq!(slab.chunk_count(), 4);
assert_eq!(slab.capacity(), 128);
}
#[test]
#[should_panic(expected = "chunk_capacity must be non-zero")]
fn builder_zero_chunk_capacity_panics() {
let _slab = unsafe { Builder::new().chunk_capacity(0).build::<u64>() };
}
}