use crate::{
error::Error,
header::{
layout::{self, AllocatorLayout},
Header, WorkerLocalListHeads,
},
index::NULL_U32,
linked_list_node::LinkedListNode,
size_classes::{MAX_SIZE, MIN_SIZE},
};
use core::ffi::c_void;
use std::{fs::File, mem::offset_of, mem::size_of, ptr::NonNull, sync::atomic::Ordering};
pub fn create(
file: &File,
file_size: usize,
min_workers: u32,
slab_size: u32,
) -> Result<NonNull<Header>, Error> {
if min_workers == 0 {
return Err(Error::InvalidNumWorkers);
}
verify_slab_size(slab_size)?;
verify_total_slabs(file_size, slab_size)?;
let limits =
layout::max_workers(file_size, slab_size, min_workers).ok_or(Error::InvalidFileSize)?;
let num_workers = limits.max_workers;
let layout = layout::layout_for_num_slabs(num_workers, slab_size, limits.usable_slabs);
if layout.num_slabs == 0 {
return Err(Error::InvalidFileSize);
}
if file.metadata()?.len() != 0 {
return Err(Error::AlreadyInitialized);
}
file.set_len(file_size as u64)?;
let mmap = crate::memory_map::map_file(file, file_size)?;
let header = NonNull::new(mmap.cast::<Header>()).expect("mmap already checked for null");
unsafe {
initialize::allocator(header, slab_size, num_workers, layout);
}
Ok(header)
}
pub fn join(file: &File) -> Result<(NonNull<Header>, usize), Error> {
let file_size = file.metadata()?.len() as usize;
if file_size < size_of::<Header>() {
return Err(Error::InvalidHeader);
}
let mmap = crate::memory_map::map_file(file, file_size)?;
join_inner(mmap, file_size).inspect_err(|_| {
let _ = crate::memory_map::unmap_file(mmap, file_size);
})
}
fn join_inner(mmap: *mut c_void, file_size: usize) -> Result<(NonNull<Header>, usize), Error> {
let header = NonNull::new(mmap.cast::<Header>()).expect("mmap already checked for null");
{
let header = unsafe { header.as_ref() };
if header.magic.load(Ordering::Acquire) != crate::header::MAGIC {
return Err(Error::InvalidMagic);
}
if header.version != crate::header::VERSION {
return Err(Error::InvalidVersion {
expected: crate::header::VERSION,
actual: header.version,
});
}
if header.num_workers == 0 {
return Err(Error::InvalidHeader);
}
verify_slab_size(header.slab_size)?;
verify_total_slabs(file_size, header.slab_size)?;
let limits = layout::max_workers(file_size, header.slab_size, header.num_workers)
.ok_or(Error::InvalidHeader)?;
if limits.max_workers != header.num_workers {
return Err(Error::HeaderMismatch);
}
let expected_layout =
layout::layout_for_num_slabs(header.num_workers, header.slab_size, limits.usable_slabs);
if header.num_slabs != expected_layout.num_slabs
|| header.free_list_elements_offset != expected_layout.free_list_elements_offset
|| header.slab_shared_meta_offset != expected_layout.slab_shared_meta_offset
|| header.slab_free_stacks_offset != expected_layout.slab_free_stacks_offset
|| header.slabs_offset != expected_layout.slabs_offset
{
return Err(Error::HeaderMismatch);
}
}
Ok((header, file_size))
}
fn verify_slab_size(slab_size: u32) -> Result<(), Error> {
if !slab_size.is_power_of_two() {
return Err(Error::InvalidSlabSize);
}
if slab_size < 4 * MAX_SIZE {
return Err(Error::InvalidSlabSize);
}
if slab_size / MIN_SIZE > u16::MAX as u32 {
return Err(Error::InvalidSlabSize);
}
Ok(())
}
fn verify_total_slabs(file_size: usize, slab_size: u32) -> Result<(), Error> {
if file_size / slab_size as usize > u32::MAX as usize {
return Err(Error::InvalidFileSize);
}
Ok(())
}
pub mod initialize {
use super::*;
use crate::slab_meta::SlabMeta;
pub unsafe fn allocator(
header: NonNull<Header>,
slab_size: u32,
num_workers: u32,
layout: AllocatorLayout,
) {
unsafe {
init_header(header, slab_size, num_workers, layout);
}
unsafe {
worker_local_lists(header);
free_list_elements(header);
slab_shared_meta(header);
}
let header = unsafe { header.as_ref() };
header.magic.store(crate::header::MAGIC, Ordering::Release);
}
unsafe fn init_header(
mut header: NonNull<Header>,
slab_size: u32,
num_workers: u32,
layout: AllocatorLayout,
) {
let header = unsafe { header.as_mut() };
header.num_workers = num_workers;
header.num_slabs = layout.num_slabs;
header.slab_size = slab_size;
header.free_list_elements_offset = layout.free_list_elements_offset;
header.slab_shared_meta_offset = layout.slab_shared_meta_offset;
header.slab_free_stacks_offset = layout.slab_free_stacks_offset;
header.slabs_offset = layout.slabs_offset;
header.global_free_list_head.store(
crate::global_free_list::pack_index(0, NULL_U32),
Ordering::Release,
);
header.version = crate::header::VERSION;
}
fn worker_local_lists(header: NonNull<Header>) {
let num_workers = {
let header = unsafe { header.as_ref() };
header.num_workers
};
let all_workers_heads = unsafe {
header
.byte_add(offset_of!(Header, worker_local_list_heads))
.cast::<WorkerLocalListHeads>()
};
for i in 0..num_workers {
let worker_head = unsafe { all_workers_heads.add(i as usize).as_mut() };
worker_head.claimed.store(0, Ordering::Release);
worker_head
.outstanding_allocation_bytes
.store(0, Ordering::Release);
for worker_partial_full in worker_head.heads.iter_mut() {
worker_partial_full
.partial
.store(NULL_U32, Ordering::Release);
worker_partial_full.full.store(NULL_U32, Ordering::Release);
}
}
}
unsafe fn free_list_elements(header: NonNull<Header>) {
let (num_slabs, free_list_elements_offset) = {
let header = unsafe { header.as_ref() };
(header.num_slabs, header.free_list_elements_offset)
};
let free_list_element_ptr =
unsafe { header.byte_add(free_list_elements_offset as usize) }.cast::<LinkedListNode>();
for slab_index in 0..num_slabs {
let global_next = if slab_index == num_slabs - 1 {
NULL_U32
} else {
slab_index + 1
};
let free_list_element =
unsafe { free_list_element_ptr.add(slab_index as usize).as_mut() };
free_list_element
.global_next
.store(global_next, Ordering::Release);
free_list_element
.worker_local_prev
.store(NULL_U32, Ordering::Release);
free_list_element
.worker_local_next
.store(NULL_U32, Ordering::Release);
}
unsafe { header.as_ref() }
.global_free_list_head
.store(crate::global_free_list::pack_index(0, 0), Ordering::Release);
}
fn slab_shared_meta(header: NonNull<Header>) {
let (num_slabs, slab_shared_meta_offset) = {
let header = unsafe { header.as_ref() };
(header.num_slabs, header.slab_shared_meta_offset)
};
for slab_index in 0..num_slabs {
let slab_meta = unsafe {
header
.byte_add(slab_shared_meta_offset as usize)
.cast::<SlabMeta>()
.add(slab_index as usize)
.as_mut()
};
slab_meta.assign(NULL_U32, 0);
}
}
}