use core::alloc::Layout;
use core::fmt;
use core::ptr::NonNull;
use allocator_api2::alloc::{AllocError, Allocator, Global};
use super::Arena;
use super::chunks::ChunkKind;
use crate::internal::drop_list::{DropEntry as InnerDropEntry, drop_shim_slice};
use crate::internal::local_chunk::LocalChunk;
use crate::internal::shared_chunk::SharedChunk;
use crate::internal::sync::Ordering;
pub(super) const fn compute_worst_case_size(layout: Layout, has_drop: bool) -> usize {
let entry = if has_drop { core::mem::size_of::<InnerDropEntry>() } else { 0 };
layout.size().saturating_add(layout.align()).saturating_add(entry)
}
#[inline]
#[cfg_attr(test, mutants::skip)]
pub(super) const fn worst_case_refill_for(layout: Layout, entry_size: usize) -> usize {
compute_worst_case_size(layout, entry_size != 0)
}
#[inline(always)]
pub(super) unsafe fn u16_truncate_unchecked(value: usize) -> u16 {
let res = u16::try_from(value);
debug_assert!(res.is_ok(), "u16_truncate_unchecked: value {value} exceeds u16::MAX");
unsafe { core::hint::assert_unchecked(res.is_ok()) };
unsafe { res.unwrap_unchecked() }
}
#[inline(always)]
pub(super) unsafe fn value_offset_in_chunk<C: ChunkKind + ?Sized>(chunk: NonNull<C>, value_ptr: NonNull<u8>) -> u16 {
let payload_base_addr = unsafe { C::data_ptr_of(chunk) }.as_ptr() as usize;
let raw_value_offset = (value_ptr.as_ptr() as usize) - payload_base_addr;
unsafe { u16_truncate_unchecked(raw_value_offset) }
}
#[inline]
#[cfg_attr(test, mutants::skip)]
pub(super) const fn bumped_exceeds_chunk(bumped: usize) -> bool {
bumped > crate::internal::constants::MAX_CHUNK_BYTES
}
#[inline]
#[cfg_attr(test, mutants::skip)]
pub(super) const fn size_exceeds_normal_alloc(size: usize, max_normal_alloc: usize) -> bool {
size > max_normal_alloc
}
#[inline]
#[cfg_attr(test, mutants::skip)]
pub(super) const fn current_chunk_evicted(cur_addr: usize, target_addr: usize) -> bool {
cur_addr != target_addr
}
#[inline]
#[cfg_attr(test, mutants::skip)]
pub(super) const fn has_drop_entry(entry_size: usize) -> bool {
entry_size > 0
}
#[inline]
#[cfg_attr(test, mutants::skip)]
pub(super) const fn slow_refill_needed(layout: Layout, entry_size: usize) -> usize {
layout.size() + layout.align().saturating_sub(core::mem::align_of::<usize>()) + entry_size
}
#[inline(never)]
pub(super) fn drop_fn_for_slice<T>() -> Option<unsafe fn(*mut u8, usize)> {
let f: unsafe fn(*mut u8, usize) = drop_shim_slice::<T>;
needs_drop_indirect::<T>().then_some(f)
}
#[inline(never)]
#[cfg_attr(test, mutants::skip)] pub(super) fn needs_drop_indirect<T>() -> bool {
core::mem::needs_drop::<T>()
}
pub(super) struct SliceInitGuard<T> {
pub(super) ptr: *mut T,
pub(super) len: usize,
}
impl<T> Drop for SliceInitGuard<T> {
fn drop(&mut self) {
for i in 0..self.len {
unsafe { core::ptr::drop_in_place(self.ptr.add(i)) };
}
}
}
pub(super) const WRITE_THROUGH_INLINE_ALIGN: usize = 16;
#[inline(always)]
#[expect(
clippy::inline_always,
reason = "must inline so LLVM sees the closure call site directly and can prove whether the closure touches `current_local`; without that proof it forces a reload of every Cell field after the call, undoing the entire fast-path win"
)]
pub(super) unsafe fn write_through_ptr<T, F: FnOnce() -> T>(ptr: *mut T, f: F) {
if const { core::mem::align_of::<T>() <= WRITE_THROUGH_INLINE_ALIGN } {
unsafe { core::ptr::write(ptr, f()) };
} else {
unsafe { write_through_ptr_outlined::<T, F>(ptr, f) };
}
}
#[inline(never)]
#[cold]
pub(super) unsafe fn write_through_ptr_outlined<T, F: FnOnce() -> T>(ptr: *mut T, f: F) {
unsafe { core::ptr::write(ptr, f()) };
}
#[inline(never)]
#[cold]
#[expect(clippy::panic, reason = "panicking allocation entry points panic on alloc failure by design")]
pub fn panic_alloc() -> ! {
panic!("multitude: allocator returned AllocError");
}
#[inline]
pub fn expect_alloc<T>(r: Result<T, AllocError>) -> T {
match r {
Ok(v) => v,
Err(_) => panic_alloc(),
}
}
#[expect(clippy::inline_always, reason = "zero-cost wrapper must inline at call site")]
#[inline(always)]
#[cfg_attr(test, mutants::skip)] pub(crate) const fn check_isize_overflow(total: usize, align: usize) -> Result<(), AllocError> {
let padding = align.saturating_sub(1);
if total > (isize::MAX as usize).saturating_sub(padding) {
Err(AllocError)
} else {
Ok(())
}
}
impl Default for Arena<Global> {
fn default() -> Self {
Self::new()
}
}
impl<A: Allocator + Clone> Drop for Arena<A> {
fn drop(&mut self) {
self.reset();
}
}
impl<A: Allocator + Clone> fmt::Debug for Arena<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut dbg = f.debug_struct("Arena");
#[cfg(feature = "stats")]
dbg.field("stats", &self.stats());
dbg.finish_non_exhaustive()
}
}
#[derive(Clone, Copy)]
pub(super) enum AllocFlavor {
SimpleRef,
Rc,
Box,
}
pub(super) struct ProtectiveHold<'a, A: Allocator + Clone> {
pub(super) arena: &'a Arena<A>,
pub(super) chunk: NonNull<LocalChunk<A>>,
pub(super) flavor: AllocFlavor,
}
impl<A: Allocator + Clone> Drop for ProtectiveHold<'_, A> {
#[cold]
fn drop(&mut self) {
match self.flavor {
AllocFlavor::SimpleRef => {
}
AllocFlavor::Rc | AllocFlavor::Box => {
if self.arena.current_local.chunk.get() == Some(self.chunk) {
let cur = self.arena.current_local.smart_pointers_issued.get();
debug_assert!(
cur > 0,
"ProtectiveHold::drop fired with smart_pointers_issued == 0; the pre-closure bump was either skipped or already undone"
);
self.arena.current_local.smart_pointers_issued.set(cur - 1);
} else {
unsafe { self.arena.release_local_chunk(self.chunk) };
}
}
}
}
}
pub(super) struct SharedArcsIssuedHold<'a, A: Allocator + Clone> {
pub(super) arena: &'a Arena<A>,
pub(super) chunk: NonNull<SharedChunk<A>>,
}
impl<A: Allocator + Clone> Drop for SharedArcsIssuedHold<'_, A> {
#[cold]
fn drop(&mut self) {
if self.arena.current_shared.chunk.get() == Some(self.chunk) {
let cur = self.arena.current_shared.smart_pointers_issued.get();
debug_assert!(
cur > 0,
"SharedArcsIssuedHold::drop fired with smart_pointers_issued == 0; the pre-closure bump was either skipped or already undone"
);
self.arena.current_shared.smart_pointers_issued.set(cur - 1);
} else {
unsafe { SharedChunk::dec_ref(self.chunk) };
}
}
}
#[inline(always)]
#[cfg_attr(test, mutants::skip)]
pub(super) unsafe fn bump_local_drop_count<A: Allocator + Clone>(chunk: NonNull<LocalChunk<A>>) {
let dc = &unsafe { chunk.as_ref() }.drop_count;
dc.set(dc.get() + 1);
}
#[inline(always)]
#[cfg_attr(test, mutants::skip)]
pub(super) unsafe fn bump_shared_drop_count<A: Allocator + Clone>(chunk: NonNull<SharedChunk<A>>) {
unsafe { chunk.as_ref() }.drop_count.fetch_add(1, Ordering::Release);
}
#[inline]
#[cfg(feature = "dst")]
pub(super) fn align_up(value: usize, align: usize) -> usize {
debug_assert!(align.is_power_of_two());
value.saturating_add(align - 1) & !(align - 1)
}
#[derive(Clone, Copy)]
pub(super) struct BumpFit {
pub(super) fits: bool,
pub(super) aligned_ptr: NonNull<u8>,
pub(super) end_ptr: NonNull<u8>,
pub(super) new_drop_back_ptr: NonNull<u8>,
}
#[inline(always)]
#[cfg_attr(test, mutants::skip)] pub(super) fn try_bump_fit(data_ptr: NonNull<u8>, drop_back_ptr: NonNull<u8>, align: usize, bumped: usize, entry_size: usize) -> BumpFit {
debug_assert!(align.is_power_of_two());
debug_assert!(align >= 1);
debug_assert!(bumped >= 1, "callers must pass `bumped >= 1` (use `layout.size().max(1)`)");
let data_addr = data_ptr.as_ptr() as usize;
let drop_back_addr = drop_back_ptr.as_ptr() as usize;
unsafe {
core::hint::assert_unchecked(data_addr > 0);
core::hint::assert_unchecked(isize::try_from(data_addr).is_ok());
core::hint::assert_unchecked(isize::try_from(bumped).is_ok());
core::hint::assert_unchecked(isize::try_from(entry_size).is_ok());
}
let aligned = (data_addr + (align - 1)) & !(align - 1);
let end = aligned + bumped + entry_size;
if end > drop_back_addr {
return BumpFit {
fits: false,
aligned_ptr: NonNull::dangling(),
end_ptr: NonNull::dangling(),
new_drop_back_ptr: NonNull::dangling(),
};
}
let aligned_offset = aligned - data_addr;
let bumped_offset = aligned_offset + bumped;
unsafe {
let aligned_raw = data_ptr.as_ptr().byte_add(aligned_offset);
let end_raw = data_ptr.as_ptr().byte_add(bumped_offset);
let new_drop_back_raw = drop_back_ptr.as_ptr().byte_sub(entry_size);
BumpFit {
fits: true,
aligned_ptr: NonNull::new_unchecked(aligned_raw),
end_ptr: NonNull::new_unchecked(end_raw),
new_drop_back_ptr: NonNull::new_unchecked(new_drop_back_raw),
}
}
}
#[inline]
pub(super) fn align_offset(value: usize, align: usize) -> Option<usize> {
debug_assert!(align.is_power_of_two());
let aligned = value.checked_add(align - 1)? & !(align - 1);
Some(aligned - value)
}
#[inline]
pub(super) unsafe fn aligned_payload_offset(data_addr: usize, align: usize, size: usize, cap: usize, reserved_tail: usize) -> usize {
unsafe {
let aligned = align_offset(data_addr, align).unwrap_unchecked();
let end = aligned.checked_add(size).unwrap_unchecked();
core::hint::assert_unchecked(end <= cap.saturating_sub(reserved_tail));
aligned
}
}
impl<A: Allocator + Clone> Arena<A> {
#[inline]
#[cfg_attr(
not(feature = "stats"),
expect(
clippy::missing_const_for_fn,
reason = "non-const under stats feature; bump_stat! invokes Cell::set"
)
)]
#[cfg_attr(
not(feature = "stats"),
expect(clippy::unused_self, reason = "no-op stub when `stats` feature is disabled")
)]
pub(crate) fn charge_alloc_stats(
&self,
#[cfg_attr(
not(feature = "stats"),
expect(unused_variables, reason = "no-op stub when `stats` feature is disabled")
)]
bytes: usize,
) {
#[cfg(feature = "stats")]
crate::arena_stats::StatsStorage::add(&self.provider.stats.total_bytes_allocated, bytes as u64);
}
#[inline]
#[cfg_attr(
not(feature = "stats"),
expect(
clippy::missing_const_for_fn,
reason = "non-const under stats feature; bump_stat! invokes Cell::set"
)
)]
#[cfg_attr(
not(feature = "stats"),
expect(clippy::unused_self, reason = "no-op stub when `stats` feature is disabled")
)]
pub(crate) fn bump_relocation(&self) {
#[cfg(feature = "stats")]
crate::arena_stats::StatsStorage::add(&self.provider.stats.relocations, 1);
}
}