use core::cell::Cell;
use core::marker::PhantomData;
use core::mem::{MaybeUninit, size_of};
use core::ptr::{NonNull, drop_in_place};
use crate::sys;
pub struct TypedArenaLazy<T> {
base: NonNull<MaybeUninit<T>>,
capacity: usize,
offset: Cell<usize>,
commit: Cell<usize>,
#[allow(clippy::type_complexity)]
_invariant: PhantomData<(*const (), fn(T) -> T)>,
}
impl<T> TypedArenaLazy<T> {
#[must_use]
pub fn new(capacity: usize) -> Self {
if capacity == 0 || size_of::<T>() == 0 {
return Self {
base: NonNull::dangling(),
capacity,
offset: Cell::new(0),
commit: Cell::new(0),
_invariant: PhantomData,
};
}
let size_bytes =
capacity.checked_mul(size_of::<T>()).expect("TypedArenaLazy: capacity overflow");
let base = sys::reserve(size_bytes).expect("TypedArenaLazy: out of virtual address space");
Self {
base: base.cast(),
capacity,
offset: Cell::new(0),
commit: Cell::new(0),
_invariant: PhantomData,
}
}
#[allow(clippy::mut_from_ref)]
pub fn alloc_raw(&self, value: T) -> Option<&mut T> {
if size_of::<T>() == 0 {
unsafe {
let dangling = NonNull::<T>::dangling();
dangling.as_ptr().write(value);
return Some(&mut *dangling.as_ptr());
}
}
let idx = self.offset.get();
if idx >= self.capacity {
return None;
}
let required_bytes = (idx + 1) * size_of::<T>();
if required_bytes > self.commit.get() {
self.try_commit(required_bytes)?;
}
unsafe {
let slot = self.base.as_ptr().add(idx);
let r = (&mut *slot).write(value);
self.offset.set(idx + 1);
Some(r)
}
}
#[cold]
fn try_commit(&self, required_bytes: usize) -> Option<()> {
let page = sys::page_size();
let current = self.commit.get();
let total_bytes = self.capacity * size_of::<T>();
let needed = required_bytes.checked_next_multiple_of(page)?.min(total_bytes);
if needed <= current {
return Some(());
}
unsafe {
let addr = NonNull::new_unchecked(self.base.as_ptr().cast::<u8>().add(current));
sys::commit(addr, needed - current).ok()?;
}
self.commit.set(needed);
Some(())
}
pub fn len(&self) -> usize {
self.offset.get()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn reset(&mut self) {
let offset = self.offset.get();
unsafe {
let start = self.base.as_ptr().cast::<T>();
for i in (0..offset).rev() {
drop_in_place(start.add(i));
}
}
self.offset.set(0);
}
}
impl<T> Drop for TypedArenaLazy<T> {
fn drop(&mut self) {
let offset = self.offset.get();
unsafe {
let start = self.base.as_ptr().cast::<T>();
for i in (0..offset).rev() {
drop_in_place(start.add(i));
}
if self.capacity > 0 && size_of::<T>() > 0 {
let total_bytes = self.capacity * size_of::<T>();
sys::release(self.base.cast(), total_bytes);
}
}
}
}
#[cfg(test)]
mod tests {
use core::ptr;
use super::*;
struct DropTracker<'a> {
id: u32,
order: &'a Cell<Vec<u32>>,
}
impl Drop for DropTracker<'_> {
fn drop(&mut self) {
let mut v = self.order.take();
v.push(self.id);
self.order.set(v);
}
}
#[test]
fn drop_order_reverse_allocation() {
let order = Cell::new(Vec::new());
let arena = TypedArenaLazy::<DropTracker>::new(10);
arena.alloc_raw(DropTracker { id: 1, order: &order }).unwrap();
arena.alloc_raw(DropTracker { id: 2, order: &order }).unwrap();
arena.alloc_raw(DropTracker { id: 3, order: &order }).unwrap();
drop(arena);
assert_eq!(order.take(), vec![3, 2, 1]);
}
#[test]
fn reset_drops_values_and_reuses_memory() {
let order = Cell::new(Vec::new());
let mut arena = TypedArenaLazy::<DropTracker>::new(10);
let ptr1 = ptr::from_mut(arena.alloc_raw(DropTracker { id: 1, order: &order }).unwrap());
let _ptr2 = ptr::from_mut(arena.alloc_raw(DropTracker { id: 2, order: &order }).unwrap());
arena.reset();
assert_eq!(order.take(), vec![2, 1]);
let ptr3 = ptr::from_mut(arena.alloc_raw(DropTracker { id: 3, order: &order }).unwrap());
assert_eq!(ptr1, ptr3);
drop(arena);
assert_eq!(order.take(), vec![3]);
}
#[test]
fn no_double_drop_after_reset() {
struct Counter<'a>(&'a Cell<u32>);
impl Drop for Counter<'_> {
fn drop(&mut self) {
self.0.set(self.0.get() + 1);
}
}
let count = Cell::new(0u32);
let mut arena = TypedArenaLazy::<Counter>::new(10);
arena.alloc_raw(Counter(&count)).unwrap();
arena.alloc_raw(Counter(&count)).unwrap();
arena.reset();
assert_eq!(count.get(), 2);
arena.alloc_raw(Counter(&count)).unwrap();
drop(arena);
assert_eq!(count.get(), 3); }
#[test]
fn oom_does_not_advance_offset() {
let arena = TypedArenaLazy::<u64>::new(1); assert!(arena.alloc_raw(1u64).is_some());
assert_eq!(arena.len(), 1);
assert!(arena.alloc_raw(2u64).is_none());
assert_eq!(arena.len(), 1);
}
#[test]
fn zst_does_not_advance_offset() {
let arena = TypedArenaLazy::<()>::new(0);
assert!(arena.alloc_raw(()).is_some());
assert_eq!(arena.len(), 0);
assert!(arena.alloc_raw(()).is_some());
assert_eq!(arena.len(), 0);
}
#[test]
fn allocated_value_is_valid() {
let arena = TypedArenaLazy::<String>::new(1);
let s = arena.alloc_raw("hello".to_string()).unwrap();
assert_eq!(s, "hello");
s.push_str(" world");
assert_eq!(s, "hello world");
}
#[test]
#[should_panic(expected = "TypedArenaLazy: capacity overflow")]
fn new_panics_on_capacity_overflow() {
let _arena = TypedArenaLazy::<u16>::new(usize::MAX);
}
}