use std::cell::UnsafeCell;
const PAGE_BYTES: usize = 64 * 1024;
pub struct Arena {
pages: UnsafeCell<Vec<Box<[u8; PAGE_BYTES]>>>,
cursor: UnsafeCell<usize>,
}
impl Arena {
pub fn new() -> Self {
Self {
pages: UnsafeCell::new(Vec::new()),
cursor: UnsafeCell::new(0),
}
}
#[allow(clippy::mut_from_ref)]
pub fn alloc(&self, len: usize) -> &mut [u8] {
assert!(len <= PAGE_BYTES, "arena alloc exceeds page size");
unsafe {
let pages = &mut *self.pages.get();
let cursor = &mut *self.cursor.get();
if pages.is_empty() || *cursor + len > PAGE_BYTES {
pages.push(Box::new([0u8; PAGE_BYTES]));
*cursor = 0;
}
let page_idx = pages.len() - 1;
let start = *cursor;
*cursor += len;
let page: *mut [u8; PAGE_BYTES] = pages[page_idx].as_mut() as *mut _;
std::slice::from_raw_parts_mut((*page).as_mut_ptr().add(start), len)
}
}
pub fn bytes_allocated(&self) -> usize {
unsafe {
let pages = &*self.pages.get();
let cursor = *self.cursor.get();
if pages.is_empty() { 0 } else { (pages.len() - 1) * PAGE_BYTES + cursor }
}
}
pub fn page_count(&self) -> usize {
unsafe { (*self.pages.get()).len() }
}
}
impl Default for Arena {
fn default() -> Self { Self::new() }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ScopeId(pub u64);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_arena_allocates_no_pages() {
let a = Arena::new();
assert_eq!(a.page_count(), 0);
assert_eq!(a.bytes_allocated(), 0);
}
#[test]
fn first_alloc_creates_a_page() {
let a = Arena::new();
let _b = a.alloc(16);
assert_eq!(a.page_count(), 1);
assert_eq!(a.bytes_allocated(), 16);
}
#[test]
fn alloc_grows_to_a_new_page_when_full() {
let a = Arena::new();
let _b1 = a.alloc(PAGE_BYTES - 100);
assert_eq!(a.page_count(), 1);
let _b2 = a.alloc(200);
assert_eq!(a.page_count(), 2);
}
#[test]
fn alloc_returns_distinct_regions() {
let a = Arena::new();
let b1 = a.alloc(8);
b1[0] = 0xAB;
let b2 = a.alloc(8);
b2[0] = 0xCD;
assert_eq!(b1[0], 0xAB);
assert_eq!(b2[0], 0xCD);
}
#[test]
fn alloc_larger_than_page_panics() {
let a = Arena::new();
let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
a.alloc(PAGE_BYTES + 1);
}));
assert!(r.is_err());
}
}