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::{OccupiedPageBits, Page, Segment};
pub use stats::{SizeClassOccupancy, ThreadAllocatorStats};
pub(crate) mod freelist_keys;
pub(crate) use freelist_keys::{get_process_key, get_tls_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 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,
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(&mut self, is_allocating: bool) {
self.defrag_counter += 1;
if self.defrag_counter >= 64 {
unsafe { self.run_periodic_defragmentation(is_allocating) };
}
}
#[cold]
#[inline(never)]
unsafe fn run_periodic_defragmentation(&mut self, is_allocating: bool) {
self.defrag_counter = 0;
if is_allocating {
unsafe { self.periodic_defragmentation_sweep() };
return;
}
unsafe { self.periodic_defragmentation_sweep() };
}
#[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();
Segment::set_current(seg_ptr, false);
for i in OccupiedPageBits::new((*seg_ptr).page_occupied_mask) {
if (*seg_ptr).pages[i].alloc_count == 0 {
(*seg_ptr).page_occupied_mask &= !(1 << i);
}
}
}
}
if let Some(next) = segment {
unsafe {
Segment::set_current(next.as_ptr(), 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) struct TestLock {
inner: std::sync::Mutex<()>,
}
#[cfg(test)]
impl TestLock {
pub(crate) const fn new() -> Self {
Self {
inner: std::sync::Mutex::new(()),
}
}
pub(crate) fn lock(&self) -> std::sync::LockResult<TestLockGuard<'_>> {
match self.inner.lock() {
Ok(guard) => Ok(TestLockGuard { guard }),
Err(error) => Err(std::sync::PoisonError::new(TestLockGuard {
guard: error.into_inner(),
})),
}
}
}
#[cfg(test)]
pub(crate) struct TestLockGuard<'lock> {
guard: std::sync::MutexGuard<'lock, ()>,
}
#[cfg(test)]
impl Drop for TestLockGuard<'_> {
fn drop(&mut self) {
let _ = &self.guard;
#[cfg(miri)]
unsafe {
crate::miri_cleanup_pools::<mnemosyne_backend::DefaultBackend>();
crate::miri_cleanup_pools::<mnemosyne_backend::MemoryBackendWrapper>();
}
}
}
#[cfg(test)]
pub(crate) static TEST_LOCK: TestLock = TestLock::new();
pub(crate) mod page;
pub(crate) mod routing;
pub(crate) mod segment;
mod stats;
#[cfg(test)]
pub(crate) mod tests;