use core::alloc::Layout;
use core::cell::Cell;
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use core::ptr::NonNull;
use core::slice;
pub struct BumpArena {
base: NonNull<[MaybeUninit<u8>]>,
offset: Cell<usize>,
_invariant: PhantomData<*const ()>,
}
impl BumpArena {
#[must_use]
pub fn new(capacity: usize) -> Self {
Self {
base: unsafe { NonNull::new_unchecked(Box::into_raw(Box::new_uninit_slice(capacity))) },
offset: Cell::new(0),
_invariant: PhantomData,
}
}
#[allow(clippy::mut_from_ref)]
pub fn alloc_uninit_slice(&self, layout: Layout) -> Option<&mut [MaybeUninit<u8>]> {
let size = layout.size();
if size == 0 {
let ptr = layout.dangling_ptr().as_ptr().cast::<MaybeUninit<u8>>();
return Some(unsafe { slice::from_raw_parts_mut(ptr, 0) });
}
let align = layout.align();
let offset = self.offset.get();
let base = self.base.as_ptr().cast::<MaybeUninit<u8>>();
let base_addr = base as usize;
let addr = base_addr + offset;
let align_mask = align - 1;
let aligned_addr = addr.checked_add(align_mask)? & !align_mask;
let aligned = aligned_addr - base_addr;
let offset = aligned.checked_add(size)?;
if offset > self.capacity() {
return None;
}
self.offset.set(offset);
unsafe { Some(slice::from_raw_parts_mut(base.add(aligned), size)) }
}
pub unsafe fn reset(&self) {
self.offset.set(0);
}
pub fn capacity(&self) -> usize {
self.base.len()
}
pub fn used(&self) -> usize {
self.offset.get()
}
}
impl Drop for BumpArena {
fn drop(&mut self) {
unsafe {
drop(Box::from_raw(self.base.as_ptr()));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alloc_alignment_and_length() {
let bump = BumpArena::new(128);
let base = bump.base.as_ptr().cast::<MaybeUninit<u8>>() as usize;
let mut prev_end = 0usize;
for align in [1, 2, 4, 8, 16] {
let layout = Layout::from_size_align(3, align).unwrap();
let slice = bump.alloc_uninit_slice(layout).unwrap();
let ptr = slice.as_ptr() as usize;
assert_eq!(ptr % align, 0);
assert_eq!(slice.len(), 3);
let start = ptr - base;
let end = start + slice.len();
assert!(end > prev_end);
assert_eq!(bump.used(), end);
assert!(bump.used() <= bump.capacity());
prev_end = end;
}
}
#[test]
fn alloc_no_overlap() {
let bump = BumpArena::new(64);
let a = bump.alloc_uninit_slice(Layout::from_size_align(16, 8).unwrap()).unwrap();
let b = bump.alloc_uninit_slice(Layout::from_size_align(8, 8).unwrap()).unwrap();
let a_start = a.as_ptr() as usize;
let a_end = a_start + a.len();
let b_start = b.as_ptr() as usize;
let b_end = b_start + b.len();
assert!(a_end <= b_start || b_end <= a_start);
}
#[test]
fn alloc_oom_does_not_advance() {
let bump = BumpArena::new(16);
let layout = Layout::from_size_align(8, 1).unwrap();
bump.alloc_uninit_slice(layout).unwrap();
let used_before = bump.used();
let too_large = Layout::from_size_align(9, 1).unwrap();
assert!(bump.alloc_uninit_slice(too_large).is_none());
assert_eq!(bump.used(), used_before);
assert!(bump.used() <= bump.capacity());
}
#[test]
fn reset_reuses_base() {
let bump = BumpArena::new(32);
let layout = Layout::from_size_align(8, 4).unwrap();
let first = bump.alloc_uninit_slice(layout).unwrap();
let first_ptr = first.as_ptr() as usize;
unsafe { bump.reset() };
assert_eq!(bump.used(), 0);
let second = bump.alloc_uninit_slice(layout).unwrap();
let second_ptr = second.as_ptr() as usize;
assert_eq!(first_ptr, second_ptr);
}
#[test]
fn zero_capacity_rejects_nonzero_alloc_uninit_slice() {
let bump = BumpArena::new(0);
let layout = Layout::from_size_align(1, 1).unwrap();
assert!(bump.alloc_uninit_slice(layout).is_none());
assert_eq!(bump.used(), 0);
}
#[test]
fn zero_size_alloc_does_not_advance() {
let bump = BumpArena::new(8);
let layout = Layout::from_size_align(0, 8).unwrap();
let slice = bump.alloc_uninit_slice(layout).unwrap();
assert_eq!(slice.len(), 0);
assert_eq!(bump.used(), 0);
}
}