#![allow(deprecated)]
use std::alloc::{alloc, Layout};
use std::sync::atomic::{AtomicU64, Ordering};
use crate::api::config::AllocConfig;
use crate::sync::mutex::Mutex;
const NUM_SIZE_CLASSES: usize = 9;
const DEFAULT_SIZE_CLASSES: [usize; NUM_SIZE_CLASSES] = [16, 32, 64, 128, 256, 512, 1024, 2048, 4096];
pub struct SlabRegistry {
size_classes: [usize; NUM_SIZE_CLASSES],
page_size: usize,
classes: [SlabClass; NUM_SIZE_CLASSES],
refill_count: AtomicU64,
}
struct SlabClass {
free_pages: Mutex<Vec<SlabPage>>,
object_size: usize,
}
struct SlabPage {
base: *mut u8,
size: usize,
free_list: Vec<*mut u8>,
}
impl SlabRegistry {
pub fn new(config: &AllocConfig) -> Self {
let mut size_classes = DEFAULT_SIZE_CLASSES;
for (i, &size) in config.slab_size_classes.iter().take(NUM_SIZE_CLASSES).enumerate() {
size_classes[i] = size;
}
let classes = std::array::from_fn(|i| SlabClass {
free_pages: Mutex::new(Vec::new()),
object_size: size_classes[i],
});
Self {
size_classes,
page_size: config.slab_page_size,
classes,
refill_count: AtomicU64::new(0),
}
}
fn size_class_index(&self, size: usize) -> Option<usize> {
self.size_classes.iter().position(|&s| s >= size)
}
pub fn refill(&self, size: usize) -> Vec<*mut u8> {
let class_idx = match self.size_class_index(size) {
Some(idx) => idx,
None => return Vec::new(), };
let class = &self.classes[class_idx];
let mut pages = class.free_pages.lock();
if let Some(mut page) = pages.pop() {
self.refill_count.fetch_add(1, Ordering::Relaxed);
let batch = std::mem::take(&mut page.free_list);
return batch;
}
drop(pages);
let page = self.allocate_page(class.object_size);
self.refill_count.fetch_add(1, Ordering::Relaxed);
page.free_list
}
fn allocate_page(&self, object_size: usize) -> SlabPage {
let layout = Layout::from_size_align(self.page_size, 16).expect("Invalid page layout");
let base = unsafe { alloc(layout) };
if base.is_null() {
panic!("Failed to allocate slab page");
}
let objects_per_page = self.page_size / object_size;
let mut free_list = Vec::with_capacity(objects_per_page);
for i in 0..objects_per_page {
let ptr = unsafe { base.add(i * object_size) };
free_list.push(ptr);
}
SlabPage {
base,
size: self.page_size,
free_list,
}
}
pub fn return_batch(&self, size: usize, batch: Vec<*mut u8>) {
if batch.is_empty() {
return;
}
let class_idx = match self.size_class_index(size) {
Some(idx) => idx,
None => return,
};
let class = &self.classes[class_idx];
let mut pages = class.free_pages.lock();
pages.push(SlabPage {
base: std::ptr::null_mut(), size: 0,
free_list: batch,
});
}
pub fn refill_count(&self) -> u64 {
self.refill_count.load(Ordering::Relaxed)
}
pub fn size_classes(&self) -> &[usize; NUM_SIZE_CLASSES] {
&self.size_classes
}
}
unsafe impl Send for SlabRegistry {}
unsafe impl Sync for SlabRegistry {}
pub struct LocalPools {
pools: [LocalPool; NUM_SIZE_CLASSES],
}
struct LocalPool {
free_list: Vec<*mut u8>,
object_size: usize,
}
impl LocalPools {
pub fn new() -> Self {
Self {
pools: std::array::from_fn(|i| LocalPool {
free_list: Vec::with_capacity(64),
object_size: DEFAULT_SIZE_CLASSES[i],
}),
}
}
fn pool_index(&self, size: usize) -> Option<usize> {
DEFAULT_SIZE_CLASSES.iter().position(|&s| s >= size)
}
pub fn alloc(&mut self, size: usize, registry: &SlabRegistry) -> *mut u8 {
let pool_idx = match self.pool_index(size) {
Some(idx) => idx,
None => return std::ptr::null_mut(), };
let pool = &mut self.pools[pool_idx];
if let Some(ptr) = pool.free_list.pop() {
return ptr;
}
let batch = registry.refill(pool.object_size);
if batch.is_empty() {
return std::ptr::null_mut();
}
pool.free_list = batch;
pool.free_list.pop().unwrap_or(std::ptr::null_mut())
}
pub fn free(&mut self, ptr: *mut u8, size: usize) {
let pool_idx = match self.pool_index(size) {
Some(idx) => idx,
None => return, };
#[cfg(feature = "debug")]
unsafe {
crate::debug::poison::poison_freed(ptr, size);
}
let pool = &mut self.pools[pool_idx];
pool.free_list.push(ptr);
}
pub fn drain_deferred(&mut self, ptr: *mut u8, size: usize) {
self.free(ptr, size);
}
}
impl Default for LocalPools {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_slab_allocation() {
let config = AllocConfig::default();
let registry = SlabRegistry::new(&config);
let mut pools = LocalPools::new();
let ptr = pools.alloc(32, ®istry);
assert!(!ptr.is_null());
pools.free(ptr, 32);
let ptr2 = pools.alloc(32, ®istry);
assert_eq!(ptr, ptr2);
}
}