use std::{
alloc::{self, Layout},
cell::Cell,
ptr::{self, NonNull},
};
use super::bumpalo_alloc::AllocErr;
#[cfg(all(feature = "track_allocations", not(feature = "disable_track_allocations")))]
use crate::tracking::AllocationStats;
use super::{
Arena, CHUNK_ALIGN, CHUNK_FOOTER_SIZE, ChunkFooter, EMPTY_CHUNK,
utils::{layout_from_size_align, oom, round_up_to},
};
const TYPICAL_PAGE_SIZE: usize = 0x1000;
#[cfg(all(feature = "fixed_size", target_pointer_width = "64", target_endian = "little"))]
const _: () = {
use crate::generated::fixed_size_constants::CHUNK_FOOTER_SIZE as EXPECTED_CHUNK_FOOTER_SIZE;
assert!(CHUNK_FOOTER_SIZE == EXPECTED_CHUNK_FOOTER_SIZE);
};
const MALLOC_OVERHEAD: usize = 16;
const OVERHEAD: usize = match round_up_to(MALLOC_OVERHEAD + CHUNK_FOOTER_SIZE, CHUNK_ALIGN) {
Some(x) => x,
None => panic!(),
};
const FIRST_ALLOCATION_GOAL: usize = 16 * 1024;
pub const DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER: usize = FIRST_ALLOCATION_GOAL - OVERHEAD;
#[derive(Debug, Clone, Copy)]
pub struct NewChunkMemoryDetails {
new_size_without_footer: usize,
align: usize,
size: usize,
}
impl Arena<1> {
pub fn new() -> Self {
Self::with_capacity(0)
}
#[expect(clippy::missing_errors_doc, reason = "`try_with_capacity(0)` always returns `Ok`")]
pub fn try_new() -> Result<Self, AllocErr> {
Arena::try_with_capacity(0)
}
pub fn with_capacity(capacity: usize) -> Self {
Self::try_with_capacity(capacity).unwrap_or_else(|_| oom())
}
pub fn try_with_capacity(capacity: usize) -> Result<Self, AllocErr> {
Self::try_with_min_align_and_capacity(capacity)
}
}
impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
pub fn with_min_align() -> Self {
Self::new_impl(EMPTY_CHUNK.get())
}
pub fn with_min_align_and_capacity(capacity: usize) -> Self {
Self::try_with_min_align_and_capacity(capacity).unwrap_or_else(|_| oom())
}
pub fn try_with_min_align_and_capacity(capacity: usize) -> Result<Self, AllocErr> {
if capacity == 0 {
return Ok(Self::new_impl(EMPTY_CHUNK.get()));
}
let layout = layout_from_size_align(capacity, MIN_ALIGN)?;
let chunk_footer = unsafe {
Self::new_chunk(
Self::new_chunk_memory_details(Some(capacity), layout).ok_or(AllocErr)?,
layout,
EMPTY_CHUNK.get(),
)
.ok_or(AllocErr)?
};
Ok(Self::new_impl(chunk_footer))
}
#[inline(always)]
pub(super) fn new_impl(chunk_footer_ptr: NonNull<ChunkFooter>) -> Self {
const { Self::MIN_ALIGN };
let start_ptr = unsafe { chunk_footer_ptr.as_ref().start_ptr };
let cursor_ptr = chunk_footer_ptr.cast::<u8>();
Self {
cursor_ptr: Cell::new(cursor_ptr),
current_chunk_footer: Cell::new(chunk_footer_ptr),
start_ptr: Cell::new(start_ptr),
can_grow: true,
#[cfg(all(feature = "track_allocations", not(feature = "disable_track_allocations")))]
stats: AllocationStats::default(),
}
}
pub(super) fn new_chunk_memory_details(
new_size_without_footer: Option<usize>,
requested_layout: Layout,
) -> Option<NewChunkMemoryDetails> {
let align = CHUNK_ALIGN
.max(MIN_ALIGN)
.max(requested_layout.align());
let mut new_size_without_footer =
new_size_without_footer.unwrap_or(DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER);
let requested_size =
round_up_to(requested_layout.size(), align).unwrap_or_else(allocation_size_overflow);
new_size_without_footer = new_size_without_footer.max(requested_size);
if new_size_without_footer < TYPICAL_PAGE_SIZE {
new_size_without_footer =
(new_size_without_footer + OVERHEAD).next_power_of_two() - OVERHEAD;
} else {
new_size_without_footer =
round_up_to(new_size_without_footer + OVERHEAD, TYPICAL_PAGE_SIZE)? - OVERHEAD;
}
debug_assert_eq!(align % CHUNK_ALIGN, 0);
debug_assert_eq!(new_size_without_footer % CHUNK_ALIGN, 0);
let size = new_size_without_footer
.checked_add(CHUNK_FOOTER_SIZE)
.unwrap_or_else(allocation_size_overflow);
Some(NewChunkMemoryDetails { new_size_without_footer, align, size })
}
pub(super) unsafe fn new_chunk(
new_chunk_memory_details: NewChunkMemoryDetails,
requested_layout: Layout,
prev: NonNull<ChunkFooter>,
) -> Option<NonNull<ChunkFooter>> {
unsafe {
let NewChunkMemoryDetails { new_size_without_footer, align, size } =
new_chunk_memory_details;
let layout = layout_from_size_align(size, align).ok()?;
debug_assert!(size >= requested_layout.size());
let start_ptr = alloc::alloc(layout);
let start_ptr = NonNull::new(start_ptr)?;
let footer_ptr = start_ptr.as_ptr().add(new_size_without_footer);
debug_assert_eq!((start_ptr.as_ptr() as usize) % align, 0);
debug_assert_eq!(footer_ptr as usize % CHUNK_ALIGN, 0);
#[expect(
clippy::cast_ptr_alignment,
reason = "footer_ptr is aligned to CHUNK_ALIGN, which is == align_of::<ChunkFooter>()"
)]
let footer_ptr = footer_ptr.cast::<ChunkFooter>();
let cursor_ptr = NonNull::new_unchecked(footer_ptr.cast::<u8>());
debug_assert_eq!(cursor_ptr.as_ptr() as usize % MIN_ALIGN, 0);
ptr::write(
footer_ptr,
ChunkFooter {
start_ptr,
layout,
previous_chunk_footer_ptr: Cell::new(prev),
cursor_ptr: Cell::new(cursor_ptr),
},
);
Some(NonNull::new_unchecked(footer_ptr))
}
}
}
impl<const MIN_ALIGN: usize> Default for Arena<MIN_ALIGN> {
fn default() -> Self {
Self::with_min_align()
}
}
#[cold]
#[inline(never)]
fn allocation_size_overflow<T>() -> T {
panic!("requested allocation size overflowed")
}