use std::cell::UnsafeCell;
use memmap2::MmapMut;
use crate::bitmap_tree::BitmapTree;
pub struct BufferSlab<const N: usize> {
storage: *mut u8,
virtual_capacity: u32,
tree: UnsafeCell<BitmapTree>,
_mmap: MmapMut,
}
pub struct AutoGuard<'a, const N: usize> {
ptr: *mut u8,
idx: u32,
slab: &'a BufferSlab<N>,
}
impl<const N: usize> BufferSlab<N> {
pub fn new(initial_capacity: u32, virtual_capacity: u32) -> Result<Self, std::io::Error> {
assert!(N > 0, "N doit être > 0");
assert!(
initial_capacity <= virtual_capacity,
"initial_capacity doit être <= virtual_capacity"
);
let virtual_bytes = (virtual_capacity as usize)
.checked_mul(N)
.expect("virtual_capacity * N dépasse usize::MAX");
let mmap = MmapMut::map_anon(virtual_bytes)?;
let storage = mmap.as_ptr() as *mut u8;
if initial_capacity > 0 {
let initial_bytes = initial_capacity as usize * N;
unsafe {
libc::madvise(
storage as *mut libc::c_void,
initial_bytes,
libc::MADV_POPULATE_WRITE|libc::MADV_HUGEPAGE,
);
}
}
Ok(Self {
storage,
virtual_capacity,
tree: UnsafeCell::new(BitmapTree::new()),
_mmap: mmap,
})
}
#[inline]
pub fn virtual_capacity(&self) -> u32 {
self.virtual_capacity
}
#[inline]
pub fn allocate(&self) -> AutoGuard<'_, N> {
self.try_allocate()
.expect("BufferSlab: virtual_capacity épuisée")
}
#[inline]
pub fn try_allocate(&self) -> Option<AutoGuard<'_, N>> {
let tree = unsafe { &mut *self.tree.get() };
let idx = tree.find_free_idx();
if idx >= self.virtual_capacity {
return None;
}
tree.set_bit(idx);
let ptr = unsafe { self.storage.add(idx as usize * N) };
Some(AutoGuard { ptr, idx, slab: self })
}
#[inline]
fn free_at(&self, idx: u32) {
debug_assert!(
unsafe { &*self.tree.get() }.is_set(idx),
"free_at: slot {} n'était pas alloué",
idx
);
unsafe { &mut *self.tree.get() }.clear_bit(idx);
}
}
impl<const N: usize> AutoGuard<'_, N> {
#[inline(always)]
const fn is_ptr_power_of_two()->bool{
N.is_power_of_two()
}
#[inline(always)]
unsafe fn hint_aligned(&self) {
if Self::is_ptr_power_of_two() {
unsafe{std::hint::assert_unchecked(self.ptr as usize & (N - 1) == 0);}
}
}
#[inline]
pub fn as_slice(&self) -> &[u8] {
unsafe {
self.hint_aligned();
std::slice::from_raw_parts(self.ptr, N)
}
}
#[inline]
pub fn as_slice_mut(&mut self) -> &mut [u8] {
unsafe {
self.hint_aligned();
std::slice::from_raw_parts_mut(self.ptr, N)
}
}
#[inline]
pub fn index(&self) -> u32 {
self.idx
}
#[inline]
pub fn as_ptr(&self) -> *mut u8 {
unsafe { self.hint_aligned() };
self.ptr
}
}
impl<const N: usize> Drop for AutoGuard<'_, N> {
#[inline]
fn drop(&mut self) {
self.slab.free_at(self.idx);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_allocate_drop_basic() {
let pool = BufferSlab::<4096>::new(4, 4).expect("should allocate");
let g0 = pool.allocate();
let g1 = pool.allocate();
let g2 = pool.allocate();
let g3 = pool.allocate();
assert_eq!(g0.index(), 0);
assert_eq!(g1.index(), 1);
assert_eq!(g2.index(), 2);
assert_eq!(g3.index(), 3);
assert!(pool.try_allocate().is_none());
drop(g1);
let g1b = pool.allocate();
assert_eq!(g1b.index(), 1);
}
#[test]
fn test_multiple_guards_simultaneous() {
let pool = BufferSlab::<64>::new(8, 8).expect("should allocate");
let guards: Vec<_> = (0..8).map(|_| pool.allocate()).collect();
assert!(pool.try_allocate().is_none());
drop(guards);
assert!(pool.try_allocate().is_some());
}
#[test]
fn test_beyond_initial_capacity() {
let pool = BufferSlab::<64>::new(2, 16).expect("should allocate");
let guards: Vec<_> = (0..16).map(|_| pool.allocate()).collect();
assert!(pool.try_allocate().is_none());
drop(guards);
}
#[test]
fn test_ptr_alignment() {
let pool = BufferSlab::<4096>::new(4, 4).expect("should allocate");
let g0 = pool.allocate();
let g1 = pool.allocate();
assert_eq!(g1.as_ptr() as usize - g0.as_ptr() as usize, 4096);
}
#[test]
fn test_rw_slice() {
let pool = BufferSlab::<64>::new(2, 2).expect("should allocate");
let mut g = pool.allocate();
g.as_slice_mut().fill(0xBE);
assert!(g.as_slice().iter().all(|&b| b == 0xBE));
}
#[test]
fn test_virtual_capacity_exhausted() {
let pool = BufferSlab::<64>::new(4, 600).expect("should allocate");
let guards: Vec<_> = (0..600).map(|_| pool.allocate()).collect();
assert!(pool.try_allocate().is_none());
drop(guards);
assert!(pool.try_allocate().is_some());
}
}