use std::alloc::{alloc, dealloc, Layout};
use std::cell::UnsafeCell;
use std::marker::PhantomData;
use std::mem;
use std::ptr::NonNull;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone)]
pub struct ArenaConfig {
pub initial_size: usize,
pub allow_growth: bool,
pub growth_factor: f64,
pub alignment: usize,
}
impl Default for ArenaConfig {
fn default() -> Self {
Self {
initial_size: 1024 * 1024, allow_growth: true, growth_factor: 2.0, alignment: 8, }
}
}
#[derive(Debug)]
struct ArenaChunk {
ptr: NonNull<u8>,
size: usize,
offset: usize,
layout: Layout,
}
unsafe impl Send for ArenaChunk {}
unsafe impl Sync for ArenaChunk {}
impl ArenaChunk {
fn new(size: usize, alignment: usize) -> Option<Self> {
let layout = Layout::from_size_align(size, alignment).ok()?;
unsafe {
let ptr = NonNull::new(alloc(layout))?;
Some(Self {
ptr,
size,
offset: 0,
layout,
})
}
}
fn allocate(&mut self, size: usize, alignment: usize) -> Option<NonNull<u8>> {
let align_mask = alignment - 1;
let aligned_offset = (self.offset + align_mask) & !align_mask;
if aligned_offset + size <= self.size {
let result = unsafe { NonNull::new_unchecked(self.ptr.as_ptr().add(aligned_offset)) };
self.offset = aligned_offset + size;
Some(result)
} else {
None
}
}
fn available_space(&self) -> usize {
self.size - self.offset
}
fn reset(&mut self) {
self.offset = 0;
}
}
impl Drop for ArenaChunk {
fn drop(&mut self) {
unsafe {
dealloc(self.ptr.as_ptr(), self.layout);
}
}
}
#[derive(Debug)]
struct ArenaState {
config: ArenaConfig,
chunks: Vec<ArenaChunk>,
total_allocated: usize,
}
#[derive(Debug)]
pub struct ArenaAllocator {
state: Arc<Mutex<UnsafeCell<ArenaState>>>,
}
impl ArenaAllocator {
pub fn new(config: ArenaConfig) -> Self {
let mut state = ArenaState {
config: config.clone(),
chunks: Vec::new(),
total_allocated: 0,
};
if let Some(chunk) = ArenaChunk::new(config.initial_size, config.alignment) {
state.total_allocated += config.initial_size;
state.chunks.push(chunk);
} else {
panic!("Failed to allocate initial arena chunk");
}
Self {
state: Arc::new(Mutex::new(UnsafeCell::new(state))),
}
}
pub fn allocate(&self, size: usize) -> Option<NonNull<u8>> {
self.allocate_aligned(size, self.get_alignment())
}
pub fn allocate_aligned(&self, size: usize, alignment: usize) -> Option<NonNull<u8>> {
if size == 0 {
return None;
}
let state_mutex = self
.state
.lock()
.expect("state mutex should not be poisoned");
let state = unsafe { &mut *state_mutex.get() };
for chunk in &mut state.chunks {
if let Some(ptr) = chunk.allocate(size, alignment) {
return Some(ptr);
}
}
if state.config.allow_growth {
let current_total = state.total_allocated;
let growth =
(current_total as f64 * (state.config.growth_factor - 1.0)).ceil() as usize;
let new_size = growth.max(size.max(state.config.initial_size));
if let Some(mut new_chunk) =
ArenaChunk::new(new_size, alignment.max(state.config.alignment))
{
let result = new_chunk.allocate(size, alignment);
state.total_allocated += new_size;
state.chunks.push(new_chunk);
return result;
}
}
None
}
pub fn reset(&self) {
let state_mutex = self
.state
.lock()
.expect("state mutex should not be poisoned");
let state = unsafe { &mut *state_mutex.get() };
for chunk in &mut state.chunks {
chunk.reset();
}
}
pub fn total_size(&self) -> usize {
let state_mutex = self
.state
.lock()
.expect("state mutex should not be poisoned");
let state = unsafe { &*state_mutex.get() };
state.total_allocated
}
pub fn available_size(&self) -> usize {
let state_mutex = self
.state
.lock()
.expect("state mutex should not be poisoned");
let state = unsafe { &*state_mutex.get() };
state
.chunks
.iter()
.map(|chunk| chunk.available_space())
.sum()
}
pub fn get_alignment(&self) -> usize {
let state_mutex = self
.state
.lock()
.expect("state mutex should not be poisoned");
let state = unsafe { &*state_mutex.get() };
state.config.alignment
}
pub fn allocate_object<T>(&self) -> Option<NonNull<T>> {
let size = mem::size_of::<T>();
let align = mem::align_of::<T>();
if size == 0 {
return None;
}
self.allocate_aligned(size, align)
.map(|ptr| unsafe { NonNull::new_unchecked(ptr.as_ptr() as *mut T) })
}
pub fn scoped(&self) -> ScopedArena {
ScopedArena {
arena: self.clone(),
}
}
}
impl Clone for ArenaAllocator {
fn clone(&self) -> Self {
Self {
state: Arc::clone(&self.state),
}
}
}
pub struct ScopedArena {
arena: ArenaAllocator,
}
impl ScopedArena {
pub fn allocate(&self, size: usize) -> Option<NonNull<u8>> {
self.arena.allocate(size)
}
pub fn allocate_aligned(&self, size: usize, alignment: usize) -> Option<NonNull<u8>> {
self.arena.allocate_aligned(size, alignment)
}
pub fn allocate_object<T>(&self) -> Option<NonNull<T>> {
self.arena.allocate_object::<T>()
}
}
impl Drop for ScopedArena {
fn drop(&mut self) {
self.arena.reset();
}
}
pub struct ArenaVec<'a, T> {
ptr: NonNull<T>,
len: usize,
capacity: usize,
arena: &'a ArenaAllocator,
phantom: PhantomData<&'a mut [T]>,
}
impl<'a, T> ArenaVec<'a, T> {
pub fn with_capacity(capacity: usize, arena: &'a ArenaAllocator) -> Self {
if capacity == 0 {
return Self {
ptr: NonNull::dangling(),
len: 0,
capacity: 0,
arena,
phantom: PhantomData,
};
}
let size = capacity * mem::size_of::<T>();
let ptr_opt = arena.allocate_aligned(size, mem::align_of::<T>());
if ptr_opt.is_none() {
return Self {
ptr: NonNull::dangling(),
len: 0,
capacity: 0,
arena,
phantom: PhantomData,
};
}
let ptr = ptr_opt.expect("allocation should succeed since we checked above");
Self {
ptr: unsafe { NonNull::new_unchecked(ptr.as_ptr() as *mut T) },
len: 0,
capacity,
arena,
phantom: PhantomData,
}
}
pub fn push(&mut self, value: T) -> bool {
if self.len >= self.capacity {
let new_capacity = self.capacity.saturating_mul(2).max(4);
if !self.reserve(new_capacity - self.capacity) {
return false;
}
}
unsafe {
std::ptr::write(self.ptr.as_ptr().add(self.len), value);
}
self.len += 1;
true
}
pub fn reserve(&mut self, additional: usize) -> bool {
if additional == 0 {
return true;
}
let new_capacity = self.capacity + additional;
let new_size = new_capacity * mem::size_of::<T>();
if let Some(new_ptr) = self.arena.allocate_aligned(new_size, mem::align_of::<T>()) {
unsafe {
std::ptr::copy_nonoverlapping(
self.ptr.as_ptr(),
new_ptr.as_ptr() as *mut T,
self.len,
);
}
self.ptr = unsafe { NonNull::new_unchecked(new_ptr.as_ptr() as *mut T) };
self.capacity = new_capacity;
true
} else {
false
}
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn as_slice(&self) -> &[T] {
unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
}
pub fn as_mut_slice(&mut self) -> &mut [T] {
unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
}
pub fn get(&self, index: usize) -> Option<&T> {
if index < self.len {
unsafe { Some(&*self.ptr.as_ptr().add(index)) }
} else {
None
}
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
if index < self.len {
unsafe { Some(&mut *self.ptr.as_ptr().add(index)) }
} else {
None
}
}
pub fn clear(&mut self) {
for i in 0..self.len {
unsafe {
std::ptr::drop_in_place(self.ptr.as_ptr().add(i));
}
}
self.len = 0;
}
}
impl<'a, T: Clone> ArenaVec<'a, T> {
pub fn from_slice(slice: &[T], arena: &'a ArenaAllocator) -> Self {
let mut vec = Self::with_capacity(slice.len(), arena);
for item in slice {
vec.push(item.clone());
}
vec
}
}
impl<T> Drop for ArenaVec<'_, T> {
fn drop(&mut self) {
self.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_arena_allocator_basic() {
let config = ArenaConfig {
initial_size: 1024,
allow_growth: true,
growth_factor: 2.0,
alignment: 8,
};
let arena = ArenaAllocator::new(config);
assert_eq!(arena.total_size(), 1024);
assert_eq!(arena.available_size(), 1024);
let ptr1 = arena.allocate(100).expect("Allocation should succeed");
assert!(arena.available_size() < 1024);
let ptr2 = arena.allocate(100).expect("Allocation should succeed");
assert!(ptr1 != ptr2);
let ptr3 = arena
.allocate(1000)
.expect("Allocation should trigger growth");
assert!(ptr1 != ptr3 && ptr2 != ptr3);
assert!(arena.total_size() > 1024);
arena.reset();
assert_eq!(arena.available_size(), arena.total_size());
}
#[test]
fn test_arena_allocator_alignment() {
let config = ArenaConfig {
initial_size: 1024,
allow_growth: true,
growth_factor: 2.0,
alignment: 8,
};
let arena = ArenaAllocator::new(config);
let ptr1 = arena
.allocate_aligned(10, 1)
.expect("Allocation should succeed");
let ptr2 = arena
.allocate_aligned(10, 4)
.expect("Allocation should succeed");
let ptr3 = arena
.allocate_aligned(10, 8)
.expect("Allocation should succeed");
let ptr4 = arena
.allocate_aligned(10, 16)
.expect("Allocation should succeed");
#[allow(useless_ptr_null_checks)]
{
assert!(!ptr1.as_ptr().is_null());
}
assert_eq!(ptr2.as_ptr() as usize % 4, 0);
assert_eq!(ptr3.as_ptr() as usize % 8, 0);
assert_eq!(ptr4.as_ptr() as usize % 16, 0);
}
#[test]
fn test_arena_vec() {
let config = ArenaConfig::default();
let arena = ArenaAllocator::new(config);
let mut vec: ArenaVec<i32> = ArenaVec::with_capacity(10, &arena);
assert_eq!(vec.len(), 0);
assert_eq!(vec.capacity(), 10);
for i in 0..15 {
vec.push(i);
}
assert_eq!(vec.len(), 15);
assert!(vec.capacity() >= 15);
for i in 0..15 {
assert_eq!(vec.get(i), Some(&(i as i32)));
}
for i in 0..vec.len() {
if let Some(value) = vec.get_mut(i) {
*value *= 2;
}
}
for i in 0..15 {
assert_eq!(vec.get(i), Some(&((i as i32) * 2)));
}
let source = [1, 2, 3, 4, 5];
let vec2 = ArenaVec::from_slice(&source, &arena);
assert_eq!(vec2.len(), 5);
assert_eq!(vec2.as_slice(), &source[..]);
}
#[test]
fn test_scoped_arena() {
let config = ArenaConfig::default();
let arena = ArenaAllocator::new(config);
let initial_available = arena.available_size();
{
let scoped = arena.scoped();
let _ptr1 = scoped.allocate(100).expect("Allocation should succeed");
let _ptr2 = scoped.allocate(100).expect("Allocation should succeed");
assert!(arena.available_size() < initial_available);
}
assert_eq!(arena.available_size(), initial_available);
}
}