#![allow(clippy::missing_const_for_thread_local)]
use core::marker::PhantomData;
use core::ptr::NonNull;
use core::sync::atomic::{AtomicUsize, Ordering};
use mnemosyne_arena::HasSegmentPool;
use mnemosyne_backend::DefaultBackend;
use mnemosyne_core::constants::NUM_SIZE_CLASSES;
use mnemosyne_core::types::{Page, Segment};
pub use stats::{SizeClassOccupancy, ThreadAllocatorStats};
melinoe::thread_cached! {
mod tls_seed: usize;
}
#[inline(always)]
pub(crate) fn get_tls_seed() -> usize {
tls_seed::get_or_init(|| {
use std::hash::{BuildHasher, Hasher};
let state = std::collections::hash_map::RandomState::new();
let mut hasher = state.build_hasher();
hasher.write_usize(0);
let mut seed = hasher.finish() as usize;
if seed == 0 {
seed = 0xdeadbeeffacefeed;
}
seed
})
}
static CROSS_THREAD_RECLAIMED_BLOCKS: AtomicUsize = AtomicUsize::new(0);
pub struct ThreadAllocator<B: HasSegmentPool = DefaultBackend> {
pub active_pages: [Option<NonNull<Page>>; NUM_SIZE_CLASSES],
pub full_pages: [Option<NonNull<Page>>; NUM_SIZE_CLASSES],
pub empty_pages: Option<NonNull<Page>>,
pub current_segment: Option<NonNull<Segment>>,
pub next_page_index: usize,
pub owned_segments_head: *mut Segment,
pub owned_segment_count: usize,
pub page_refills: usize,
pub recycled_pages: usize,
pub fresh_pages: usize,
pub fresh_segments: usize,
pub orphan_segments_adopted: usize,
pub recycle_sweeps: usize,
pub cross_thread_reclaimed: usize,
pub is_allocating: bool,
pub rng_state: u64,
pub defrag_counter: usize,
pub _phantom: PhantomData<B>,
}
impl<B: HasSegmentPool> Default for ThreadAllocator<B> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<B: HasSegmentPool> ThreadAllocator<B> {
pub const fn new() -> Self {
Self {
active_pages: [None; NUM_SIZE_CLASSES],
full_pages: [None; NUM_SIZE_CLASSES],
empty_pages: None,
current_segment: None,
next_page_index: 0,
owned_segments_head: core::ptr::null_mut(),
owned_segment_count: 0,
page_refills: 0,
recycled_pages: 0,
fresh_pages: 0,
fresh_segments: 0,
orphan_segments_adopted: 0,
recycle_sweeps: 0,
cross_thread_reclaimed: 0,
is_allocating: false,
rng_state: 0x123456789abcdefu64,
defrag_counter: 0,
_phantom: PhantomData,
}
}
#[inline]
pub fn next_random(&mut self) -> u64 {
if self.rng_state == 0x123456789abcdefu64 {
let seed = get_tls_seed() as u64;
let addr = self as *const Self as usize as u64;
self.rng_state = seed ^ addr;
if self.rng_state == 0 {
self.rng_state = 0x123456789abcdefu64;
}
}
let mut x = self.rng_state;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.rng_state = x;
x
}
pub fn cross_thread_reclaimed_blocks() -> usize {
CROSS_THREAD_RECLAIMED_BLOCKS.load(Ordering::Relaxed)
}
#[inline(always)]
pub(crate) fn record_cross_thread_reclaimed(&mut self, count: usize) {
self.cross_thread_reclaimed += count;
}
#[inline(always)]
pub fn is_current_segment(&self, segment: *mut Segment) -> bool {
self.current_segment
.is_some_and(|current| current.as_ptr() == segment)
}
#[inline(always)]
pub unsafe fn record_defrag_operation<P: mnemosyne_core::AllocPolicy>(&mut self) {
self.defrag_counter += 1;
if self.defrag_counter >= 64 {
unsafe { self.run_periodic_defragmentation::<P>() };
}
}
#[cold]
#[inline(never)]
unsafe fn run_periodic_defragmentation<P: mnemosyne_core::AllocPolicy>(&mut self) {
self.defrag_counter = 0;
if self.is_allocating {
unsafe { self.periodic_defragmentation_sweep::<P>() };
return;
}
self.is_allocating = true;
unsafe { self.periodic_defragmentation_sweep::<P>() };
self.is_allocating = false;
}
#[inline(always)]
pub(crate) unsafe fn set_current_segment(&mut self, segment: Option<NonNull<Segment>>) {
if self.current_segment == segment {
return;
}
if let Some(current) = self.current_segment {
unsafe {
let seg_ptr = current.as_ptr();
(*seg_ptr).is_current = false;
let mut mask = (*seg_ptr).page_occupied_mask;
while mask != 0 {
let i = mask.trailing_zeros() as usize;
mask &= mask - 1;
if i > 0 && (*seg_ptr).pages[i].alloc_count == 0 {
(*seg_ptr).page_occupied_mask &= !(1 << i);
}
}
}
}
if let Some(next) = segment {
unsafe {
(*next.as_ptr()).is_current = true;
}
}
self.current_segment = segment;
}
}
impl<B: HasSegmentPool> Drop for ThreadAllocator<B> {
fn drop(&mut self) {
self.reclaim_owned_segments();
if self.cross_thread_reclaimed != 0 {
CROSS_THREAD_RECLAIMED_BLOCKS.fetch_add(self.cross_thread_reclaimed, Ordering::Relaxed);
self.cross_thread_reclaimed = 0;
}
}
}
unsafe impl<B: HasSegmentPool> Send for ThreadAllocator<B> {}
#[cfg(test)]
pub(crate) static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
pub(crate) mod page;
pub(crate) mod routing;
pub(crate) mod segment;
mod stats;
#[cfg(test)]
mod tests;