#![expect(clippy::inline_always, reason = "hot bump-allocator helpers must inline into their callers")]
use alloc::sync::Arc as StdArc;
use core::cell::Cell;
use core::marker::PhantomData;
use core::ptr::NonNull;
use allocator_api2::alloc::{AllocError, Allocator, Global};
use crate::arena_builder::ArenaBuilder;
#[cfg(feature = "stats")]
use crate::arena_stats::ArenaStats;
use crate::internal::chunk_provider::ChunkProvider;
use crate::internal::local_chunk::LocalChunk;
use crate::internal::shared_chunk::SharedChunk;
use crate::internal::sync::Ordering;
macro_rules! try_alloc_prefixed {
(
self = $self:ident,
src_ptr = $src_ptr:expr,
len = $len:expr,
payload_bytes = $payload_bytes:expr,
elem_ty = $elem_ty:ty,
slot = $slot:ident,
refill = $refill:ident,
accounting = $accounting:block,
) => {{
let len = $len;
let prefix = core::mem::size_of::<usize>();
let total = prefix.checked_add($payload_bytes).ok_or(::allocator_api2::alloc::AllocError)?;
let align = core::mem::align_of::<$elem_ty>();
$crate::arena::check_isize_overflow(total, align)?;
unsafe {
core::hint::assert_unchecked(isize::try_from(total).is_ok());
}
loop {
let data_ptr = $self.$slot.data_ptr.get();
let drop_back_ptr = $self.$slot.drop_back.get();
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(isize::try_from(data_addr).is_ok());
}
let data_aligned_addr = (data_addr + prefix + (align - 1)) & !(align - 1);
let prefix_addr = data_aligned_addr - prefix;
let end_addr = data_aligned_addr + $payload_bytes;
if end_addr <= drop_back_addr {
let prefix_offset = prefix_addr - data_addr;
let bumped_offset = end_addr - data_addr;
let (prefix_ptr_nn, end_ptr) = unsafe {
(data_ptr.byte_add(prefix_offset), data_ptr.byte_add(bumped_offset))
};
#[allow(
clippy::cast_ptr_alignment,
reason = "prefix slot is accessed via write_unaligned below; alignment of the cast target is not relied on"
)]
let prefix_ptr: *mut usize = prefix_ptr_nn.as_ptr().cast::<usize>();
#[allow(
clippy::cast_ptr_alignment,
reason = "data is bump-aligned to align_of::<$elem_ty>() above; the cast is well-aligned at runtime"
)]
let elems_ptr = unsafe { prefix_ptr_nn.as_ptr().add(prefix).cast::<$elem_ty>() };
$self.$slot.data_ptr.set(end_ptr);
let prefix_slot =
unsafe { $crate::internal::slot::UninitSlot::<usize>::from_raw(prefix_ptr) };
prefix_slot.write_unaligned(len);
unsafe { core::ptr::copy_nonoverlapping($src_ptr, elems_ptr, len) };
$accounting
$self.charge_alloc_stats(end_addr - prefix_addr);
return Ok(unsafe { ::core::ptr::NonNull::new_unchecked(elems_ptr) });
}
$self.$refill(total + align)?;
}
}};
}
mod alloc_growable;
mod alloc_slice_arc;
mod alloc_slice_box;
mod alloc_slice_rc;
mod alloc_slice_ref;
mod alloc_str;
mod alloc_uninit;
#[cfg(feature = "dst")]
mod alloc_unsized;
#[cfg(feature = "utf16")]
mod alloc_utf16;
mod alloc_value;
mod chunks;
mod inner_slice;
mod inner_value;
mod internals;
mod primitives;
mod refill;
#[cfg(test)]
mod tests;
use chunks::{CurrentLocalChunk, CurrentSharedChunk, OversizedLocalGuard, OversizedSharedGuard};
#[cfg(test)]
use internals::align_offset;
#[cfg(feature = "dst")]
use internals::align_up;
pub(crate) use internals::check_isize_overflow;
use internals::{
AllocFlavor, ProtectiveHold, SharedArcsIssuedHold, SliceInitGuard, aligned_payload_offset, bump_local_drop_count,
bump_shared_drop_count, bumped_exceeds_chunk, compute_worst_case_size, current_chunk_evicted, drop_fn_for_slice, has_drop_entry,
size_exceeds_normal_alloc, slow_refill_needed, try_bump_fit, u16_truncate_unchecked, value_offset_in_chunk, worst_case_refill_for,
write_through_ptr,
};
pub use internals::{expect_alloc, panic_alloc};
#[repr(C)]
pub struct Arena<A: Allocator + Clone = Global> {
current_local: CurrentLocalChunk<A>,
current_local_pinned: Cell<bool>,
current_shared: CurrentSharedChunk<A>,
pinned_local: Cell<Option<NonNull<LocalChunk<A>>>>,
provider: StdArc<ChunkProvider<A>>,
_not_thread_safe: PhantomData<*mut ()>,
}
impl Arena<Global> {
#[must_use]
#[inline]
pub fn new() -> Self {
Self::new_in(Global)
}
#[must_use]
#[inline]
#[cfg_attr(test, mutants::skip)] pub fn builder() -> ArenaBuilder<Global> {
ArenaBuilder::new()
}
}
impl<A: Allocator + Clone> Arena<A> {
#[must_use]
#[inline]
pub fn builder_in(allocator: A) -> ArenaBuilder<A> {
ArenaBuilder::new_in(allocator)
}
#[must_use]
#[inline]
pub fn new_in(allocator: A) -> Self
where
A: 'static,
{
Self::from_config(allocator, crate::internal::constants::MAX_NORMAL_ALLOC, None, 0, 0)
}
pub(crate) fn from_config(
allocator: A,
max_normal_alloc: usize,
byte_budget: Option<usize>,
initial_local_class: u8,
initial_shared_class: u8,
) -> Self
where
A: 'static,
{
let provider = ChunkProvider::new(allocator, max_normal_alloc, byte_budget, initial_local_class, initial_shared_class);
Self {
provider,
current_local: CurrentLocalChunk::<A>::default(),
current_local_pinned: Cell::new(false),
pinned_local: Cell::new(None),
current_shared: CurrentSharedChunk::<A>::default(),
_not_thread_safe: PhantomData,
}
}
#[must_use]
#[inline]
pub fn allocator(&self) -> &A {
&self.provider.allocator
}
pub(crate) fn preallocate_one_local(&self) -> Result<(), AllocError> {
self.provider.preallocate_local()
}
pub(crate) fn preallocate_one_shared(&self) -> Result<(), AllocError> {
self.provider.preallocate_shared()
}
#[cfg(feature = "stats")]
#[cfg_attr(docsrs, doc(cfg(feature = "stats")))]
#[must_use]
#[inline]
pub fn stats(&self) -> ArenaStats {
self.provider.stats_snapshot()
}
#[cold]
pub fn reset(&mut self) {
loop {
let mut did_work = false;
let mut head = self.pinned_local.replace(None);
while let Some(chunk) = head {
did_work = true;
let next = unsafe { (*chunk.as_ptr()).next.replace(None) };
unsafe { self.release_local_chunk(chunk) };
head = next;
}
if let Some(chunk) = self.current_local.chunk.replace(None) {
did_work = true;
#[cfg(feature = "stats")]
{
let data_ptr_addr = self.current_local.data_ptr.get().as_ptr() as usize;
let drop_back_addr = self.current_local.drop_back.get().as_ptr() as usize;
let wasted = drop_back_addr.saturating_sub(data_ptr_addr);
crate::arena_stats::StatsStorage::add(&self.provider.stats.wasted_tail_bytes, wasted as u64);
}
let mirror_dc = unsafe { self.current_local.drop_count(chunk) };
let chunk_dc = unsafe { (*chunk.as_ptr()).drop_count.get() };
debug_assert_eq!(mirror_dc, chunk_dc, "drop_count mirror diverged from on-chunk counter");
let _ = chunk_dc;
unsafe { (*chunk.as_ptr()).drop_count.set(mirror_dc) };
let rcs_issued = self.current_local.smart_pointers_issued.replace(0);
self.current_local_pinned.set(false);
self.current_local.data_ptr.set(NonNull::dangling());
self.current_local.drop_back.set(NonNull::dangling());
unsafe { LocalChunk::reconcile_swap_out(chunk, rcs_issued, false) };
}
if let Some(chunk) = self.current_shared.chunk.replace(None) {
did_work = true;
#[cfg(feature = "stats")]
{
let data_ptr_addr = self.current_shared.data_ptr.get().as_ptr() as usize;
let drop_back_addr = self.current_shared.drop_back.get().as_ptr() as usize;
let wasted = drop_back_addr.saturating_sub(data_ptr_addr);
crate::arena_stats::StatsStorage::add(&self.provider.stats.wasted_tail_bytes, wasted as u64);
}
let dc = unsafe { self.current_shared.drop_count(chunk) };
unsafe { (*chunk.as_ptr()).drop_count.store(dc, Ordering::Release) };
let arcs_issued = self.current_shared.smart_pointers_issued.replace(0);
self.current_shared.data_ptr.set(NonNull::dangling());
self.current_shared.drop_back.set(NonNull::dangling());
unsafe { SharedChunk::reconcile_swap_out(chunk, arcs_issued) };
}
if did_work {
continue;
}
break;
}
}
#[cfg(feature = "zerocopy")]
#[cfg_attr(docsrs, doc(cfg(feature = "zerocopy")))]
#[inline]
#[must_use]
pub const fn zerocopy(&self) -> crate::zerocopy::ZerocopyView<'_, A> {
crate::zerocopy::ZerocopyView::new(self)
}
#[cfg(feature = "bytemuck")]
#[cfg_attr(docsrs, doc(cfg(feature = "bytemuck")))]
#[inline]
#[must_use]
pub const fn bytemuck(&self) -> crate::bytemuck::BytemuckView<'_, A> {
crate::bytemuck::BytemuckView::new(self)
}
}