use std::mem::{self, MaybeUninit};
use std::ptr::NonNull;
use super::block::{align_up, Block, MIN_BLOCK_ALIGN};
use super::stats::ArenaStats;
use crate::util::{
inline_vec::InlineVec,
transaction::{run_with_transaction, run_with_transaction_infallible, Transaction},
};
#[cfg(feature = "drop-tracking")]
use crate::util::drop_registry::DropRegistry;
const MIN_BLOCK_SIZE: usize = 64;
const DEFAULT_BLOCK_SIZE: usize = 64 * 1_024;
const MAX_BLOCK_SIZE: usize = 16 * 1_024 * 1_024;
const BLOCKS_INLINE_CAP: usize = 8;
pub(crate) const CACHE_LINE_SIZE: usize = 64;
#[derive(Debug, Clone, Copy)]
#[must_use = "checkpoint is useless unless passed to Arena::rewind"]
pub struct Checkpoint {
pub(crate) block_idx: usize,
pub(crate) offset: usize,
pub(crate) bytes_allocated: usize,
#[cfg(feature = "drop-tracking")]
pub(crate) drop_registry_len: usize,
}
impl std::fmt::Display for Checkpoint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Checkpoint(block={}, offset={}, bytes={})",
self.block_idx, self.offset, self.bytes_allocated
)
}
}
pub struct Arena {
blocks: InlineVec<Block, BLOCKS_INLINE_CAP>,
pub(crate) current: usize,
next_block_size: usize,
pub(crate) bytes_allocated: usize,
bytes_reserved: usize,
pub(crate) txn_depth: usize,
#[cfg(feature = "drop-tracking")]
pub(crate) drop_registry: DropRegistry,
#[cfg(not(feature = "drop-tracking"))]
_drop_registry: (),
pub(crate) cur_base: *mut u8,
pub(crate) cur_ptr: *mut u8,
high_water_mark: usize,
largest_remaining: usize,
}
impl Arena {
#[must_use]
pub fn new() -> Self {
Self::with_capacity(DEFAULT_BLOCK_SIZE)
}
#[must_use]
pub fn with_capacity(initial_bytes: usize) -> Self {
let size = initial_bytes.max(MIN_BLOCK_SIZE);
let block = Block::new(size, MIN_BLOCK_ALIGN);
let base = block.base;
let mut blocks: InlineVec<Block, BLOCKS_INLINE_CAP> = InlineVec::new();
blocks.push(block);
Arena {
blocks,
current: 0,
next_block_size: size.saturating_mul(2).min(MAX_BLOCK_SIZE),
bytes_allocated: 0,
bytes_reserved: size,
txn_depth: 0,
#[cfg(feature = "drop-tracking")]
drop_registry: DropRegistry::new(),
#[cfg(not(feature = "drop-tracking"))]
_drop_registry: (),
cur_base: base,
cur_ptr: base,
high_water_mark: 0,
largest_remaining: 0,
}
}
}
impl Default for Arena {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for Arena {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let stats = self.stats();
f.debug_struct("Arena")
.field("bytes_allocated", &stats.bytes_allocated)
.field("bytes_reserved", &stats.bytes_reserved)
.field("block_count", &stats.block_count)
.field("txn_depth", &self.txn_depth)
.finish()
}
}
impl Arena {
#[inline]
pub fn alloc<T>(&mut self, val: T) -> &mut T {
if mem::size_of::<T>() == 0 {
return unsafe { &mut *NonNull::dangling().as_ptr() };
}
let ptr = self.alloc_raw_inner(mem::size_of::<T>(), mem::align_of::<T>());
unsafe {
let typed = ptr.as_ptr().cast::<T>();
typed.write(val);
#[cfg(feature = "drop-tracking")]
self.drop_registry.register(typed);
&mut *typed
}
}
#[inline]
pub fn alloc_slice<T, I>(&mut self, iter: I) -> &mut [T]
where
I: IntoIterator<Item = T>,
I::IntoIter: ExactSizeIterator,
{
let mut iter = iter.into_iter();
let len = iter.len();
if len == 0 {
return &mut [];
}
let total = mem::size_of::<T>().checked_mul(len).expect("overflow");
let ptr = self.alloc_raw_inner(total, mem::align_of::<T>());
unsafe {
let start = ptr.as_ptr().cast::<T>();
Self::write_slice_bulk::<T, _>(start, &mut iter, len, total);
#[cfg(feature = "drop-tracking")]
self.drop_registry.register_slice(start, len);
std::slice::from_raw_parts_mut(start, len)
}
}
#[inline]
pub fn alloc_slice_copy<T: Copy>(&mut self, src: &[T]) -> &mut [T] {
let len = src.len();
if len == 0 {
return &mut [];
}
let total = mem::size_of::<T>().checked_mul(len).expect("overflow");
let ptr = self.alloc_raw_inner(total, mem::align_of::<T>());
unsafe {
let dst = ptr.as_ptr().cast::<T>();
std::ptr::copy_nonoverlapping(src.as_ptr(), dst, len);
#[cfg(feature = "drop-tracking")]
self.drop_registry.register_slice(dst, len);
std::slice::from_raw_parts_mut(dst, len)
}
}
#[inline(always)]
pub fn alloc_str(&mut self, s: &str) -> &str {
if s.is_empty() {
return "";
}
let ptr = self.alloc_raw_inner(s.len(), 1);
unsafe {
std::ptr::copy_nonoverlapping(s.as_ptr(), ptr.as_ptr(), s.len());
std::str::from_utf8_unchecked(std::slice::from_raw_parts(ptr.as_ptr(), s.len()))
}
}
#[inline]
pub fn alloc_uninit<T>(&mut self) -> &mut MaybeUninit<T> {
let size = mem::size_of::<T>();
let align = mem::align_of::<T>();
if size == 0 {
return unsafe { &mut *NonNull::dangling().as_ptr() };
}
let ptr = self.alloc_raw_inner(size, align);
unsafe { &mut *ptr.as_ptr().cast::<MaybeUninit<T>>() }
}
#[inline]
pub fn alloc_zeroed(&mut self, size: usize, align: usize) -> NonNull<u8> {
if size == 0 {
return NonNull::dangling();
}
let ptr = self.alloc_raw(size, align);
unsafe { ptr.as_ptr().write_bytes(0, size) };
ptr
}
#[inline]
pub fn alloc_cache_aligned(&mut self, size: usize) -> NonNull<u8> {
self.alloc_raw(size, CACHE_LINE_SIZE)
}
#[inline]
pub fn alloc_raw(&mut self, size: usize, align: usize) -> NonNull<u8> {
assert!(align.is_power_of_two(), "align must be a power of two");
if size == 0 {
return NonNull::dangling();
}
self.alloc_raw_inner(size, align)
}
}
impl Arena {
#[inline]
pub fn try_alloc<T>(&mut self, val: T) -> Option<&mut T> {
if mem::size_of::<T>() == 0 {
return Some(unsafe { &mut *NonNull::dangling().as_ptr() });
}
let ptr = self.try_alloc_raw_inner(mem::size_of::<T>(), mem::align_of::<T>())?;
Some(unsafe {
let typed = ptr.as_ptr().cast::<T>();
typed.write(val);
#[cfg(feature = "drop-tracking")]
self.drop_registry.register(typed);
&mut *typed
})
}
#[inline]
pub fn try_alloc_slice<T, I>(&mut self, iter: I) -> Option<&mut [T]>
where
I: IntoIterator<Item = T>,
I::IntoIter: ExactSizeIterator,
{
let mut iter = iter.into_iter();
let len = iter.len();
if len == 0 {
return Some(&mut []);
}
let total = mem::size_of::<T>().checked_mul(len)?;
let ptr = self.try_alloc_raw_inner(total, mem::align_of::<T>())?;
Some(unsafe {
let start = ptr.as_ptr().cast::<T>();
Self::write_slice_bulk::<T, _>(start, &mut iter, len, total);
#[cfg(feature = "drop-tracking")]
self.drop_registry.register_slice(start, len);
std::slice::from_raw_parts_mut(start, len)
})
}
#[inline]
pub fn try_alloc_str(&mut self, s: &str) -> Option<&str> {
if s.is_empty() {
return Some("");
}
let ptr = self.try_alloc_raw_inner(s.len(), 1)?;
unsafe {
core::ptr::copy_nonoverlapping(s.as_ptr(), ptr.as_ptr(), s.len());
Some(core::str::from_utf8_unchecked(core::slice::from_raw_parts(
ptr.as_ptr(),
s.len(),
)))
}
}
#[inline]
pub fn try_alloc_raw(&mut self, size: usize, align: usize) -> Option<NonNull<u8>> {
assert!(align.is_power_of_two(), "align must be a power of two");
if size == 0 {
return Some(NonNull::dangling());
}
self.try_alloc_raw_inner(size, align)
}
#[inline]
pub fn try_alloc_slice_copy<T: Copy>(&mut self, src: &[T]) -> Option<&mut [T]> {
let len = src.len();
if len == 0 {
return Some(&mut []);
}
let total = mem::size_of::<T>().checked_mul(len)?;
let ptr = self.try_alloc_raw_inner(total, mem::align_of::<T>())?;
unsafe {
let dst = ptr.as_ptr().cast::<T>();
std::ptr::copy_nonoverlapping(src.as_ptr(), dst, len);
#[cfg(feature = "drop-tracking")]
self.drop_registry.register_slice(dst, len);
Some(std::slice::from_raw_parts_mut(dst, len))
}
}
#[inline]
pub fn try_alloc_zeroed(&mut self, size: usize, align: usize) -> Option<NonNull<u8>> {
assert!(align.is_power_of_two(), "align must be a power of two");
if size == 0 {
return Some(NonNull::dangling());
}
let ptr = self.try_alloc_raw_inner(size, align)?;
unsafe { ptr.as_ptr().write_bytes(0, size) };
Some(ptr)
}
#[inline]
pub fn try_alloc_cache_aligned(&mut self, size: usize) -> Option<NonNull<u8>> {
self.try_alloc_raw(size, CACHE_LINE_SIZE)
}
}
impl Arena {
#[inline(always)]
pub fn checkpoint(&self) -> Checkpoint {
let current_offset = self.cur_ptr as usize - self.cur_base as usize;
Checkpoint {
block_idx: self.current,
offset: current_offset,
bytes_allocated: self.bytes_allocated + current_offset,
#[cfg(feature = "drop-tracking")]
drop_registry_len: self.drop_registry.len(),
}
}
pub fn rewind(&mut self, cp: Checkpoint) {
assert!(
cp.block_idx < self.blocks.len(),
"rewind: checkpoint block_idx {} out of range (arena has {} blocks)",
cp.block_idx,
self.blocks.len()
);
debug_assert!(
cp.offset <= self.blocks[cp.block_idx].capacity,
"rewind: checkpoint offset {} exceeds block capacity {}",
cp.offset,
self.blocks[cp.block_idx].capacity
);
#[cfg(feature = "drop-tracking")]
self.drop_registry.run_drops_until(cp.drop_registry_len);
for i in (cp.block_idx + 1)..=self.current {
self.blocks.get_mut(i).offset = 0;
}
self.blocks[cp.block_idx].offset = cp.offset;
self.bytes_allocated = cp.bytes_allocated - cp.offset;
self.set_current_block(cp.block_idx);
}
pub fn reset(&mut self) {
#[cfg(feature = "drop-tracking")]
self.drop_registry.run_all_drops();
for i in 0..=self.high_water_mark {
self.blocks.get_mut(i).offset = 0;
}
self.high_water_mark = 0;
self.bytes_allocated = 0;
self.largest_remaining = 0;
self.set_current_block(0);
}
}
impl Arena {
#[inline]
pub fn transaction(&mut self) -> Transaction<'_> {
Transaction::new(self)
}
#[inline]
pub fn with_transaction<F, T, E>(&mut self, f: F) -> Result<T, E>
where
F: FnOnce(&mut Transaction<'_>) -> Result<T, E>,
{
run_with_transaction(self, f)
}
#[inline]
pub fn with_transaction_infallible<F, T>(&mut self, f: F) -> T
where
F: FnOnce(&mut Transaction<'_>) -> T,
{
run_with_transaction_infallible(self, f)
}
#[inline]
#[must_use]
pub fn transaction_depth(&self) -> usize {
self.txn_depth
}
}
impl Arena {
#[cfg(feature = "drop-tracking")]
pub unsafe fn register_drop<T>(&mut self, ptr: *mut T) {
self.drop_registry.register(ptr);
}
#[cfg(not(feature = "drop-tracking"))]
pub unsafe fn register_drop<T>(&mut self, _ptr: *mut T) {}
#[inline(always)]
pub fn stats(&self) -> ArenaStats {
let current_used = self.cur_ptr as usize - self.cur_base as usize;
ArenaStats {
bytes_allocated: self.bytes_allocated + current_used,
bytes_reserved: self.bytes_reserved,
block_count: self.blocks.len(),
}
}
#[inline(always)]
#[must_use]
pub fn block_count(&self) -> usize {
self.blocks.len()
}
}
impl Arena {
#[inline(always)]
unsafe fn write_slice_bulk<T, I: Iterator<Item = T>>(
dst: *mut T,
iter: &mut I,
len: usize,
total_bytes: usize,
) {
if !mem::needs_drop::<T>() && mem::size_of::<T>() > 0 && total_bytes <= 256 {
let mut buf = [0u8; 256];
let buf_ptr = buf.as_mut_ptr().cast::<T>();
for i in 0..len {
buf_ptr.add(i).write(iter.next().unwrap());
}
std::ptr::copy_nonoverlapping(buf_ptr, dst, len);
} else {
for i in 0..len {
dst.add(i).write(iter.next().unwrap());
}
}
}
#[inline(always)]
pub(crate) fn alloc_raw_inner(&mut self, size: usize, align: usize) -> NonNull<u8> {
if align > MIN_BLOCK_ALIGN {
return self.alloc_slow(size, align);
}
let cur_offset = self.cur_ptr as usize - self.cur_base as usize;
let aligned_offset = align_up(cur_offset, align);
let new_offset = aligned_offset.saturating_add(size);
if new_offset <= self.blocks[self.current].capacity {
let ptr = unsafe { self.cur_base.add(aligned_offset) };
self.cur_ptr = unsafe { self.cur_base.add(new_offset) };
return unsafe { NonNull::new_unchecked(ptr) };
}
self.alloc_slow(size, align)
}
#[inline(always)]
pub(crate) fn try_alloc_raw_inner(&mut self, size: usize, align: usize) -> Option<NonNull<u8>> {
if align > MIN_BLOCK_ALIGN {
return self.alloc_slow_try(size, align);
}
let cur_offset = self.cur_ptr as usize - self.cur_base as usize;
let aligned_offset = align_up(cur_offset, align);
let new_offset = aligned_offset.checked_add(size)?;
if new_offset <= self.blocks[self.current].capacity {
let ptr = unsafe { self.cur_base.add(aligned_offset) };
self.cur_ptr = unsafe { self.cur_base.add(new_offset) };
return Some(unsafe { NonNull::new_unchecked(ptr) });
}
self.alloc_slow_try(size, align)
}
#[cold]
fn alloc_slow(&mut self, size: usize, align: usize) -> NonNull<u8> {
self.finish_slow_path(size, align);
if align > MIN_BLOCK_ALIGN {
return self.alloc_new_block(size, align);
}
if size <= self.largest_remaining {
for i in (self.current + 1)..self.blocks.len() {
let block = &mut self.blocks[i];
if block.align >= align {
if let Some((ptr, delta)) = block.try_alloc(size, align) {
self.bytes_allocated += delta;
self.set_current_block(i);
return ptr;
}
}
}
}
self.alloc_new_block(size, align)
}
#[cold]
fn alloc_slow_try(&mut self, size: usize, align: usize) -> Option<NonNull<u8>> {
self.finish_slow_path(size, align);
if align > MIN_BLOCK_ALIGN {
return self.try_alloc_new_block(size, align);
}
if size <= self.largest_remaining {
for i in (self.current + 1)..self.blocks.len() {
let block = &mut self.blocks[i];
if block.align >= align {
if let Some((ptr, delta)) = block.try_alloc(size, align) {
self.bytes_allocated += delta;
self.set_current_block(i);
return Some(ptr);
}
}
}
}
self.try_alloc_new_block(size, align)
}
#[inline]
fn finish_slow_path(&mut self, _size: usize, _align: usize) {
self.bytes_allocated += self.cur_ptr as usize - self.cur_base as usize;
self.blocks[self.current].offset = self.cur_ptr as usize - self.cur_base as usize;
}
#[inline]
fn alloc_new_block(&mut self, size: usize, align: usize) -> NonNull<u8> {
let block_size = self.next_block_for(size, align);
let mut block = Block::new(block_size, align);
let (ptr, delta) = block
.try_alloc(size, align)
.expect("fresh block must satisfy request");
self.bytes_reserved += block_size;
self.bytes_allocated += delta;
self.blocks.push(block);
self.set_current_block(self.blocks.len() - 1);
ptr
}
#[inline]
fn try_alloc_new_block(&mut self, size: usize, align: usize) -> Option<NonNull<u8>> {
let block_size = self.next_block_for(size, align);
let mut block = Block::try_new(block_size, align)?;
let (ptr, delta) = block.try_alloc(size, align)?;
self.bytes_reserved += block_size;
self.bytes_allocated += delta;
self.blocks.push(block);
self.set_current_block(self.blocks.len() - 1);
Some(ptr)
}
#[inline(always)]
fn set_current_block(&mut self, idx: usize) {
let block = self.blocks.get(idx);
self.current = idx;
if idx > self.high_water_mark {
self.high_water_mark = idx;
}
self.cur_base = block.base;
self.cur_ptr = unsafe { block.base.add(block.offset) };
self.largest_remaining = self.compute_largest_remaining(idx);
}
fn next_block_for(&mut self, size: usize, align: usize) -> usize {
let worst = size
.checked_add(align - 1)
.expect("allocation size overflow");
let rounded = self
.next_block_size
.max(worst)
.max(align * 2)
.checked_next_power_of_two()
.unwrap_or(usize::MAX)
.min(MAX_BLOCK_SIZE)
.max(worst)
.max(align * 2);
self.next_block_size = rounded.saturating_add(rounded / 2).min(MAX_BLOCK_SIZE);
rounded
}
#[inline]
fn compute_largest_remaining(&self, from_idx: usize) -> usize {
let mut max_rem = 0;
for i in (from_idx + 1)..self.blocks.len() {
let rem = self.blocks.get(i).capacity - self.blocks.get(i).offset;
if rem > max_rem {
max_rem = rem;
}
}
max_rem
}
}