#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::alloc::Layout;
#[cfg(not(feature = "std"))]
use alloc::alloc::{alloc, dealloc, realloc};
#[cfg(feature = "std")]
use std::alloc::{alloc, dealloc, realloc};
pub struct SlabEntry {
key_len: u32,
val_len: u32,
cap: u32,
data: *mut u8,
}
impl SlabEntry {
#[inline]
pub fn key(&self) -> &[u8] {
unsafe { core::slice::from_raw_parts(self.data, self.key_len as usize) }
}
#[inline]
pub fn value(&self) -> &[u8] {
unsafe {
let val_ptr = self.data.add(self.key_len as usize);
core::slice::from_raw_parts(val_ptr, self.val_len as usize)
}
}
#[inline]
pub fn rewrite(&mut self, key: &[u8], value: &[u8]) {
let needed = key.len() + value.len();
if needed > self.cap as usize {
let old_layout = Layout::from_size_align(self.cap as usize, 1).unwrap();
let new_layout = Layout::from_size_align(needed, 1).unwrap();
self.data = unsafe { realloc(self.data, old_layout, new_layout.size()) };
self.cap = needed as u32;
}
unsafe {
core::ptr::copy_nonoverlapping(key.as_ptr(), self.data, key.len());
core::ptr::copy_nonoverlapping(value.as_ptr(), self.data.add(key.len()), value.len());
}
self.key_len = key.len() as u32;
self.val_len = value.len() as u32;
}
unsafe fn dealloc_data(&mut self) {
if self.cap > 0 {
let layout = Layout::from_size_align(self.cap as usize, 1).unwrap();
dealloc(self.data, layout);
self.cap = 0;
}
}
}
pub struct SlabPool {
entries: Vec<Option<Box<SlabEntry>>>,
free_list: Vec<usize>,
}
impl SlabPool {
pub fn new() -> Self {
Self {
entries: Vec::new(),
free_list: Vec::new(),
}
}
pub fn alloc(&mut self, key: &[u8], value: &[u8]) -> usize {
if let Some(idx) = self.free_list.pop() {
self.entries[idx].as_mut().unwrap().rewrite(key, value);
return idx;
}
let total_len = key.len() + value.len();
let layout = Layout::from_size_align(total_len.max(1), 1).unwrap();
let data = unsafe { alloc(layout) };
unsafe {
core::ptr::copy_nonoverlapping(key.as_ptr(), data, key.len());
core::ptr::copy_nonoverlapping(value.as_ptr(), data.add(key.len()), value.len());
}
let entry = Box::new(SlabEntry {
key_len: key.len() as u32,
val_len: value.len() as u32,
cap: total_len as u32,
data,
});
let idx = self.entries.len();
self.entries.push(Some(entry));
idx
}
#[inline]
pub fn free(&mut self, idx: usize) {
self.free_list.push(idx);
}
#[inline]
pub fn get(&self, idx: usize) -> &SlabEntry {
self.entries[idx].as_ref().unwrap()
}
}
impl Drop for SlabPool {
fn drop(&mut self) {
for entry in self.entries.iter_mut().flatten() {
unsafe {
entry.dealloc_data();
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_slab_alloc_and_get() {
let mut pool = SlabPool::new();
let idx = pool.alloc(b"hello", b"world");
assert_eq!(pool.get(idx).key(), b"hello");
assert_eq!(pool.get(idx).value(), b"world");
}
#[test]
fn test_free_list_reuse_index() {
let mut pool = SlabPool::new();
let idx0 = pool.alloc(b"key0", b"val0");
let idx1 = pool.alloc(b"key1", b"val1");
assert_eq!(pool.entries.len(), 2);
pool.free(idx0);
let idx2 = pool.alloc(b"key2", b"val2");
assert_eq!(idx2, idx0, "free list must reuse idx0");
assert_eq!(pool.entries.len(), 2, "pool must not grow on reuse");
assert_eq!(pool.get(idx2).key(), b"key2");
assert_eq!(pool.get(idx2).value(), b"val2");
assert_eq!(pool.get(idx1).key(), b"key1");
}
#[test]
fn test_free_list_larger_rewrite() {
let mut pool = SlabPool::new();
let idx = pool.alloc(b"k", b"v");
pool.free(idx);
let idx2 = pool.alloc(b"longer_key_here", b"longer_value_here");
assert_eq!(idx2, idx);
assert_eq!(pool.get(idx2).key(), b"longer_key_here");
assert_eq!(pool.get(idx2).value(), b"longer_value_here");
}
#[test]
fn test_free_list_bulk_reuse() {
let mut pool = SlabPool::new();
let i0 = pool.alloc(b"a", b"1");
let i1 = pool.alloc(b"b", b"2");
let i2 = pool.alloc(b"c", b"3");
let len_before = pool.entries.len();
pool.free(i0);
pool.free(i1);
pool.free(i2);
let _ = pool.alloc(b"x", b"10");
let _ = pool.alloc(b"y", b"20");
let _ = pool.alloc(b"z", b"30");
assert_eq!(pool.entries.len(), len_before, "all reallocs must reuse");
assert_eq!(pool.free_list.len(), 0, "free list must be empty");
}
}