use crate::alloc::small_path_class;
use crate::free_helpers::resolve_owner_slot;
use crate::usable_size;
use crate::validation::{init_bytes, poison_bytes};
use crate::{
LocalAllocatorSelector, ThreadAllocator, initialize_allocated_bytes, poison_freed_bytes,
thread_alloc_layout, thread_free,
};
use core::alloc::Layout;
use core::ptr::NonNull;
use mnemosyne_arena::HasSegmentPool;
use mnemosyne_core::constants::{MAX_SMALL_ALLOC_SIZE, MIN_BLOCK_SIZE};
use mnemosyne_core::policy::AllocPolicy;
use mnemosyne_core::size_class::round_up_size;
use mnemosyne_core::types::Segment;
use mnemosyne_core::types::{Block, locate_segment};
#[inline(always)]
unsafe fn realloc_delta_init(
ptr: *mut u8,
old_size: usize,
new_size: usize,
zero_init: bool,
poison: bool,
alloc_byte: u8,
free_byte: u8,
) {
if new_size > old_size {
let delta_ptr = unsafe { ptr.add(old_size) };
let delta = new_size - old_size;
unsafe { init_bytes(delta_ptr, delta, zero_init, poison, alloc_byte) };
} else if new_size < old_size {
let tail_ptr = unsafe { ptr.add(new_size) };
let truncated = old_size - new_size;
unsafe { poison_bytes(tail_ptr, truncated, poison, free_byte) };
}
}
#[inline(always)]
pub fn small_realloc_fits_existing_class(layout: Layout, new_size: usize) -> bool {
if layout.align() > MIN_BLOCK_SIZE {
return false;
}
let old_adjusted_size = core::cmp::max(layout.size(), layout.align());
match round_up_size(old_adjusted_size) {
Some(block_stride) => new_size <= block_stride,
None => false,
}
}
#[inline]
unsafe fn realloc_can_reuse(ptr: *mut u8, layout: Layout, new_size: usize) -> bool {
let is_small = layout.size() <= MAX_SMALL_ALLOC_SIZE && layout.align() <= MIN_BLOCK_SIZE;
if new_size <= layout.size() {
if is_small {
return new_size >= layout.size() / 2;
}
let new_adjusted = core::cmp::max(new_size, layout.align());
if new_size >= layout.size() / 2 {
return true;
}
if new_adjusted > MAX_SMALL_ALLOC_SIZE || layout.align() > MIN_BLOCK_SIZE {
let current_usable = unsafe { usable_size(ptr) };
let page_size = mnemosyne_core::constants::PAGE_SIZE;
let new_page_rounded = (new_adjusted + page_size - 1) & !(page_size - 1);
return new_page_rounded >= current_usable;
}
false
} else {
if is_small {
small_realloc_fits_existing_class(layout, new_size)
} else {
let current_usable = unsafe { usable_size(ptr) };
new_size <= current_usable
}
}
}
#[inline]
pub unsafe fn thread_realloc<
P: AllocPolicy + crate::tls_slot::PolicySlotSelection<B>,
B: HasSegmentPool + LocalAllocatorSelector<B>,
>(
ptr: *mut u8,
layout: Layout,
new_size: usize,
) -> *mut u8 {
if !ptr.is_null() && new_size != 0 {
let can_reuse = unsafe { realloc_can_reuse(ptr, layout, new_size) };
if can_reuse {
unsafe {
realloc_delta_init(
ptr,
layout.size(),
new_size,
P::ZERO_INITIALIZE,
P::ENABLE_POISONING,
P::POISON_ALLOC_BYTE,
P::POISON_FREE_BYTE,
)
};
return ptr;
}
} else {
if ptr.is_null() {
if new_size == 0 {
return core::ptr::null_mut();
}
return unsafe { thread_alloc_layout::<P, B>(new_size, layout.align()) };
}
unsafe { thread_free::<P, B>(ptr) };
return core::ptr::null_mut();
}
let new_adjusted = core::cmp::max(new_size, layout.align());
let new_class = small_path_class(new_size, layout.align());
let (segment, page_index) = unsafe { locate_segment(ptr) };
let page = unsafe { &raw mut (*segment).pages[page_index] };
let is_old_small = page_index > 0 && unsafe { (*page).block_size > 0 };
let mut new_ptr = core::ptr::null_mut();
let mut local_free_done = false;
if is_old_small && let Some(class) = new_class {
let slot_ptr = B::get_allocator_ptr_raw_for_policy::<P>();
let owner = unsafe { Segment::owner(segment) };
let (is_owner, owner_slot) = unsafe { resolve_owner_slot(segment, owner, slot_ptr) };
if is_owner && !owner_slot.is_null() {
if !unsafe { crate::tls_slot::LocalAllocatorSlot::<B>::is_allocating(owner_slot) } {
let alloc = unsafe { &mut *(owner_slot as *mut ThreadAllocator<B>) };
unsafe {
crate::tls_slot::LocalAllocatorSlot::<B>::set_allocating(owner_slot, true)
};
let encrypted = unsafe { Segment::free_list_encrypted(segment) };
let allocated = if encrypted {
unsafe { alloc.alloc_class::<mnemosyne_core::policy::HardenedPolicy>(class) }
} else {
unsafe { alloc.alloc_class::<P>(class) }
};
new_ptr = allocated;
if !new_ptr.is_null() {
crate::bin_stats::record_alloc_with_size(class, new_adjusted);
unsafe {
initialize_allocated_bytes::<P>(new_ptr, new_adjusted);
core::ptr::copy_nonoverlapping(
ptr,
new_ptr,
core::cmp::min(layout.size(), new_size),
);
let cookie = Segment::cookie_for_dynamic(segment, encrypted, page_index);
let page_ref = &mut *page;
if P::ENABLE_POISONING {
poison_freed_bytes::<P>(ptr, page_ref.block_size as usize);
}
let block = ptr as *mut Block;
let page_free = page_ref.free;
let page_alloc_count = page_ref.alloc_count as usize;
let randomized = (P::RANDOMIZE_ALLOCATION && encrypted)
|| page_ref.secondary_free.is_some();
if page_ref.alloc_count == 0 {
std::process::abort();
}
if Some(NonNull::new_unchecked(block)) == page_free
|| (randomized
&& Some(NonNull::new_unchecked(block)) == page_ref.secondary_free)
{
std::process::abort();
}
if page_free.is_some()
&& (page_alloc_count != 1 || alloc.is_current_segment(segment))
{
crate::free_helpers::commit_in_place_free(
block,
page_ref,
page_free,
cookie,
encrypted,
page_alloc_count,
randomized,
);
} else {
let _became_empty = if encrypted {
crate::do_local_free_internal_policy::<
mnemosyne_core::policy::HardenedPolicy,
B,
>(
alloc, block, page_ref, segment, page_index
)
} else {
crate::do_local_free_internal_policy::<P, B>(
alloc, block, page_ref, segment, page_index,
)
};
}
}
local_free_done = true;
}
unsafe {
crate::tls_slot::LocalAllocatorSlot::<B>::set_allocating(owner_slot, false)
};
}
}
}
if new_ptr.is_null() {
new_ptr = unsafe { thread_alloc_layout::<P, B>(new_size, layout.align()) };
if new_ptr.is_null() {
return core::ptr::null_mut();
}
}
if !local_free_done {
unsafe {
core::ptr::copy_nonoverlapping(ptr, new_ptr, core::cmp::min(layout.size(), new_size));
thread_free::<P, B>(ptr);
}
}
new_ptr
}