use crate::prelude::*;
use core::marker::PhantomData;
use core::ptr::NonNull;
type SlotIndex = u16;
#[derive(PartialEq)]
pub enum SlotsState {
Free,
Partial,
Full,
}
#[derive(Debug)]
pub struct Slots<T> {
pub buffer_ptr: NonNull<T>,
pub total_slots: SlotIndex,
pub free_slots: SlotIndex,
pub free_count: SlotIndex,
pub used_count: SlotIndex,
_marker: PhantomData<T>,
}
impl<T> Slots<T> {
const SLOT_SIZE: usize = const {
let size: usize = size_of::<T>();
Self::type_validate();
size
};
const SLOT_ALIGNMENT: usize = align_of::<T>();
pub const fn type_validate() {
let type_size = size_of::<T>();
let type_align = align_of::<T>();
let base_size = size_of::<SlotIndex>();
let base_align = align_of::<SlotIndex>();
assert!(
type_align.is_multiple_of(base_align),
"Slab Type Should Align With u16 Alignment"
);
assert!(
type_size >= base_size,
"Slots Size Is To Small \n Generic Slot Type Should Bigger Than u16"
);
}
pub fn new(buffer_ptr: NonNull<u8>, buffer_size: usize) -> Result<Self> {
let (al_buffer_ptr, al_buffer_size) = buffer_ptr
.align_up_buffer(Self::SLOT_ALIGNMENT, buffer_size)
.ok_or(SlabError::OutOfMemory)?;
let total_slots = Self::calculate_total_slots(al_buffer_size);
(total_slots != 0).on_err(SlabError::OutOfMemory)?;
let type_buffer_ptr = al_buffer_ptr.cast();
Ok(Self {
buffer_ptr: type_buffer_ptr,
total_slots,
free_slots: SlotIndex::MAX,
free_count: 0,
used_count: 0,
_marker: PhantomData,
})
}
pub const fn calculate_total_slots(buffer_size: usize) -> SlotIndex {
let count = buffer_size / Self::SLOT_SIZE;
if count >= SlotIndex::MAX as usize {
SlotIndex::MAX - 1
} else {
count as SlotIndex
}
}
#[inline]
pub fn try_pop_slot(&mut self) -> Result<NonNull<T>> {
let index = match self.free_slots {
SlotIndex::MAX => {
if self.used_count >= self.total_slots {
return Err(SlabError::OutOfMemory);
}
let index = self.used_count;
self.used_count += 1;
index
}
_ => {
let index = self.free_slots;
let next_index = self.read_from_index(index);
self.free_slots = next_index;
self.free_count -= 1;
index
}
};
self.check_index(index)?;
Ok(self.index_to_ptr(index))
}
#[inline]
pub fn try_push_slot(&mut self, ptr: NonNull<T>) -> Result<()> {
self.check_ptr(ptr)?;
let index = self.ptr_to_index(ptr)?;
self.check_index(index)?;
let root_index = self.free_slots;
self.free_slots = index;
self.write_at_index(index, root_index);
self.free_count += 1;
Ok(())
}
#[inline]
#[allow(unused)]
pub fn has_free(&self) -> bool {
self.free_count > 0 || self.used_count < self.total_slots
}
#[inline]
pub fn get_state(&self) -> SlotsState {
let free_slots = self.get_free_slots();
let total_slot = self.total_slots;
if free_slots == 0 {
SlotsState::Full
} else if free_slots == total_slot {
SlotsState::Free
} else {
SlotsState::Partial
}
}
#[inline]
pub fn get_free_slots(&self) -> SlotIndex {
(self.total_slots - self.used_count) + self.free_count
}
#[inline]
#[allow(unused)]
pub fn get_slot_count(&self) -> SlotIndex {
self.total_slots
}
#[inline]
fn write_at_index(&mut self, index: SlotIndex, value: SlotIndex) {
unsafe {
self.buffer_ptr
.add(index.into())
.cast::<SlotIndex>()
.write(value);
}
}
#[inline]
fn read_from_index(&self, index: SlotIndex) -> SlotIndex {
unsafe {
self.buffer_ptr.add(index.into()).cast::<SlotIndex>().read()
}
}
#[inline]
fn ptr_to_index(&self, ptr: NonNull<T>) -> Result<SlotIndex> {
let offset = unsafe { ptr.offset_from(self.buffer_ptr) };
let index = offset.try_into().map_err(|_| SlabError::InvalidPointer)?;
self.check_index(index)?;
Ok(index)
}
#[inline]
fn index_to_ptr(&self, index: SlotIndex) -> NonNull<T> {
self.buffer_ptr.unsafe_unsafe_add(index.into())
}
#[inline]
fn check_index(&self, index: SlotIndex) -> Result<()> {
(index < self.total_slots).as_result((), SlabError::InvalidPointer)
}
#[inline]
fn check_ptr(&self, ptr: NonNull<T>) -> Result<()> {
let address = ptr.as_address();
let base_address = self.buffer_ptr.as_address();
let buffer_bound_address =
self.buffer_ptr.as_address() + (self.total_slots as usize * Self::SLOT_SIZE);
let valid_range = address >= base_address && address < buffer_bound_address;
valid_range.on_err(SlabError::InvalidPointer)?;
let offset = address - base_address;
offset .is_aligned_to(Self::SLOT_SIZE)
.on_err(SlabError::InvalidPointer)
}
}
#[cfg(test)]
mod tests {
extern crate std;
use bolero::AnySliceMutExt;
use std::vec::Vec;
use super::*;
use std::alloc::{Layout, alloc_zeroed, dealloc};
use std::ptr::NonNull;
use crate::slots::Slots;
#[test]
fn slab_container_test() {
type SlotType = usize;
let alignment = align_of::<SlotType>();
const BUF_SZ: usize = size_of::<SlotType>() * 700;
let layout = Layout::from_size_align(BUF_SZ, alignment).expect("memory layout failed");
let ptr = unsafe { alloc_zeroed(layout) };
let buffer_ptr = NonNull::new(ptr).unwrap().cast::<SlotType>();
let unsafe_buffer_ptr = unsafe { buffer_ptr.cast::<u8>().add(1) };
Slots::<SlotType>::new(unsafe_buffer_ptr.cast(), 1).expect_err("Slots::new failed");
let mut slots = Slots::<SlotType>::new(buffer_ptr.cast(), BUF_SZ).unwrap();
assert!(
slots.check_ptr(buffer_ptr).is_ok(),
"Slots::check_ptr failed 0"
);
assert!(
slots.check_ptr(unsafe_buffer_ptr.cast()).is_err(),
"Slots::check_ptr failed 1"
);
let slots_count = Slots::<SlotType>::calculate_total_slots(BUF_SZ);
let mut v = Vec::new();
let mut count = 0;
while let Ok(l) = slots.try_pop_slot() {
count += 1;
v.push(l);
}
assert_eq!(count, 700, "Slots buffer allocation faile");
v.shuffle();
assert!(slots.get_free_slots() == 0, "Slots::get_free_slots failed");
for ptr in v.iter() {
let unsafe_ptr = unsafe { ptr.cast::<u8>().add(1) };
slots
.try_push_slot(unsafe_ptr.cast())
.expect_err("Slots::try_push_slot failed 0");
slots
.try_push_slot(*ptr)
.expect("Slots::try_push_slot failed 1");
}
assert!(slots.has_free(), "Slots::has_free failed");
assert!(
slots.get_free_slots() == slots_count,
"Slots::get_free_slots failed"
);
let mut allocated_total: u16 = 0u16;
while let Ok(_) = slots.try_pop_slot() {
allocated_total += 1;
}
assert!(
allocated_total == slots_count,
"Slots::try_pop_slot failed 1"
);
unsafe {
dealloc(ptr, layout);
}
}
#[test]
#[cfg(not(miri))]
fn pressure_test() {
type SlotType = usize;
let alignment = align_of::<SlotType>();
const BUF_SZ: usize = (size_of::<SlotType>() * 200) + 1;
let layout = Layout::from_size_align(BUF_SZ, alignment).expect("memory layout failed");
let ptr = unsafe { alloc_zeroed(layout) };
let buffer_ptr = NonNull::new(ptr).unwrap();
type SlotSize = usize; let mut slots = Slots::<SlotSize>::new(buffer_ptr, BUF_SZ).expect("Slots::new failed 0");
#[derive(Debug, bolero::TypeGenerator)]
enum Op {
PUSH,
POP,
}
bolero::check!()
.with_type::<(usize, Vec<Op>)>()
.for_each(|(rand, ops)| {
let mut holder_v = Vec::with_capacity(ops.len());
for op in ops {
match op {
Op::PUSH => {
if let Ok(ptr) = slots.try_pop_slot() {
let address_c = ptr.as_address() ^ rand;
unsafe { ptr.cast::<usize>().write(address_c) }
holder_v.push(ptr);
}
}
Op::POP => {
if let Some(ptr) = holder_v.pop() {
let address_c = ptr.as_address() ^ rand;
let cookie = unsafe { ptr.cast::<usize>().read() };
assert_eq!(address_c, cookie, "data currupted");
slots
.try_push_slot(ptr)
.expect("Slots::try_push_slot failed 0");
}
}
}
}
while let Some(ptr) = holder_v.pop() {
let address_c = ptr.as_address() ^ rand;
let cookie = unsafe { ptr.cast::<usize>().read() };
assert_eq!(address_c, cookie, "data currupted");
slots
.try_push_slot(ptr)
.expect("Slots::try_push_slot failed 0");
}
});
unsafe {
dealloc(ptr, layout);
}
}
}