use crate::ThreadAllocator;
use crate::free_helpers::is_sole_active_page;
use crate::local_alloc::page::{move_page_raw, push_page_front_raw, unlink_page_from_list_raw};
use core::ptr::NonNull;
use mnemosyne_arena::HasSegmentPool;
use mnemosyne_core::types::{Block, Page, Segment};
#[inline(always)]
pub unsafe fn do_local_free_internal<B: HasSegmentPool>(
alloc: &mut ThreadAllocator<B>,
block: *mut Block,
page: *mut Page,
segment: *mut Segment,
page_index: usize,
) -> bool {
unsafe {
do_local_free_internal_policy::<mnemosyne_core::policy::StandardPolicy, B>(
alloc, block, page, segment, page_index,
)
}
}
#[inline(always)]
pub unsafe fn do_local_free_internal_policy<
P: mnemosyne_core::policy::AllocPolicy,
B: HasSegmentPool,
>(
alloc: &mut ThreadAllocator<B>,
block: *mut Block,
page: *mut Page,
segment: *mut Segment,
page_index: usize,
) -> bool {
unsafe {
do_local_free_internal_raw::<B>(
alloc,
block,
page,
segment,
page_index,
P::ENABLE_FREE_LIST_ENCRYPTION,
P::DELAY_PAGE_WAKE,
P::WAKE_DENOMINATOR,
)
}
}
#[inline(always)]
#[expect(
clippy::too_many_arguments,
reason = "the non-generic free path keeps policy inputs explicit for monomorphization"
)]
unsafe fn do_local_free_internal_raw<B: HasSegmentPool>(
alloc: &mut ThreadAllocator<B>,
block: *mut Block,
page: *mut Page,
segment: *mut Segment,
page_index: usize,
enable_encryption: bool,
delay_wake: bool,
wake_denominator: u16,
) -> bool {
if unsafe { (*page).alloc_count } == 0 {
std::process::abort();
}
if Some(unsafe { NonNull::new_unchecked(block) }) == unsafe { (*page).free } {
std::process::abort();
}
let was_full = unsafe { (*page).list_state } == 2;
let encrypted = unsafe { Segment::free_list_encrypted(segment) };
let cookie = unsafe { Segment::cookie_for_dynamic(segment, encrypted, page_index) };
if enable_encryption {
if unsafe { mnemosyne_core::types::Block::check_double_free(block, cookie) } {
std::process::abort();
}
unsafe { mnemosyne_core::types::Block::write_free_canary(block, cookie) };
}
unsafe {
(*block).set_next_dynamic((*page).free, encrypted, cookie);
}
unsafe { (*page).free = Some(NonNull::new_unchecked(block)) };
let becomes_empty = unsafe {
let count = (*page).alloc_count - 1;
(*page).alloc_count = count;
if count == 0 && !Segment::is_current(segment) {
(*segment).page_occupied_mask &= !(1 << page_index);
}
count == 0
};
let class = unsafe { (*page).size_class } as usize;
let page_ptr = unsafe { NonNull::new_unchecked(page) };
if was_full {
if becomes_empty && !alloc.is_current_segment(segment) {
unsafe {
unlink_page_from_list_raw(page_ptr, alloc.full_pages.get_unchecked_mut(class));
push_page_front_raw(page_ptr, &mut alloc.empty_pages, 3);
}
} else {
let should_wake = !delay_wake
|| unsafe {
Page::should_reactivate_after_free(
class,
(*page).alloc_count as usize,
wake_denominator as usize,
)
};
if should_wake {
unsafe {
move_page_raw(
page_ptr,
alloc.full_pages.get_unchecked_mut(class),
alloc.active_pages.get_unchecked_mut(class),
1,
);
}
}
}
} else if becomes_empty && !alloc.is_current_segment(segment) {
let is_only_active =
unsafe { is_sole_active_page(*alloc.active_pages.get_unchecked(class), page) };
if !is_only_active {
unsafe {
unlink_page_from_list_raw(page_ptr, alloc.active_pages.get_unchecked_mut(class));
push_page_front_raw(page_ptr, &mut alloc.empty_pages, 3);
}
}
}
becomes_empty
}