use core::marker::PhantomData;
use core::mem;
use crate::shared::SlotCell;
use super::{AlignedBytes, Slot, validate_type};
pub struct Slab<const N: usize> {
inner: crate::unbounded::Slab<AlignedBytes<N>>,
}
impl<const N: usize> Slab<N> {
#[inline]
pub unsafe fn with_chunk_capacity(chunk_capacity: usize) -> Self {
unsafe { Builder::new().chunk_capacity(chunk_capacity).build::<N>() }
}
#[inline]
pub fn alloc<T>(&self, value: T) -> Slot<T> {
validate_type::<T, N>();
let (slot_ptr, _chunk_idx) = self.inner.claim_ptr();
unsafe {
let data_ptr = slot_ptr.cast::<T>();
core::ptr::write(data_ptr, value);
}
Slot {
ptr: slot_ptr.cast::<u8>(),
_marker: PhantomData,
}
}
#[inline]
pub fn claim(&self) -> super::ByteClaim<'_> {
let claim = self.inner.claim();
let (ptr, chunk_idx) = claim.into_ptr();
let slab_ptr = core::ptr::from_ref(&self.inner).cast::<u8>();
unsafe {
super::ByteClaim::from_raw_parts(
ptr.cast::<u8>(),
slab_ptr,
free_raw_impl::<N>,
chunk_idx,
N,
)
}
}
#[inline]
pub unsafe fn free_raw(&self, ptr: *mut u8) {
unsafe {
self.inner.free_ptr(ptr.cast());
}
}
#[inline]
pub unsafe fn free_raw_in_chunk(&self, ptr: *mut u8, chunk_idx: usize) {
unsafe {
self.inner.free_ptr_in_chunk(ptr.cast(), chunk_idx);
}
}
#[inline]
pub unsafe fn alloc_raw(&self, src: *const u8, size: usize) -> *mut u8 {
assert!(size <= N, "raw alloc size {size} exceeds slot size {N}");
let (slot_ptr, _chunk_idx) = self.inner.claim_ptr();
let dst = slot_ptr.cast::<u8>();
unsafe { core::ptr::copy_nonoverlapping(src, dst, size) };
dst
}
#[inline]
pub fn free<T>(&self, ptr: Slot<T>) {
let data_ptr = ptr.ptr;
debug_assert!(
self.inner.contains_ptr(data_ptr as *const ()),
"slot was not allocated from this slab"
);
mem::forget(ptr);
unsafe {
core::ptr::drop_in_place(data_ptr.cast::<T>());
self.inner
.free_ptr(data_ptr.cast::<SlotCell<AlignedBytes<N>>>());
}
}
#[inline]
pub fn take<T>(&self, ptr: Slot<T>) -> T {
let data_ptr = ptr.ptr;
debug_assert!(
self.inner.contains_ptr(data_ptr as *const ()),
"slot was not allocated from this slab"
);
mem::forget(ptr);
unsafe {
let value = core::ptr::read(data_ptr.cast::<T>());
self.inner
.free_ptr(data_ptr.cast::<SlotCell<AlignedBytes<N>>>());
value
}
}
}
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<const N: usize>(self) -> Slab<N> {
let inner = unsafe {
crate::unbounded::Builder::new()
.chunk_capacity(self.chunk_capacity)
.initial_chunks(self.initial_chunks)
.build::<AlignedBytes<N>>()
};
Slab { inner }
}
}
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
impl core::fmt::Debug for Builder {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Builder")
.field("chunk_capacity", &self.chunk_capacity)
.field("initial_chunks", &self.initial_chunks)
.finish()
}
}
unsafe fn free_raw_impl<const N: usize>(slab_ptr: *const u8, slot_ptr: *mut u8, chunk_idx: usize) {
let slab = unsafe { &*(slab_ptr as *const crate::unbounded::Slab<super::AlignedBytes<N>>) };
unsafe {
slab.free_ptr_in_chunk(slot_ptr.cast(), chunk_idx);
}
}
impl<const N: usize> core::fmt::Debug for Slab<N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("byte::unbounded::Slab")
.field("slot_size", &N)
.finish()
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::*;
#[test]
fn basic_alloc_free() {
let slab: Slab<64> = unsafe { Slab::with_chunk_capacity(256) };
let ptr = slab.alloc(42u64);
assert_eq!(*ptr, 42);
slab.free(ptr);
}
#[test]
fn heterogeneous_types() {
let slab: Slab<128> = unsafe { Slab::with_chunk_capacity(256) };
let p1 = slab.alloc(42u64);
let p2 = slab.alloc(String::from("hello"));
let p3 = slab.alloc([1.0f64; 8]);
assert_eq!(*p1, 42);
assert_eq!(&*p2, "hello");
assert_eq!(p3[0], 1.0);
slab.free(p3);
slab.free(p2);
slab.free(p1);
}
#[test]
fn grows_automatically() {
let slab: Slab<16> = unsafe { Slab::with_chunk_capacity(2) };
let mut ptrs = alloc::vec::Vec::new();
for i in 0..100u64 {
ptrs.push(slab.alloc(i));
}
for (i, ptr) in ptrs.iter().enumerate() {
assert_eq!(**ptr, i as u64);
}
for ptr in ptrs {
slab.free(ptr);
}
}
#[test]
fn take_returns_value() {
let slab: Slab<64> = unsafe { Slab::with_chunk_capacity(256) };
let ptr = slab.alloc(String::from("taken"));
let val = slab.take(ptr);
assert_eq!(val, "taken");
}
#[test]
fn claim_write_typed() {
let slab: Slab<64> = unsafe { Slab::with_chunk_capacity(256) };
let claim = slab.claim();
let slot = claim.write(42u64);
assert_eq!(*slot, 42);
slab.free(slot);
}
#[test]
fn claim_drop_returns_to_freelist() {
let slab: Slab<64> = unsafe { Slab::with_chunk_capacity(1) };
let claim = slab.claim();
drop(claim);
let claim = slab.claim();
let slot = claim.write(99u64);
assert_eq!(*slot, 99);
slab.free(slot);
}
#[test]
fn claim_write_raw() {
let slab: Slab<64> = unsafe { Slab::with_chunk_capacity(256) };
let claim = slab.claim();
let val: u64 = 77;
let ptr = unsafe {
claim.write_raw(&val as *const u64 as *const u8, core::mem::size_of::<u64>())
};
assert_eq!(unsafe { *(ptr as *const u64) }, 77);
let slot = unsafe { super::Slot::<u64>::from_raw(ptr) };
slab.free(slot);
}
#[test]
fn builder_defaults() {
let slab = unsafe { Builder::new().build::<64>() };
let slot = slab.alloc(42u64);
assert_eq!(*slot, 42);
slab.free(slot);
}
#[test]
fn builder_custom_chunk_capacity() {
let slab = unsafe { Builder::new().chunk_capacity(32).build::<64>() };
let slot = slab.alloc(1u64);
slab.free(slot);
}
#[test]
fn builder_initial_chunks() {
let slab = unsafe {
Builder::new()
.chunk_capacity(16)
.initial_chunks(3)
.build::<64>()
};
let mut ptrs = alloc::vec::Vec::new();
for i in 0..48u64 {
ptrs.push(slab.alloc(i));
}
for ptr in ptrs {
slab.free(ptr);
}
}
#[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::<64>() };
}
}