use std::alloc::{self, Layout};
use std::cell::RefCell;
use std::marker::PhantomData;
use std::mem;
use std::ptr::NonNull;
const DEFAULT_BLOCK_SIZE: usize = 64 * 1024;
pub struct Arena {
chunks: RefCell<Vec<Chunk>>,
block_size: usize,
}
impl Arena {
pub fn new() -> Self {
Self {
chunks: RefCell::new(Vec::new()),
block_size: DEFAULT_BLOCK_SIZE,
}
}
pub fn with_capacity(block_size: usize) -> Self {
Self {
chunks: RefCell::new(Vec::new()),
block_size,
}
}
#[allow(clippy::mut_from_ref)]
pub fn allocate_with<T>(&self, init: impl FnOnce() -> T) -> &mut T {
let layout = Layout::new::<T>();
assert!(
layout.size() > 0,
"cannot allocate zero-sized types in Arena"
);
let ptr = self.alloc_raw(layout);
unsafe {
let typed = ptr.as_ptr().cast::<T>();
typed.write(init());
&mut *typed
}
}
pub fn allocate_slice<T: Copy>(&self, values: &[T]) -> &[T] {
if values.is_empty() {
return &[];
}
let layout = Layout::from_size_align(mem::size_of_val(values), mem::align_of::<T>())
.expect("invalid slice layout");
assert!(
layout.size() > 0,
"cannot allocate zero-sized types in Arena"
);
let ptr = self.alloc_raw(layout);
unsafe {
let typed = ptr.as_ptr().cast::<T>();
std::ptr::copy_nonoverlapping(values.as_ptr(), typed, values.len());
std::slice::from_raw_parts(typed, values.len())
}
}
fn alloc_raw(&self, layout: Layout) -> NonNull<u8> {
let mut chunks = self.chunks.borrow_mut();
if let Some(chunk) = chunks.last_mut()
&& let Some(ptr) = chunk.try_alloc(layout)
{
return ptr;
}
let size = layout.size().max(self.block_size);
let align = layout.align();
let mut new_chunk = Chunk::new(size, align);
let ptr = new_chunk
.try_alloc(layout)
.expect("new chunk should fit any layout up to its size");
chunks.push(new_chunk);
ptr
}
pub fn reset(&self) {
let mut chunks = self.chunks.borrow_mut();
chunks.truncate(1);
if let Some(first) = chunks.first_mut() {
first.offset = 0;
}
}
pub fn chunk_count(&self) -> usize {
self.chunks.borrow().len()
}
}
impl Default for Arena {
fn default() -> Self {
Self::new()
}
}
impl Drop for Arena {
fn drop(&mut self) {
}
}
struct Chunk {
memory: NonNull<u8>,
size: usize,
align: usize,
offset: usize,
}
impl Chunk {
fn new(size: usize, align: usize) -> Self {
let layout = Layout::from_size_align(size, align).expect("invalid chunk layout");
let memory = unsafe { NonNull::new_unchecked(alloc::alloc(layout)) };
Self {
memory,
size,
align,
offset: 0,
}
}
fn try_alloc(&mut self, layout: Layout) -> Option<NonNull<u8>> {
if layout.align() > self.align {
return None;
}
let aligned_offset = align_up(self.offset, layout.align());
let end = aligned_offset.checked_add(layout.size())?;
if end > self.size {
return None;
}
let ptr = unsafe { NonNull::new_unchecked(self.memory.as_ptr().add(aligned_offset)) };
self.offset = end;
Some(ptr)
}
}
impl Drop for Chunk {
fn drop(&mut self) {
let layout = Layout::from_size_align(self.size, self.align).expect("invalid chunk layout");
unsafe {
alloc::dealloc(self.memory.as_ptr(), layout);
}
}
}
fn align_up(offset: usize, align: usize) -> usize {
assert!(align.is_power_of_two(), "alignment must be a power of two");
(offset + align - 1) & !(align - 1)
}
pub struct OwnedExpr<T> {
#[allow(dead_code)]
arena: Box<Arena>,
root: *mut T,
_marker: PhantomData<T>,
}
impl<T> OwnedExpr<T> {
pub unsafe fn new(arena: Box<Arena>, root: *mut T) -> Self {
Self {
arena,
root,
_marker: PhantomData,
}
}
pub fn root(&self) -> &T {
unsafe { &*self.root }
}
}
unsafe impl<T: Send> Send for OwnedExpr<T> {}
unsafe impl<T: Sync> Sync for OwnedExpr<T> {}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
mod reset {
use super::*;
#[test]
fn reset_keeps_first_chunk() {
let arena = Arena::with_capacity(64);
for i in 0..100u64 {
arena.allocate_with(|| i);
}
assert!(arena.chunk_count() > 1);
arena.reset();
assert_eq!(arena.chunk_count(), 1);
}
#[test]
fn reset_allows_reuse() {
let arena = Arena::new();
let _ = arena.allocate_with(|| 1u64);
arena.reset();
let value = arena.allocate_with(|| 2u64);
assert_eq!(*value, 2);
}
#[test]
fn reset_reuse_steady_state_allocates_nothing() {
let arena = Arena::with_capacity(4096);
for round in 0..1000 {
for i in 0..50u64 {
let v = arena.allocate_with(|| i + round);
assert_eq!(*v, i + round);
}
arena.reset();
}
assert_eq!(arena.chunk_count(), 1);
}
#[test]
fn overaligned_allocation_gets_own_chunk() {
let arena = Arena::new();
let _ = arena.allocate_with(|| 1u8);
#[repr(align(64))]
#[derive(Copy, Clone)]
struct Wide(u64);
let w = arena.allocate_with(|| Wide(7));
assert_eq!(w as *const Wide as usize % 64, 0);
assert_eq!(w.0, 7);
}
}
mod simple {
use super::*;
#[test]
fn allocate_single_integer() {
let arena = Arena::new();
let value = arena.allocate_with(|| 42);
assert_eq!(*value, 42);
}
#[test]
fn allocate_two_integers() {
let arena = Arena::new();
let a = arena.allocate_with(|| 1);
let b = arena.allocate_with(|| 2);
assert_eq!(*a, 1);
assert_eq!(*b, 2);
}
#[test]
fn allocate_empty_slice() {
let arena = Arena::new();
let slice: &[i32] = arena.allocate_slice(&[]);
assert!(slice.is_empty());
}
#[test]
fn allocate_small_slice() {
let arena = Arena::new();
let data = [10, 20, 30];
let slice = arena.allocate_slice(&data);
assert_eq!(slice, &data[..]);
}
#[test]
fn arena_default_matches_new() {
let arena: Arena = Default::default();
let value = arena.allocate_with(|| "x");
assert_eq!(*value, "x");
}
}
mod medium {
use super::*;
#[test]
fn allocate_larger_than_block() {
let arena = Arena::with_capacity(16);
let data = [0u8; 128];
let ptr = arena.allocate_with(|| data);
assert_eq!(*ptr, data);
}
#[test]
fn allocate_slice_larger_than_block() {
let arena = Arena::with_capacity(16);
let values: Vec<u8> = (0..=255).collect();
let slice = arena.allocate_slice(&values);
assert_eq!(slice, &values[..]);
}
#[test]
fn multiple_chunks_for_many_values() {
let arena = Arena::with_capacity(32);
let mut sum = 0i64;
for i in 0..100 {
let value = arena.allocate_with(|| i);
sum += *value;
}
assert_eq!(sum, 4950);
}
#[test]
fn multiple_chunks_for_many_slices() {
let arena = Arena::with_capacity(64);
let mut total = 0i64;
for i in 0..50 {
let values: Vec<i64> = (0..10).map(|j| i * 10 + j).collect();
let slice = arena.allocate_slice(&values);
total += slice.iter().sum::<i64>();
}
assert_eq!(total, 124_750);
}
#[test]
fn owned_expr_keeps_arena_alive() {
let arena = Box::new(Arena::new());
let root = arena.allocate_with(|| 123);
let root_ptr: *mut i32 = root;
let owned = unsafe { OwnedExpr::new(arena, root_ptr) };
assert_eq!(*owned.root(), 123);
}
}
mod complex {
use super::*;
#[test]
fn copy_values_survive_arena_drop() {
let value = {
let arena = Arena::new();
let ptr = arena.allocate_with(|| 42i32);
*ptr
};
assert_eq!(value, 42);
}
#[test]
fn alignment_of_large_type() {
#[derive(Clone, Copy)]
#[repr(C, align(64))]
struct BigAlign(u64);
let arena = Arena::with_capacity(4096);
let values = [BigAlign(7)];
let slice = arena.allocate_slice(&values);
assert!((slice.as_ptr() as usize).is_multiple_of(64));
assert_eq!(slice[0].0, 7);
}
#[test]
fn alignment_of_single_value() {
#[derive(Clone, Copy)]
#[repr(C, align(64))]
struct BigAlign(u64);
let arena = Arena::with_capacity(4096);
let value = arena.allocate_with(|| BigAlign(7));
assert_eq!((value as *const BigAlign) as usize % 64, 0);
assert_eq!(value.0, 7);
}
#[test]
#[should_panic(expected = "cannot allocate zero-sized types in Arena")]
fn zero_sized_type_panics() {
let arena = Arena::new();
let _: &mut () = arena.allocate_with(|| ());
}
#[test]
#[should_panic(expected = "cannot allocate zero-sized types in Arena")]
fn zero_sized_slice_panics() {
let arena = Arena::new();
let _: &[()] = arena.allocate_slice(&[()]);
}
#[test]
fn owned_expr_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<OwnedExpr<u8>>();
}
}
mod extreme {
use super::*;
#[test]
fn stress_mixed_allocations() {
let arena = Arena::with_capacity(256);
let mut total = 0usize;
for size in (1usize..=1000).step_by(7) {
let data: Vec<u8> = (0..size).map(|i| (i % 256) as u8).collect();
let ptr = arena.allocate_with(|| data.clone());
total += ptr.iter().map(|&x| x as usize).sum::<usize>();
}
assert!(total > 0);
}
proptest! {
#[test]
fn allocate_random_sizes(size in 1usize..10_000) {
let arena = Arena::with_capacity(256);
let data: Vec<u8> = (0..size).map(|i| (i % 256) as u8).collect();
let ptr = arena.allocate_with(|| data.clone());
prop_assert_eq!(&ptr[..], &data[..]);
}
#[test]
fn allocate_many_random_values(sizes in prop::collection::vec(1usize..512, 1..50)) {
let arena = Arena::with_capacity(256);
let mut total = 0usize;
for (idx, size) in sizes.iter().enumerate() {
let expected: Vec<u8> = (0..*size).map(|i| (i.wrapping_add(idx)) as u8).collect();
let ptr = arena.allocate_with(|| expected.clone());
prop_assert_eq!(&ptr[..], &expected[..]);
total += size;
}
prop_assert!(total > 0);
}
#[test]
fn slice_roundtrip(values in prop::collection::vec(0i32..100, 0..512)) {
let arena = Arena::with_capacity(256);
let slice = arena.allocate_slice(&values);
prop_assert_eq!(slice, &values[..]);
}
}
}
}