use crate::local_alloc::page::{
move_page_between_lists_branded, push_page_front, unlink_page_from_list, with_page_list_token,
};
use crate::per_cpu;
use crate::{LocalAllocatorSelector, ThreadAllocator, poison_freed_bytes};
use core::ptr::NonNull;
use mnemosyne_arena::{HasSegmentPool, deallocate_large_or_huge};
use mnemosyne_core::constants::PAGE_SIZE;
use mnemosyne_core::policy::AllocPolicy;
use mnemosyne_core::types::{Block, Page, Segment, locate_page, locate_segment};
#[inline(always)]
pub unsafe fn thread_free<P: AllocPolicy, B: HasSegmentPool + LocalAllocatorSelector<B>>(
ptr: *mut u8,
) {
unsafe { thread_free_classified::<P, B, false>(ptr) }
}
#[inline(always)]
pub unsafe fn thread_free_layout<P: AllocPolicy, B: HasSegmentPool + LocalAllocatorSelector<B>>(
ptr: *mut u8,
size: usize,
align: usize,
) {
if size != 0 && crate::alloc::small_path_class(size, align).is_some() {
unsafe { thread_free_classified::<P, B, true>(ptr) };
} else {
unsafe { thread_free_classified::<P, B, false>(ptr) };
}
}
#[inline(always)]
unsafe fn thread_free_classified<
P: AllocPolicy,
B: HasSegmentPool + LocalAllocatorSelector<B>,
const LAYOUT_PROVES_SMALL: bool,
>(
ptr: *mut u8,
) {
if ptr.is_null() {
return;
}
let ptr_val = ptr as usize;
let (segment, page_index) = unsafe { locate_segment(ptr) };
let page_ptr = unsafe { locate_page(segment, page_index) };
if mnemosyne_prof::is_active() {
unsafe { record_free_profile(ptr, page_ptr, page_index) };
}
if !LAYOUT_PROVES_SMALL && unsafe { (*page_ptr).block_size } == 0 {
let segment = unsafe { *((ptr as *mut *mut Segment).sub(1)) };
if P::ENABLE_POISONING {
let size = unsafe { (*segment).pages[0].alloc_count };
let size = if size > 0 {
size
} else {
unsafe { (*segment).huge_mapping_suffix_from(ptr) }
};
unsafe { poison_freed_bytes::<P>(ptr, size) };
}
let _released = unsafe { deallocate_large_or_huge::<B>(ptr, segment) };
#[cfg(feature = "dealloc-probe")]
crate::dealloc_counters::record(crate::dealloc_counters::DeallocPath::HugeClassifier);
return;
}
debug_assert_eq!(
(ptr_val & (PAGE_SIZE - 1)) % unsafe { (*page_ptr).block_size },
0,
"small free ptr must be aligned to the page's block stride"
);
if P::ENABLE_POISONING {
unsafe { poison_freed_bytes::<P>(ptr, (*page_ptr).block_size) };
}
let block = ptr as *mut Block;
let owner = unsafe { (*segment).owner };
#[cfg(all(windows, target_arch = "x86_64", not(miri)))]
let (is_owner, owner_allocator) = {
let tid = mnemosyne_core::types::current_thread_id();
if owner.matches_thread_id(tid) {
(true, unsafe { (*segment).owner_allocator })
} else {
(false, core::ptr::null_mut())
}
};
#[cfg(any(not(all(windows, target_arch = "x86_64")), miri))]
let (is_owner, owner_allocator) = {
let standard_allocator = B::get_allocator_ptr_raw_for_encryption::<false>();
let encrypted_allocator = B::get_allocator_ptr_raw_for_encryption::<true>();
if owner.matches(standard_allocator) {
(true, standard_allocator)
} else if owner.matches(encrypted_allocator) {
(true, encrypted_allocator)
} else {
(false, core::ptr::null_mut())
}
};
if is_owner && !owner_allocator.is_null() {
let page_alloc_count = unsafe { (*page_ptr).alloc_count };
if page_alloc_count == 0 {
std::process::abort();
}
if Some(unsafe { NonNull::new_unchecked(block) }) == unsafe { (*page_ptr).free } {
std::process::abort();
}
let alloc = unsafe { &mut *(owner_allocator as *mut ThreadAllocator<B>) };
let page_free = unsafe { (*page_ptr).free };
let encrypted = unsafe { (*segment).free_list_encrypted };
let cookie = unsafe { (*segment).cookie_for_dynamic(encrypted, page_index) };
if unsafe { (*page_ptr).list_state } != 2 {
if page_alloc_count > 1 || alloc.is_current_segment(segment) {
unsafe {
commit_in_place_free(
block,
page_ptr,
page_free,
cookie,
encrypted,
page_alloc_count,
)
};
#[cfg(feature = "dealloc-probe")]
crate::dealloc_counters::record(crate::dealloc_counters::DeallocPath::InPlaceSmall);
return;
} else if !alloc.is_allocating {
alloc.is_allocating = true;
let _became_empty = unsafe {
do_local_free_internal::<B>(alloc, block, page_ptr, segment, page_index)
};
unsafe { alloc.record_defrag_operation::<P>() };
alloc.is_allocating = false;
#[cfg(feature = "dealloc-probe")]
crate::dealloc_counters::record(
crate::dealloc_counters::DeallocPath::ActiveFreeLastBlock,
);
return;
}
} else if !alloc.is_allocating {
let _became_empty =
unsafe { do_local_free_internal::<B>(alloc, block, page_ptr, segment, page_index) };
#[cfg(feature = "dealloc-probe")]
crate::dealloc_counters::record(crate::dealloc_counters::DeallocPath::FullToActive);
return;
}
}
unsafe { thread_free_cold::<B>(ptr, page_ptr, block) };
}
#[cold]
#[inline(never)]
unsafe fn record_free_profile(ptr: *mut u8, page: *const Page, page_index: usize) {
let block_size = unsafe { (*page).block_size };
let size = if page_index == 0 || block_size == 0 {
unsafe { crate::usable_size::huge_allocation_size(ptr) }
} else {
block_size
};
mnemosyne_prof::on_free(ptr, size);
}
#[cold]
#[inline(never)]
unsafe fn thread_free_cold<B: HasSegmentPool + LocalAllocatorSelector<B>>(
ptr: *mut u8,
page: *mut Page,
block: *mut Block,
) {
let encrypted = unsafe { (*(*page).parent_segment()).free_list_encrypted };
if B::ENABLE_CPU_CACHE
&& per_cpu::try_free_cpu(ptr, unsafe { (*page).size_class } as usize, encrypted)
{
#[cfg(feature = "dealloc-probe")]
crate::dealloc_counters::record(crate::dealloc_counters::DeallocPath::ColdOrRecursing);
return;
}
unsafe {
(*page)
.thread_free
.push_dynamic(NonNull::new_unchecked(block), encrypted);
}
#[cfg(feature = "dealloc-probe")]
crate::dealloc_counters::record(crate::dealloc_counters::DeallocPath::ColdOrRecursing);
}
#[inline(always)]
pub(crate) unsafe fn is_sole_active_page(
active_head: Option<NonNull<Page>>,
page: *const Page,
) -> bool {
active_head.is_some_and(|head| {
core::ptr::eq(head.as_ptr(), page) && unsafe { (*head.as_ptr()).next_page.is_none() }
})
}
#[inline(always)]
pub(crate) unsafe fn commit_in_place_free(
block: *mut Block,
page: *mut Page,
page_free: Option<NonNull<Block>>,
cookie: usize,
encrypted: bool,
page_alloc_count: usize,
) {
unsafe {
(*block).set_next_dynamic(page_free, encrypted, cookie);
(*page).free = Some(NonNull::new_unchecked(block));
(*page).alloc_count = page_alloc_count - 1;
}
}
#[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 {
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 };
let cookie = unsafe { (*segment).cookie_for_dynamic(encrypted, page_index) };
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).page_occupied_mask &= !(1 << page_index);
}
count == 0
};
let class = unsafe { (*page).size_class } as usize;
let page_ptr = unsafe { NonNull::new_unchecked(page) };
with_page_list_token::<B, _>(|mut token| {
let branded_page = unsafe { token.page(page_ptr) };
if was_full {
if becomes_empty && !alloc.is_current_segment(segment) {
unsafe {
unlink_page_from_list(
&mut token,
alloc.full_pages.get_unchecked_mut(class),
branded_page,
);
push_page_front(&mut token, &mut alloc.empty_pages, branded_page, 3);
}
} else {
unsafe {
move_page_between_lists_branded(
&mut token,
alloc.full_pages.get_unchecked_mut(class),
alloc.active_pages.get_unchecked_mut(class),
branded_page,
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(
&mut token,
alloc.active_pages.get_unchecked_mut(class),
branded_page,
);
push_page_front(&mut token, &mut alloc.empty_pages, branded_page, 3);
}
}
}
});
becomes_empty
}