use core::alloc::Layout;
use core::cell::Cell;
use core::mem::MaybeUninit;
use core::ptr::NonNull;
use crate::memory::tag::{MemTag, Realm};
const ARENA_ALIGN: usize = 64;
pub struct Arena {
ptr: NonNull<u8>,
cap: usize,
used: Cell<usize>,
peak: Cell<usize>,
overflows: Cell<u32>,
tag: Option<MemTag>,
}
#[expect(
clippy::mut_from_ref,
reason = "allocations never overlap and reset takes &mut self, so the general-case lint does not apply"
)]
impl Arena {
pub fn with_capacity(bytes: usize) -> Self {
Self::new(bytes, None)
}
pub fn tagged(bytes: usize, tag: MemTag) -> Self {
crate::memory::ledger().add(tag, Realm::Host, bytes as u64);
Self::new(bytes, Some(tag))
}
fn new(bytes: usize, tag: Option<MemTag>) -> Self {
if bytes == 0 {
return Self {
ptr: NonNull::dangling(),
cap: 0,
used: Cell::new(0),
peak: Cell::new(0),
overflows: Cell::new(0),
tag,
};
}
let layout = Layout::from_size_align(bytes, ARENA_ALIGN).expect("arena layout");
let ptr = unsafe { alloc::alloc::alloc(layout) };
let Some(ptr) = NonNull::new(ptr) else {
alloc::alloc::handle_alloc_error(layout)
};
Self {
ptr,
cap: bytes,
used: Cell::new(0),
peak: Cell::new(0),
overflows: Cell::new(0),
tag,
}
}
pub fn capacity(&self) -> usize {
self.cap
}
pub fn used(&self) -> usize {
self.used.get()
}
pub fn remaining(&self) -> usize {
self.cap - self.used.get()
}
pub fn peak(&self) -> usize {
self.peak.get()
}
pub fn overflows(&self) -> u32 {
self.overflows.get()
}
pub fn clear_overflows(&self) {
self.overflows.set(0);
}
pub fn reset(&mut self) {
self.used.set(0);
}
pub fn alloc<T: Copy>(&self, value: T) -> Option<&mut T> {
let ptr = self.bump(size_of::<T>(), align_of::<T>())?.cast::<T>();
unsafe {
ptr.write(value);
Some(&mut *ptr.as_ptr())
}
}
#[cfg(test)]
pub(crate) fn alloc_slice<T: Copy>(&self, len: usize, value: T) -> Option<&mut [T]> {
let slice = self.uninit_slice::<T>(len)?;
for slot in slice.iter_mut() {
slot.write(value);
}
Some(unsafe { assume_init_mut(slice) })
}
#[cfg(test)]
pub(crate) fn alloc_slice_copy<T: Copy>(&self, src: &[T]) -> Option<&mut [T]> {
let slice = self.uninit_slice::<T>(src.len())?;
for (slot, value) in slice.iter_mut().zip(src) {
slot.write(*value);
}
Some(unsafe { assume_init_mut(slice) })
}
pub fn vec<T: Copy>(&self, capacity: usize) -> Option<ArenaVec<'_, T>> {
Some(ArenaVec {
buf: self.uninit_slice::<T>(capacity)?,
len: 0,
})
}
fn uninit_slice<T>(&self, len: usize) -> Option<&mut [MaybeUninit<T>]> {
let bytes = size_of::<T>().saturating_mul(len);
let ptr = self.bump(bytes, align_of::<T>())?.cast::<MaybeUninit<T>>();
Some(unsafe { core::slice::from_raw_parts_mut(ptr.as_ptr(), len) })
}
fn bump(&self, size: usize, align: usize) -> Option<NonNull<u8>> {
let carved = self.try_bump(size, align);
if carved.is_none() {
self.overflows.set(self.overflows.get().saturating_add(1));
}
carved
}
fn try_bump(&self, size: usize, align: usize) -> Option<NonNull<u8>> {
if align > ARENA_ALIGN || self.cap == 0 {
return None;
}
let start = self.used.get().checked_next_multiple_of(align)?;
let end = start.checked_add(size)?;
if end > self.cap {
return None;
}
self.used.set(end);
if end > self.peak.get() {
self.peak.set(end);
}
Some(unsafe { NonNull::new_unchecked(self.ptr.as_ptr().add(start)) })
}
}
impl Drop for Arena {
fn drop(&mut self) {
if let Some(tag) = self.tag {
crate::memory::ledger().release(tag, Realm::Host, self.cap as u64);
}
if self.cap == 0 {
return;
}
let layout = Layout::from_size_align(self.cap, ARENA_ALIGN).expect("arena layout");
unsafe { alloc::alloc::dealloc(self.ptr.as_ptr(), layout) };
}
}
unsafe impl Send for Arena {}
pub struct ArenaVec<'a, T: Copy> {
buf: &'a mut [MaybeUninit<T>],
len: usize,
}
impl<T: Copy> ArenaVec<'_, T> {
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn capacity(&self) -> usize {
self.buf.len()
}
pub(crate) fn is_full(&self) -> bool {
self.len == self.buf.len()
}
#[must_use]
pub fn push(&mut self, value: T) -> bool {
if self.is_full() {
return false;
}
self.buf[self.len].write(value);
self.len += 1;
true
}
pub fn extend(&mut self, values: impl IntoIterator<Item = T>) -> usize {
let before = self.len;
for value in values {
if !self.push(value) {
break;
}
}
self.len - before
}
pub fn clear(&mut self) {
self.len = 0;
}
pub fn as_slice(&self) -> &[T] {
unsafe { assume_init_ref(&self.buf[..self.len]) }
}
pub fn as_mut_slice(&mut self) -> &mut [T] {
unsafe { assume_init_mut(&mut self.buf[..self.len]) }
}
}
impl<T: Copy> core::ops::Deref for ArenaVec<'_, T> {
type Target = [T];
fn deref(&self) -> &[T] {
self.as_slice()
}
}
impl<T: Copy> core::ops::DerefMut for ArenaVec<'_, T> {
fn deref_mut(&mut self) -> &mut [T] {
self.as_mut_slice()
}
}
impl<T: Copy + core::fmt::Debug> core::fmt::Debug for ArenaVec<'_, T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.as_slice().fmt(f)
}
}
unsafe fn assume_init_ref<T>(slice: &[MaybeUninit<T>]) -> &[T] {
unsafe { &*(slice as *const [MaybeUninit<T>] as *const [T]) }
}
unsafe fn assume_init_mut<T>(slice: &mut [MaybeUninit<T>]) -> &mut [T] {
unsafe { &mut *(slice as *mut [MaybeUninit<T>] as *mut [T]) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn allocations_come_back_with_their_values() {
let arena = Arena::with_capacity(4096);
let a = arena.alloc(7u32).expect("fits");
let b = arena.alloc_slice(4, 1u16).expect("fits");
let c = arena.alloc_slice_copy(&[9u64, 8, 7]).expect("fits");
assert_eq!(*a, 7);
assert_eq!(b, &[1, 1, 1, 1]);
assert_eq!(c, &[9, 8, 7]);
}
#[test]
fn separate_allocations_do_not_overlap() {
let arena = Arena::with_capacity(4096);
let first = arena.alloc_slice(8, 0u32).expect("fits");
let second = arena.alloc_slice(8, 0u32).expect("fits");
first.fill(0xAAAA_AAAA);
second.fill(0x5555_5555);
assert!(first.iter().all(|&v| v == 0xAAAA_AAAA));
assert!(second.iter().all(|&v| v == 0x5555_5555));
}
#[test]
fn allocations_are_aligned_for_their_type() {
let arena = Arena::with_capacity(4096);
let _ = arena.alloc(1u8).expect("fits");
let wide = arena.alloc(1u128).expect("fits");
assert!((wide as *const u128).is_aligned());
let _ = arena.alloc(1u8).expect("fits");
let slice = arena.alloc_slice(3, 0u64).expect("fits");
assert!(slice.as_ptr().is_aligned());
}
#[test]
fn a_full_arena_declines_rather_than_panicking() {
let arena = Arena::with_capacity(64);
assert!(arena.alloc_slice(8, 0u64).is_some());
assert!(arena.alloc(0u8).is_none());
assert_eq!(arena.remaining(), 0);
}
#[test]
fn an_empty_arena_declines_everything() {
let arena = Arena::with_capacity(0);
assert_eq!(arena.capacity(), 0);
assert!(arena.alloc(1u8).is_none());
}
#[test]
fn reset_hands_the_whole_arena_back() {
let mut arena = Arena::with_capacity(128);
{
let slice = arena.alloc_slice(16, 0u64).expect("fits");
assert_eq!(slice.len(), 16);
}
assert_eq!(arena.used(), 128);
assert!(arena.alloc(0u8).is_none());
arena.reset();
assert_eq!(arena.used(), 0);
assert!(arena.alloc_slice(16, 0u64).is_some());
}
#[test]
fn peak_survives_a_reset() {
let mut arena = Arena::with_capacity(1024);
let _ = arena.alloc_slice(64, 0u8).expect("fits");
arena.reset();
let _ = arena.alloc_slice(8, 0u8).expect("fits");
assert_eq!(arena.used(), 8);
assert_eq!(arena.peak(), 64);
}
#[test]
fn an_over_aligned_type_is_declined() {
#[repr(align(128))]
#[derive(Clone, Copy)]
struct Overaligned(u8);
let arena = Arena::with_capacity(4096);
let value = Overaligned(7);
assert_eq!(value.0, 7);
assert!(arena.alloc(value).is_none());
}
#[test]
fn a_vector_pushes_into_its_reservation() {
let arena = Arena::with_capacity(4096);
let mut v = arena.vec::<u32>(4).expect("fits");
assert!(v.is_empty());
for i in 0..4 {
assert!(v.push(i));
}
assert!(v.is_full());
assert_eq!(v.as_slice(), &[0, 1, 2, 3]);
assert_eq!(v.len(), 4);
}
#[test]
fn a_vector_declines_pushes_past_its_reservation() {
let arena = Arena::with_capacity(4096);
let mut v = arena.vec::<u8>(2).expect("fits");
assert!(v.push(1));
assert!(v.push(2));
assert!(!v.push(3));
assert_eq!(v.as_slice(), &[1, 2]);
}
#[test]
fn extend_reports_what_it_took() {
let arena = Arena::with_capacity(4096);
let mut v = arena.vec::<u16>(3).expect("fits");
assert_eq!(v.extend([1, 2, 3, 4, 5]), 3);
assert_eq!(v.as_slice(), &[1, 2, 3]);
}
#[test]
fn a_vector_sorts_and_reads_back_through_the_slice() {
let arena = Arena::with_capacity(4096);
let mut v = arena.vec::<u32>(5).expect("fits");
assert_eq!(v.extend([5, 3, 1, 4, 2]), 5);
v.sort_unstable();
assert_eq!(&*v, &[1, 2, 3, 4, 5]);
}
#[test]
fn clearing_a_vector_keeps_its_reservation() {
let arena = Arena::with_capacity(4096);
let mut v = arena.vec::<u8>(2).expect("fits");
assert!(v.push(1));
v.clear();
assert!(v.is_empty());
assert!(v.push(2));
assert_eq!(v.as_slice(), &[2]);
}
#[test]
fn two_vectors_hold_separate_reservations() {
let arena = Arena::with_capacity(4096);
let mut a = arena.vec::<u32>(2).expect("fits");
let mut b = arena.vec::<u32>(2).expect("fits");
assert_eq!(a.extend([1, 2]), 2);
assert_eq!(b.extend([3, 4]), 2);
assert_eq!(a.as_slice(), &[1, 2]);
assert_eq!(b.as_slice(), &[3, 4]);
}
#[test]
fn a_tagged_arena_reports_its_reservation_for_as_long_as_it_lives() {
const BYTES: usize = 8192;
let held = || {
crate::memory::ledger()
.usage(MemTag::Scratch, Realm::Host)
.bytes
};
let before = held();
{
let arena = Arena::tagged(BYTES, MemTag::Scratch);
assert_eq!(held(), before + BYTES as u64);
let _ = arena.alloc_slice(16, 0u8).expect("fits");
assert_eq!(held(), before + BYTES as u64);
}
assert_eq!(held(), before);
}
#[test]
fn an_untagged_arena_reports_nothing() {
let held = || crate::memory::ledger().usage(MemTag::Ui, Realm::Host).bytes;
let before = held();
let _arena = Arena::with_capacity(8192);
assert_eq!(held(), before);
}
#[test]
fn a_reservation_larger_than_the_arena_is_declined() {
let arena = Arena::with_capacity(64);
assert!(arena.vec::<u64>(1024).is_none());
}
#[test]
fn declined_requests_are_counted() {
let arena = Arena::with_capacity(64);
assert_eq!(arena.overflows(), 0);
assert!(arena.alloc_slice(8, 0u64).is_some());
assert_eq!(arena.overflows(), 0, "a request that fits counts nothing");
assert!(arena.alloc(0u8).is_none());
assert!(arena.vec::<u32>(4).is_none());
assert_eq!(arena.overflows(), 2);
arena.clear_overflows();
assert_eq!(arena.overflows(), 0);
}
#[test]
fn reset_keeps_the_sizing_evidence() {
let mut arena = Arena::with_capacity(64);
let _ = arena.alloc_slice(8, 0u64).expect("fits");
assert!(arena.alloc(0u8).is_none());
arena.reset();
assert_eq!(arena.used(), 0, "the cursor rewinds");
assert_eq!(arena.peak(), 64, "the peak does not");
assert_eq!(arena.overflows(), 1, "nor does the overflow count");
}
#[test]
fn every_decline_path_reaches_the_counter() {
#[repr(align(128))]
#[derive(Clone, Copy)]
struct Overaligned(u8);
let arena = Arena::with_capacity(4096);
let value = Overaligned(7);
assert_eq!(value.0, 7);
assert!(arena.alloc(value).is_none());
assert!(arena.vec::<u64>(usize::MAX).is_none());
assert_eq!(arena.overflows(), 2);
let empty = Arena::with_capacity(0);
assert!(empty.alloc(1u8).is_none());
assert_eq!(empty.overflows(), 1);
}
}