use crate::core::error::{Error, Result};
use std::alloc::{alloc, dealloc, Layout};
use std::cell::{Cell, RefCell};
use std::marker::PhantomData;
use std::mem::{needs_drop, size_of, MaybeUninit};
use std::ptr::NonNull;
use std::slice;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Mutex, MutexGuard};
const DEFAULT_CHUNK_SIZE: usize = 64 * 1024;
const MAX_CHUNK_SIZE: usize = 16 * 1024 * 1024;
const MIN_ALIGNMENT: usize = 8;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ArenaStats {
pub total_allocated: usize,
pub bytes_in_use: usize,
pub chunk_count: usize,
pub allocation_count: usize,
pub peak_usage: usize,
pub alignment_waste: usize,
}
struct Chunk {
data: NonNull<u8>,
layout: Layout,
offset: usize,
capacity: usize,
}
unsafe impl Send for Chunk {}
impl Chunk {
fn new(capacity: usize) -> Option<Self> {
let layout = Layout::from_size_align(capacity, MIN_ALIGNMENT).ok()?;
if layout.size() == 0 {
return None;
}
let data = unsafe {
let ptr = alloc(layout);
NonNull::new(ptr)?
};
Some(Chunk {
data,
layout,
offset: 0,
capacity,
})
}
fn try_alloc(&mut self, size: usize, align: usize) -> Option<(NonNull<u8>, usize)> {
let current_ptr = (self.data.as_ptr() as usize).checked_add(self.offset)?;
let aligned_ptr = current_ptr.checked_add(align - 1)? & !(align - 1);
let padding = aligned_ptr - current_ptr;
let total_size = size.checked_add(padding)?;
if self.offset.checked_add(total_size)? > self.capacity {
return None;
}
let result_ptr = unsafe { self.data.as_ptr().add(self.offset + padding) };
self.offset += total_size;
NonNull::new(result_ptr).map(|ptr| (ptr, padding))
}
fn reset(&mut self) {
self.offset = 0;
}
}
impl Drop for Chunk {
fn drop(&mut self) {
unsafe {
dealloc(self.data.as_ptr(), self.layout);
}
}
}
#[derive(Debug, Clone, Copy)]
struct DropEntry {
addr: NonNull<u8>,
drop_fn: unsafe fn(NonNull<u8>),
}
unsafe impl Send for DropEntry {}
unsafe fn drop_in_place_at<T>(addr: NonNull<u8>) {
std::ptr::drop_in_place(addr.as_ptr() as *mut T);
}
unsafe fn run_drop_entries(entries: Vec<DropEntry>) {
for entry in entries.into_iter().rev() {
(entry.drop_fn)(entry.addr);
}
}
#[derive(Debug, Clone, Copy)]
struct ArenaCheckpoint {
chunk_count: usize,
last_chunk_offset: usize,
drops_len: usize,
bytes_in_use: usize,
allocation_count: usize,
alignment_waste: usize,
}
pub struct Arena {
chunks: RefCell<Vec<Chunk>>,
drops: RefCell<Vec<DropEntry>>,
chunk_size: Cell<usize>,
stats: RefCell<ArenaStats>,
grow_chunks: bool,
_not_send: PhantomData<*const ()>,
}
impl Arena {
pub fn new() -> Self {
Self::with_capacity(DEFAULT_CHUNK_SIZE)
}
pub fn with_capacity(chunk_size: usize) -> Self {
Arena {
chunks: RefCell::new(Vec::new()),
drops: RefCell::new(Vec::new()),
chunk_size: Cell::new(chunk_size.max(1024)),
stats: RefCell::new(ArenaStats::default()),
grow_chunks: true,
_not_send: PhantomData,
}
}
pub fn fixed_chunk_size(chunk_size: usize) -> Self {
let mut arena = Self::with_capacity(chunk_size);
arena.grow_chunks = false;
arena
}
pub fn alloc_raw(&self, layout: Layout) -> Option<NonNull<u8>> {
let size = layout.size();
let align = layout.align().max(MIN_ALIGNMENT);
{
let mut chunks = self.chunks.borrow_mut();
if let Some(chunk) = chunks.last_mut() {
if let Some((ptr, padding)) = chunk.try_alloc(size, align) {
drop(chunks);
let mut stats = self.stats.borrow_mut();
stats.bytes_in_use += size;
stats.alignment_waste += padding;
stats.allocation_count += 1;
stats.peak_usage = stats.peak_usage.max(stats.bytes_in_use);
return Some(ptr);
}
}
}
self.alloc_new_chunk(size, align)
}
fn alloc_new_chunk(&self, size: usize, align: usize) -> Option<NonNull<u8>> {
let chunk_size = self.chunk_size.get();
let needed_size = size.checked_add(align)?.max(chunk_size);
let mut chunk = Chunk::new(needed_size)?;
let (ptr, padding) = chunk.try_alloc(size, align)?;
{
let mut stats = self.stats.borrow_mut();
stats.total_allocated += needed_size;
stats.bytes_in_use += size;
stats.alignment_waste += padding;
stats.chunk_count += 1;
stats.allocation_count += 1;
stats.peak_usage = stats.peak_usage.max(stats.bytes_in_use);
}
self.chunks.borrow_mut().push(chunk);
if self.grow_chunks {
let new_size = chunk_size.saturating_mul(2).min(MAX_CHUNK_SIZE);
self.chunk_size.set(new_size);
}
Some(ptr)
}
#[allow(clippy::mut_from_ref)]
pub fn alloc<T: 'static>(&self, value: T) -> Result<&mut T> {
let layout = Layout::new::<T>();
let ptr = self.alloc_raw(layout).ok_or_else(|| {
Error::OperationFailed(format!(
"Arena: failed to allocate {} bytes for {}",
layout.size(),
std::any::type_name::<T>()
))
})?;
unsafe {
let typed_ptr = ptr.as_ptr() as *mut T;
typed_ptr.write(value);
if needs_drop::<T>() {
self.drops.borrow_mut().push(DropEntry {
addr: ptr,
drop_fn: drop_in_place_at::<T>,
});
}
Ok(&mut *typed_ptr)
}
}
#[allow(clippy::mut_from_ref)]
pub unsafe fn alloc_uninit<T>(&self) -> Result<&mut T> {
let layout = Layout::new::<T>();
let ptr = self.alloc_raw(layout).ok_or_else(|| {
Error::OperationFailed(format!(
"Arena: failed to allocate {} bytes for {}",
layout.size(),
std::any::type_name::<T>()
))
})?;
Ok(&mut *(ptr.as_ptr() as *mut T))
}
#[allow(clippy::mut_from_ref)]
pub fn alloc_slice<T: Copy>(&self, values: &[T]) -> Result<&mut [T]> {
if values.is_empty() {
return Ok(&mut []);
}
let layout = Layout::array::<T>(values.len()).map_err(|_| {
Error::InvalidInput(format!(
"Arena: invalid layout for {} x {}",
values.len(),
std::any::type_name::<T>()
))
})?;
let ptr = self.alloc_raw(layout).ok_or_else(|| {
Error::OperationFailed(format!("Arena: failed to allocate {} bytes", layout.size()))
})?;
unsafe {
let slice_ptr = ptr.as_ptr() as *mut T;
std::ptr::copy_nonoverlapping(values.as_ptr(), slice_ptr, values.len());
Ok(slice::from_raw_parts_mut(slice_ptr, values.len()))
}
}
#[allow(clippy::mut_from_ref)]
pub fn alloc_slice_maybe_uninit<T>(&self, len: usize) -> Result<&mut [MaybeUninit<T>]> {
if len == 0 {
return Ok(&mut []);
}
let layout = Layout::array::<MaybeUninit<T>>(len).map_err(|_| {
Error::InvalidInput(format!(
"Arena: invalid layout for {} x {}",
len,
std::any::type_name::<T>()
))
})?;
let ptr = self.alloc_raw(layout).ok_or_else(|| {
Error::OperationFailed(format!("Arena: failed to allocate {} bytes", layout.size()))
})?;
unsafe {
Ok(slice::from_raw_parts_mut(
ptr.as_ptr() as *mut MaybeUninit<T>,
len,
))
}
}
#[allow(clippy::mut_from_ref)]
pub unsafe fn alloc_slice_uninit<T>(&self, len: usize) -> Result<&mut [T]> {
let uninit = self.alloc_slice_maybe_uninit::<T>(len)?;
Ok(slice::from_raw_parts_mut(
uninit.as_mut_ptr() as *mut T,
uninit.len(),
))
}
pub fn alloc_str(&self, s: &str) -> Result<&str> {
let bytes = self.alloc_slice(s.as_bytes())?;
Ok(unsafe { std::str::from_utf8_unchecked(bytes) })
}
fn take_drop_entries(&self) -> Vec<DropEntry> {
std::mem::take(&mut *self.drops.borrow_mut())
}
pub fn reset(&mut self) {
unsafe { run_drop_entries(self.take_drop_entries()) };
let kept_capacity = {
let mut chunks = self.chunks.borrow_mut();
if let Some(best_idx) = chunks
.iter()
.enumerate()
.max_by_key(|(_, chunk)| chunk.capacity)
.map(|(idx, _)| idx)
{
let mut kept = chunks.swap_remove(best_idx);
kept.reset();
chunks.clear();
let capacity = kept.capacity;
chunks.push(kept);
capacity
} else {
0
}
};
let mut stats = self.stats.borrow_mut();
stats.total_allocated = kept_capacity;
stats.chunk_count = usize::from(kept_capacity > 0);
stats.bytes_in_use = 0;
stats.allocation_count = 0;
stats.alignment_waste = 0;
}
pub fn clear(&mut self) {
unsafe { run_drop_entries(self.take_drop_entries()) };
self.chunks.borrow_mut().clear();
self.chunk_size.set(DEFAULT_CHUNK_SIZE);
*self.stats.borrow_mut() = ArenaStats::default();
}
fn checkpoint(&self) -> ArenaCheckpoint {
let chunks = self.chunks.borrow();
let stats = self.stats.borrow();
ArenaCheckpoint {
chunk_count: chunks.len(),
last_chunk_offset: chunks.last().map(|chunk| chunk.offset).unwrap_or(0),
drops_len: self.drops.borrow().len(),
bytes_in_use: stats.bytes_in_use,
allocation_count: stats.allocation_count,
alignment_waste: stats.alignment_waste,
}
}
fn restore(&mut self, checkpoint: ArenaCheckpoint) {
let tail = {
let mut drops = self.drops.borrow_mut();
if drops.len() > checkpoint.drops_len {
drops.split_off(checkpoint.drops_len)
} else {
Vec::new()
}
};
unsafe { run_drop_entries(tail) };
let (released, chunk_count) = {
let mut chunks = self.chunks.borrow_mut();
let released: usize = chunks
.iter()
.skip(checkpoint.chunk_count)
.map(|chunk| chunk.capacity)
.sum();
chunks.truncate(checkpoint.chunk_count);
if let Some(last) = chunks.last_mut() {
last.offset = checkpoint.last_chunk_offset;
}
(released, chunks.len())
};
let mut stats = self.stats.borrow_mut();
stats.total_allocated = stats.total_allocated.saturating_sub(released);
stats.chunk_count = chunk_count;
stats.bytes_in_use = checkpoint.bytes_in_use;
stats.allocation_count = checkpoint.allocation_count;
stats.alignment_waste = checkpoint.alignment_waste;
}
pub fn stats(&self) -> ArenaStats {
self.stats.borrow().clone()
}
pub fn total_allocated(&self) -> usize {
self.stats.borrow().total_allocated
}
pub fn bytes_in_use(&self) -> usize {
self.stats.borrow().bytes_in_use
}
}
impl Drop for Arena {
fn drop(&mut self) {
unsafe { run_drop_entries(self.take_drop_entries()) };
}
}
impl Default for Arena {
fn default() -> Self {
Self::new()
}
}
pub struct TypedArena<T> {
arena: Arena,
_marker: PhantomData<T>,
}
impl<T> TypedArena<T> {
pub fn new(capacity: usize) -> Self {
let item_size = size_of::<T>().max(1);
let chunk_capacity = capacity
.saturating_mul(item_size)
.clamp(1024, MAX_CHUNK_SIZE);
TypedArena {
arena: Arena::with_capacity(chunk_capacity),
_marker: PhantomData,
}
}
pub fn reset(&mut self) {
self.arena.reset();
}
pub fn clear(&mut self) {
self.arena.clear();
}
pub fn stats(&self) -> ArenaStats {
self.arena.stats()
}
}
impl<T: 'static> TypedArena<T> {
pub fn alloc(&self, value: T) -> Result<&mut T> {
self.arena.alloc(value)
}
pub fn alloc_from_iter<I: IntoIterator<Item = T>>(&self, iter: I) -> Result<Vec<&mut T>> {
iter.into_iter().map(|value| self.alloc(value)).collect()
}
}
impl<T: Copy> TypedArena<T> {
pub fn alloc_slice(&self, values: &[T]) -> Result<&mut [T]> {
self.arena.alloc_slice(values)
}
}
impl<T> Default for TypedArena<T> {
fn default() -> Self {
Self::new(1024)
}
}
struct SyncArenaState {
chunks: Vec<Chunk>,
drops: Vec<DropEntry>,
}
pub struct SyncArena {
state: Mutex<SyncArenaState>,
chunk_size: AtomicUsize,
total_allocated: AtomicUsize,
bytes_in_use: AtomicUsize,
allocation_count: AtomicUsize,
peak_usage: AtomicUsize,
alignment_waste: AtomicUsize,
}
impl SyncArena {
pub fn new() -> Self {
Self::with_capacity(DEFAULT_CHUNK_SIZE)
}
pub fn with_capacity(chunk_size: usize) -> Self {
SyncArena {
state: Mutex::new(SyncArenaState {
chunks: Vec::new(),
drops: Vec::new(),
}),
chunk_size: AtomicUsize::new(chunk_size.max(1024)),
total_allocated: AtomicUsize::new(0),
bytes_in_use: AtomicUsize::new(0),
allocation_count: AtomicUsize::new(0),
peak_usage: AtomicUsize::new(0),
alignment_waste: AtomicUsize::new(0),
}
}
fn lock_state(&self) -> MutexGuard<'_, SyncArenaState> {
self.state
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
pub fn alloc_raw(&self, layout: Layout) -> Result<NonNull<u8>> {
let size = layout.size();
let align = layout.align().max(MIN_ALIGNMENT);
let mut state = self.lock_state();
if let Some(chunk) = state.chunks.last_mut() {
if let Some((ptr, padding)) = chunk.try_alloc(size, align) {
drop(state);
self.record_allocation(size, padding);
return Ok(ptr);
}
}
let chunk_size = self.chunk_size.load(Ordering::Relaxed);
let needed_size = size
.checked_add(align)
.ok_or_else(|| Error::InvalidInput("SyncArena: allocation size overflow".to_string()))?
.max(chunk_size);
let mut chunk = Chunk::new(needed_size).ok_or_else(|| {
Error::OperationFailed(format!(
"SyncArena: failed to allocate a {needed_size} byte chunk"
))
})?;
let (ptr, padding) = chunk.try_alloc(size, align).ok_or_else(|| {
Error::OperationFailed(format!(
"SyncArena: fresh chunk of {needed_size} bytes cannot hold a {size} byte allocation"
))
})?;
state.chunks.push(chunk);
drop(state);
self.total_allocated
.fetch_add(needed_size, Ordering::Relaxed);
self.record_allocation(size, padding);
let new_size = chunk_size.saturating_mul(2).min(MAX_CHUNK_SIZE);
self.chunk_size.store(new_size, Ordering::Relaxed);
Ok(ptr)
}
fn record_allocation(&self, size: usize, padding: usize) {
self.bytes_in_use.fetch_add(size, Ordering::Relaxed);
self.alignment_waste.fetch_add(padding, Ordering::Relaxed);
self.allocation_count.fetch_add(1, Ordering::Relaxed);
self.update_peak();
}
fn update_peak(&self) {
let current = self.bytes_in_use.load(Ordering::Relaxed);
let mut peak = self.peak_usage.load(Ordering::Relaxed);
while current > peak {
match self.peak_usage.compare_exchange_weak(
peak,
current,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(p) => peak = p,
}
}
}
#[allow(clippy::mut_from_ref)]
pub fn alloc<T: Send + 'static>(&self, value: T) -> Result<&mut T> {
let layout = Layout::new::<T>();
let ptr = self.alloc_raw(layout)?;
unsafe {
let typed_ptr = ptr.as_ptr() as *mut T;
typed_ptr.write(value);
if needs_drop::<T>() {
self.lock_state().drops.push(DropEntry {
addr: ptr,
drop_fn: drop_in_place_at::<T>,
});
}
Ok(&mut *typed_ptr)
}
}
pub fn stats(&self) -> ArenaStats {
let chunk_count = self.lock_state().chunks.len();
ArenaStats {
total_allocated: self.total_allocated.load(Ordering::Relaxed),
bytes_in_use: self.bytes_in_use.load(Ordering::Relaxed),
chunk_count,
allocation_count: self.allocation_count.load(Ordering::Relaxed),
peak_usage: self.peak_usage.load(Ordering::Relaxed),
alignment_waste: self.alignment_waste.load(Ordering::Relaxed),
}
}
pub fn reset(&mut self) {
let entries = std::mem::take(&mut self.lock_state().drops);
unsafe { run_drop_entries(entries) };
let kept_capacity = {
let mut state = self.lock_state();
let chunks = &mut state.chunks;
if let Some(best_idx) = chunks
.iter()
.enumerate()
.max_by_key(|(_, chunk)| chunk.capacity)
.map(|(idx, _)| idx)
{
let mut kept = chunks.swap_remove(best_idx);
kept.reset();
chunks.clear();
let capacity = kept.capacity;
chunks.push(kept);
capacity
} else {
0
}
};
self.total_allocated.store(kept_capacity, Ordering::Relaxed);
self.bytes_in_use.store(0, Ordering::Relaxed);
self.allocation_count.store(0, Ordering::Relaxed);
self.alignment_waste.store(0, Ordering::Relaxed);
}
pub fn clear(&mut self) {
let entries = std::mem::take(&mut self.lock_state().drops);
unsafe { run_drop_entries(entries) };
self.lock_state().chunks.clear();
self.chunk_size.store(DEFAULT_CHUNK_SIZE, Ordering::Relaxed);
self.total_allocated.store(0, Ordering::Relaxed);
self.bytes_in_use.store(0, Ordering::Relaxed);
self.allocation_count.store(0, Ordering::Relaxed);
self.peak_usage.store(0, Ordering::Relaxed);
self.alignment_waste.store(0, Ordering::Relaxed);
}
}
impl Drop for SyncArena {
fn drop(&mut self) {
let entries = std::mem::take(&mut self.lock_state().drops);
unsafe { run_drop_entries(entries) };
}
}
impl Default for SyncArena {
fn default() -> Self {
Self::new()
}
}
pub struct ScopedArena<'a> {
arena: &'a mut Arena,
checkpoint: ArenaCheckpoint,
}
impl<'a> ScopedArena<'a> {
pub fn new(arena: &'a mut Arena) -> Self {
let checkpoint = arena.checkpoint();
ScopedArena { arena, checkpoint }
}
pub fn alloc<T: 'static>(&self, value: T) -> Result<&mut T> {
self.arena.alloc(value)
}
pub fn alloc_slice<T: Copy>(&self, values: &[T]) -> Result<&mut [T]> {
self.arena.alloc_slice(values)
}
pub fn bytes_in_use(&self) -> usize {
self.arena.bytes_in_use()
}
}
impl Drop for ScopedArena<'_> {
fn drop(&mut self) {
let checkpoint = self.checkpoint;
self.arena.restore(checkpoint);
}
}
pub struct ArenaVec<'a, T> {
data: &'a mut [MaybeUninit<T>],
len: usize,
}
impl<'a, T: Copy> ArenaVec<'a, T> {
pub fn with_capacity(arena: &'a Arena, capacity: usize) -> Result<Self> {
let data = arena.alloc_slice_maybe_uninit::<T>(capacity)?;
Ok(ArenaVec { data, len: 0 })
}
pub fn push(&mut self, value: T) -> Result<()> {
if self.len >= self.data.len() {
return Err(Error::OperationFailed(format!(
"ArenaVec is full ({} elements); it cannot grow",
self.data.len()
)));
}
self.data[self.len].write(value);
self.len += 1;
Ok(())
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn as_slice(&self) -> &[T] {
unsafe { slice::from_raw_parts(self.data.as_ptr() as *const T, self.len) }
}
pub fn as_mut_slice(&mut self) -> &mut [T] {
unsafe { slice::from_raw_parts_mut(self.data.as_mut_ptr() as *mut T, self.len) }
}
pub fn capacity(&self) -> usize {
self.data.len()
}
}
const _: () = {
const fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<SyncArena>();
};
#[cfg(test)]
mod tests {
#![allow(clippy::approx_constant)]
use super::*;
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
struct DropCounter(Arc<AtomicUsize>);
impl Drop for DropCounter {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
#[test]
fn test_arena_basic() {
let arena = Arena::new();
let a = arena.alloc(42i32).expect("allocation should succeed");
let b = arena.alloc(3.14f64).expect("allocation should succeed");
let c = arena
.alloc("hello".to_string())
.expect("allocation should succeed");
assert_eq!(*a, 42);
assert_eq!(*b, 3.14);
assert_eq!(&*c, "hello");
let stats = arena.stats();
assert_eq!(stats.allocation_count, 3);
assert!(stats.bytes_in_use > 0);
}
#[test]
fn test_arena_slice() {
let arena = Arena::new();
let slice = arena
.alloc_slice(&[1.0, 2.0, 3.0, 4.0, 5.0])
.expect("allocation should succeed");
assert_eq!(slice.len(), 5);
assert_eq!(slice[0], 1.0);
assert_eq!(slice[4], 5.0);
slice[2] = 100.0;
assert_eq!(slice[2], 100.0);
}
#[test]
fn test_arena_string() {
let arena = Arena::new();
let s1 = arena.alloc_str("hello").expect("allocation should succeed");
let s2 = arena.alloc_str("world").expect("allocation should succeed");
assert_eq!(s1, "hello");
assert_eq!(s2, "world");
}
#[test]
fn test_arena_reset() {
let mut arena = Arena::new();
for i in 0..100 {
arena.alloc(i).expect("allocation should succeed");
}
let stats_before = arena.stats();
assert_eq!(stats_before.allocation_count, 100);
arena.reset();
let stats_after = arena.stats();
assert_eq!(stats_after.allocation_count, 0);
assert_eq!(stats_after.bytes_in_use, 0);
assert!(stats_after.total_allocated > 0);
assert_eq!(stats_after.chunk_count, 1);
}
#[test]
fn test_arena_reset_runs_destructors() {
let counter = Arc::new(AtomicUsize::new(0));
let mut arena = Arena::new();
for _ in 0..10 {
arena
.alloc(DropCounter(Arc::clone(&counter)))
.expect("allocation should succeed");
}
assert_eq!(counter.load(Ordering::SeqCst), 0);
arena.reset();
assert_eq!(counter.load(Ordering::SeqCst), 10);
arena.reset();
assert_eq!(counter.load(Ordering::SeqCst), 10);
}
#[test]
fn test_arena_drop_runs_destructors() {
let counter = Arc::new(AtomicUsize::new(0));
{
let arena = Arena::new();
for _ in 0..5 {
arena
.alloc(DropCounter(Arc::clone(&counter)))
.expect("allocation should succeed");
}
assert_eq!(counter.load(Ordering::SeqCst), 0);
}
assert_eq!(counter.load(Ordering::SeqCst), 5);
}
#[test]
fn test_arena_reset_does_not_grow_unboundedly() {
let mut arena = Arena::new();
let mut previous = 0usize;
for cycle in 0..5 {
for i in 0..2000i64 {
arena.alloc(i).expect("allocation should succeed");
}
arena.reset();
let total = arena.stats().total_allocated;
if cycle > 0 {
assert_eq!(
total, previous,
"arena memory grew across reset cycles: {previous} -> {total}"
);
}
previous = total;
}
}
#[test]
fn test_arena_clear() {
let mut arena = Arena::new();
for i in 0..100 {
arena.alloc(i).expect("allocation should succeed");
}
arena.clear();
let stats = arena.stats();
assert_eq!(stats.total_allocated, 0);
assert_eq!(stats.bytes_in_use, 0);
assert_eq!(stats.chunk_count, 0);
}
#[test]
fn test_typed_arena() {
let arena: TypedArena<f64> = TypedArena::new(1024);
let values: Vec<&mut f64> = (0..100)
.map(|i| arena.alloc(i as f64).expect("allocation should succeed"))
.collect();
for (i, v) in values.iter().enumerate() {
assert_eq!(**v, i as f64);
}
}
#[test]
fn test_typed_arena_slice() {
let arena: TypedArena<i32> = TypedArena::new(1024);
let data = vec![1, 2, 3, 4, 5];
let slice = arena.alloc_slice(&data).expect("allocation should succeed");
assert_eq!(slice, &[1, 2, 3, 4, 5]);
}
#[test]
fn test_typed_arena_capacity_overflow_is_clamped() {
let arena: TypedArena<u64> = TypedArena::new(usize::MAX);
let value = arena.alloc(7u64).expect("allocation should succeed");
assert_eq!(*value, 7);
}
#[test]
fn test_sync_arena() {
let arena = SyncArena::new();
let a = arena.alloc(42i32).expect("operation should succeed");
let b = arena.alloc(3.14f64).expect("operation should succeed");
assert_eq!(*a, 42);
assert_eq!(*b, 3.14);
let stats = arena.stats();
assert_eq!(stats.allocation_count, 2);
}
#[test]
fn test_sync_arena_threaded() {
use std::thread;
let arena = Arc::new(SyncArena::new());
let mut handles = vec![];
for t in 0..4 {
let arena_clone = Arc::clone(&arena);
handles.push(thread::spawn(move || {
for i in 0..100 {
arena_clone
.alloc(t * 100 + i)
.expect("allocation should succeed");
}
}));
}
for handle in handles {
handle.join().expect("operation should succeed");
}
let stats = arena.stats();
assert_eq!(stats.allocation_count, 400);
}
#[test]
fn test_sync_arena_runs_destructors() {
let counter = Arc::new(AtomicUsize::new(0));
let mut arena = SyncArena::new();
for _ in 0..8 {
arena
.alloc(DropCounter(Arc::clone(&counter)))
.expect("allocation should succeed");
}
assert_eq!(counter.load(Ordering::SeqCst), 0);
arena.reset();
assert_eq!(counter.load(Ordering::SeqCst), 8);
arena.clear();
assert_eq!(counter.load(Ordering::SeqCst), 8);
}
#[test]
fn test_arena_large_allocation() {
let arena = Arena::new();
let large: Vec<f64> = (0..10000).map(|i| i as f64).collect();
let slice = arena
.alloc_slice(&large)
.expect("allocation should succeed");
assert_eq!(slice.len(), 10000);
assert_eq!(slice[0], 0.0);
assert_eq!(slice[9999], 9999.0);
}
#[test]
fn test_arena_alignment() {
let arena = Arena::new();
let _byte = arena.alloc(1u8).expect("allocation should succeed");
let int_ptr = arena.alloc(42i32).expect("allocation should succeed");
let double_ptr = arena.alloc(3.14f64).expect("allocation should succeed");
let int_addr = int_ptr as *const i32 as usize;
let double_addr = double_ptr as *const f64 as usize;
assert_eq!(int_addr % std::mem::align_of::<i32>(), 0);
assert_eq!(double_addr % std::mem::align_of::<f64>(), 0);
assert!(arena.stats().alignment_waste > 0);
}
#[test]
fn test_arena_vec() {
let arena = Arena::new();
let mut vec: ArenaVec<i32> =
ArenaVec::with_capacity(&arena, 100).expect("allocation should succeed");
for i in 0..50 {
vec.push(i).expect("push should succeed");
}
assert_eq!(vec.len(), 50);
assert_eq!(vec.capacity(), 100);
assert_eq!(vec.as_slice()[0], 0);
assert_eq!(vec.as_slice()[49], 49);
}
#[test]
fn test_arena_vec_full_reports_error() {
let arena = Arena::new();
let mut vec: ArenaVec<u16> =
ArenaVec::with_capacity(&arena, 4).expect("allocation should succeed");
for i in 0..4u16 {
vec.push(i).expect("push should succeed");
}
assert!(
vec.push(4).is_err(),
"overflowing push must report an error"
);
assert_eq!(vec.as_slice(), &[0, 1, 2, 3]);
}
#[test]
fn test_scoped_arena_rolls_back() {
let counter = Arc::new(AtomicUsize::new(0));
let mut arena = Arena::new();
let persistent = arena.alloc(7u64).expect("allocation should succeed");
assert_eq!(*persistent, 7);
let baseline = arena.bytes_in_use();
{
let scope = ScopedArena::new(&mut arena);
for _ in 0..4 {
scope
.alloc(DropCounter(Arc::clone(&counter)))
.expect("allocation should succeed");
}
assert!(scope.bytes_in_use() > baseline);
}
assert_eq!(counter.load(Ordering::SeqCst), 4);
assert_eq!(arena.bytes_in_use(), baseline);
}
#[test]
fn test_arena_stats() {
let arena = Arena::new();
let stats = arena.stats();
assert_eq!(stats.allocation_count, 0);
assert_eq!(stats.bytes_in_use, 0);
for _ in 0..10 {
arena.alloc(42i64).expect("allocation should succeed");
}
let stats = arena.stats();
assert_eq!(stats.allocation_count, 10);
assert!(stats.bytes_in_use >= 80); assert!(stats.peak_usage >= stats.bytes_in_use);
}
}