use crate::per_cpu;
use crate::validation::init_bytes;
use crate::{LocalAllocatorSelector, ThreadAllocator, initialize_allocated_bytes};
use mnemosyne_arena::{HasSegmentPool, allocate_large_or_huge};
use mnemosyne_core::constants::MIN_BLOCK_SIZE;
use mnemosyne_core::policy::AllocPolicy;
use mnemosyne_core::size_class::{class_to_size, size_to_class_nonzero};
use mnemosyne_core::validation::{is_valid_alloc_request, is_valid_layout_alloc_request};
#[inline(always)]
pub unsafe fn thread_alloc<
P: AllocPolicy + crate::tls_slot::PolicySlotSelection<B>,
B: HasSegmentPool + LocalAllocatorSelector<B>,
>(
size: usize,
align: usize,
) -> *mut u8 {
if !is_valid_alloc_request(size, align) {
return core::ptr::null_mut();
}
if P::MAX_ALLOC_SIZE_LIMIT != 0 && size > P::MAX_ALLOC_SIZE_LIMIT {
return core::ptr::null_mut();
}
let ptr = {
unsafe { thread_alloc_checked::<P, B>(size, align) }
};
if mnemosyne_prof::is_active() && !ptr.is_null() {
mnemosyne_prof::on_alloc(ptr, size);
}
ptr
}
#[inline(always)]
pub unsafe fn thread_alloc_layout<
P: AllocPolicy + crate::tls_slot::PolicySlotSelection<B>,
B: HasSegmentPool + LocalAllocatorSelector<B>,
>(
size: usize,
align: usize,
) -> *mut u8 {
if !is_valid_layout_alloc_request(size, align) {
return core::ptr::null_mut();
}
debug_assert!(
align != 0 && align.is_power_of_two(),
"Layout-validated allocation received invalid alignment {align}"
);
let ptr = unsafe { thread_alloc_checked::<P, B>(size, align) };
if mnemosyne_prof::is_active() && !ptr.is_null() {
mnemosyne_prof::on_alloc(ptr, size);
}
ptr
}
#[inline(always)]
pub(crate) fn small_path_class(size: usize, align: usize) -> Option<usize> {
let adjusted_size = core::cmp::max(size, align);
if align <= MIN_BLOCK_SIZE {
return size_to_class_nonzero(adjusted_size);
}
let rounded = (adjusted_size + align - 1) & !(align - 1);
match size_to_class_nonzero(rounded) {
Some(c) if class_to_size(c) & (align - 1) == 0 => Some(c),
_ => None,
}
}
#[inline(always)]
unsafe fn thread_alloc_checked<
P: AllocPolicy + crate::tls_slot::PolicySlotSelection<B>,
B: HasSegmentPool + LocalAllocatorSelector<B>,
>(
size: usize,
align: usize,
) -> *mut u8 {
let adjusted_size = core::cmp::max(size, align);
let class = match small_path_class(size, align) {
Some(c) => c,
None => {
return unsafe { fallback_large_or_huge::<P, B>(adjusted_size, align) };
}
};
let slot_ptr = B::get_allocator_ptr_raw_for_policy::<P>();
if !slot_ptr.is_null() {
if !unsafe { crate::tls_slot::LocalAllocatorSlot::<B>::is_allocating(slot_ptr) } {
let alloc = unsafe { &mut *(slot_ptr as *mut ThreadAllocator<B>) };
if let Some(page_ptr) = unsafe { *alloc.active_pages.get_unchecked(class) } {
let page = page_ptr.as_ptr();
if let Some(block) =
unsafe { crate::local_alloc::page::try_allocate_page_local::<P>(page) }
{
return unsafe {
finalize_small_alloc::<P>(block.as_ptr() as *mut u8, class, adjusted_size)
};
}
if let Some(block) = unsafe {
crate::local_alloc::page::try_reclaim_and_allocate::<P>(
page,
&mut alloc.cross_thread_reclaimed,
)
} {
return unsafe {
finalize_small_alloc::<P>(block.as_ptr() as *mut u8, class, adjusted_size)
};
}
}
}
unsafe {
thread_alloc_cold::<P, B>(
class,
adjusted_size,
align,
Some(core::ptr::NonNull::new_unchecked(
slot_ptr as *mut ThreadAllocator<B>,
)),
)
}
} else {
unsafe { thread_alloc_cold::<P, B>(class, adjusted_size, align, None) }
}
}
#[cold]
#[inline(never)]
unsafe fn thread_alloc_cold<
P: AllocPolicy + crate::tls_slot::PolicySlotSelection<B>,
B: HasSegmentPool + LocalAllocatorSelector<B>,
>(
class: usize,
adjusted_size: usize,
align: usize,
alloc_opt: Option<core::ptr::NonNull<ThreadAllocator<B>>>,
) -> *mut u8 {
if B::ENABLE_CPU_CACHE {
let cpu_ptr = per_cpu::try_alloc_cpu::<P>(class);
if !cpu_ptr.is_null() {
return unsafe { finalize_small_alloc::<P>(cpu_ptr, class, adjusted_size) };
}
}
let slot_ptr = match alloc_opt {
Some(p) => p.as_ptr().cast::<core::ffi::c_void>(),
None => B::get_allocator_ptr_for_policy::<P>(),
};
if slot_ptr.is_null() {
return unsafe { fallback_large_or_huge::<P, B>(adjusted_size, align) };
}
if unsafe { crate::tls_slot::LocalAllocatorSlot::<B>::is_allocating(slot_ptr) } {
return unsafe { fallback_large_or_huge::<P, B>(adjusted_size, align) };
}
unsafe { crate::tls_slot::LocalAllocatorSlot::<B>::set_allocating(slot_ptr, true) };
let alloc = unsafe { &mut *(slot_ptr as *mut ThreadAllocator<B>) };
let ptr = unsafe { alloc.alloc_cold::<P>(class) };
unsafe { crate::tls_slot::LocalAllocatorSlot::<B>::set_allocating(slot_ptr, false) };
unsafe { finalize_small_alloc_or_fallback::<P, B>(ptr, class, adjusted_size, align) }
}
#[inline(always)]
unsafe fn finalize_small_alloc<P: AllocPolicy>(
ptr: *mut u8,
class: usize,
adjusted_size: usize,
) -> *mut u8 {
unsafe { initialize_allocated_bytes::<P>(ptr, adjusted_size) };
crate::bin_stats::record_alloc_with_size(class, adjusted_size);
ptr
}
#[inline(always)]
unsafe fn finalize_small_alloc_or_fallback<P: AllocPolicy, B: HasSegmentPool>(
ptr: *mut u8,
class: usize,
adjusted_size: usize,
align: usize,
) -> *mut u8 {
if ptr.is_null() {
unsafe { fallback_large_or_huge::<P, B>(adjusted_size, align) }
} else {
unsafe { finalize_small_alloc::<P>(ptr, class, adjusted_size) }
}
}
#[inline(always)]
unsafe fn fallback_large_or_huge<P: AllocPolicy, B: HasSegmentPool>(
adjusted_size: usize,
align: usize,
) -> *mut u8 {
unsafe {
allocate_large_or_huge_initialized::<B>(
adjusted_size,
align,
P::ENABLE_POISONING,
P::ZERO_INITIALIZE,
P::POISON_ALLOC_BYTE,
)
}
}
#[inline(always)]
unsafe fn allocate_large_or_huge_initialized<B: HasSegmentPool>(
size: usize,
align: usize,
enable_poisoning: bool,
zero_init: bool,
poison_alloc_byte: u8,
) -> *mut u8 {
let ptr = {
unsafe { allocate_large_or_huge::<B>(size, align, enable_poisoning) }
};
if !ptr.is_null() {
unsafe { init_bytes(ptr, size, zero_init, enable_poisoning, poison_alloc_byte) };
}
ptr
}