use std::{
alloc::{self, Layout},
cell::Cell,
cmp::max,
ptr::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_ARENA_DATA_PTR,
utils::{
is_pointer_aligned_to, layout_from_size_align, oom, round_up_to, round_up_to_unchecked,
},
};
pub const TYPICAL_PAGE_SIZE: usize = 0x1000;
const _: () = {
assert!(TYPICAL_PAGE_SIZE.is_power_of_two());
assert!(TYPICAL_PAGE_SIZE <= (usize::MAX / 8) + 1);
};
#[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 _: () = {
assert!(MALLOC_OVERHEAD == CHUNK_ALIGN);
assert!(MALLOC_OVERHEAD < TYPICAL_PAGE_SIZE);
};
pub const OVERHEAD: usize = match round_up_to(MALLOC_OVERHEAD + CHUNK_FOOTER_SIZE, CHUNK_ALIGN) {
Some(x) => x,
None => panic!(),
};
const _: () = {
assert!(OVERHEAD > CHUNK_FOOTER_SIZE);
assert!(OVERHEAD < TYPICAL_PAGE_SIZE); };
const FIRST_ALLOCATION_GOAL: usize = 16 * 1024;
const _: () = {
assert!(FIRST_ALLOCATION_GOAL.is_power_of_two());
assert!(FIRST_ALLOCATION_GOAL <= (usize::MAX / 8) + 1);
assert!(FIRST_ALLOCATION_GOAL > OVERHEAD);
};
pub const DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER: usize = FIRST_ALLOCATION_GOAL - OVERHEAD;
impl Arena<1> {
#[inline] pub fn new() -> Self {
Self::with_min_align()
}
#[inline] pub fn with_capacity(capacity: usize) -> Self {
Self::with_min_align_and_capacity(capacity)
}
#[inline] 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> {
#[inline] pub fn with_min_align() -> Self {
Self::new_impl(EMPTY_ARENA_DATA_PTR, EMPTY_ARENA_DATA_PTR, None)
}
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::with_min_align());
}
let layout = layout_from_size_align(capacity, MIN_ALIGN)?;
let chunk_footer_ptr = unsafe { Self::new_chunk(capacity, layout, None) };
let chunk_footer_ptr = chunk_footer_ptr.ok_or(AllocErr)?;
let start_ptr = unsafe { chunk_footer_ptr.as_ref().start_ptr };
let cursor_ptr = chunk_footer_ptr.cast::<u8>();
Ok(Self::new_impl(start_ptr, cursor_ptr, Some(chunk_footer_ptr)))
}
#[inline(always)]
pub(super) fn new_impl(
start_ptr: NonNull<u8>,
cursor_ptr: NonNull<u8>,
chunk_footer_ptr: Option<NonNull<ChunkFooter>>,
) -> Self {
const { Self::MIN_ALIGN };
debug_assert!(is_pointer_aligned_to(cursor_ptr, MIN_ALIGN));
debug_assert!(start_ptr <= cursor_ptr);
debug_assert!(
chunk_footer_ptr.is_none_or(|footer_ptr| cursor_ptr <= footer_ptr.cast::<u8>())
);
Self {
cursor_ptr: Cell::new(cursor_ptr),
current_chunk_footer_ptr: 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) unsafe fn new_chunk(
new_size_without_footer: usize,
requested_layout: Layout,
previous_chunk_footer_ptr: Option<NonNull<ChunkFooter>>,
) -> Option<NonNull<ChunkFooter>> {
let align = max(requested_layout.align(), CHUNK_ALIGN);
let requested_size = unsafe { round_up_to_unchecked(requested_layout.size(), align) };
let new_size_without_footer = max(new_size_without_footer, requested_size);
let new_size_with_overhead = if new_size_without_footer < TYPICAL_PAGE_SIZE {
(new_size_without_footer + OVERHEAD).next_power_of_two()
} else {
const MAX_SIZE_WITHOUT_FOOTER: usize = isize::MAX as usize + 1 - OVERHEAD;
if new_size_without_footer > MAX_SIZE_WITHOUT_FOOTER {
return None;
}
unsafe { round_up_to_unchecked(new_size_without_footer + OVERHEAD, TYPICAL_PAGE_SIZE) }
};
debug_assert!(new_size_with_overhead <= isize::MAX as usize + 1);
let new_size_without_footer = new_size_with_overhead - OVERHEAD;
debug_assert!(align.is_multiple_of(CHUNK_ALIGN));
debug_assert!(new_size_without_footer.is_multiple_of(CHUNK_ALIGN));
let size = new_size_without_footer + CHUNK_FOOTER_SIZE;
let layout = layout_from_size_align(size, align).ok()?;
debug_assert!(size >= requested_layout.size());
unsafe {
let start_ptr = alloc::alloc(layout);
let start_ptr = NonNull::new(start_ptr)?;
let footer_ptr = start_ptr.add(new_size_without_footer).cast::<ChunkFooter>();
debug_assert!(is_pointer_aligned_to(start_ptr, align));
debug_assert!(is_pointer_aligned_to(footer_ptr, CHUNK_ALIGN));
let cursor_ptr = footer_ptr.cast::<u8>();
debug_assert!(is_pointer_aligned_to(cursor_ptr, MIN_ALIGN));
footer_ptr.write(ChunkFooter {
start_ptr,
layout,
previous_chunk_footer_ptr: Cell::new(previous_chunk_footer_ptr),
cursor_ptr: Cell::new(cursor_ptr),
});
Some(footer_ptr)
}
}
}
impl<const MIN_ALIGN: usize> Default for Arena<MIN_ALIGN> {
#[inline] fn default() -> Self {
Self::with_min_align()
}
}