use std::alloc::{alloc, dealloc, Layout};
use std::cell::Cell;
use std::ptr::NonNull;
use super::hints::{likely, unlikely};
use super::sizeclass;
#[repr(C, align(64))] pub struct FrameArena {
base: NonNull<u8>,
size: usize,
head: Cell<usize>,
}
impl FrameArena {
pub fn new(size: usize) -> Option<Self> {
if unlikely(size == 0) {
return None;
}
let aligned_size = (size + 63) & !63; let layout = Layout::from_size_align(aligned_size, 64).ok()?;
let ptr = unsafe { alloc(layout) };
let base = NonNull::new(ptr)?;
Some(Self {
base,
size: aligned_size,
head: Cell::new(0),
})
}
#[inline(always)]
pub fn alloc(&self, layout: Layout) -> *mut u8 {
let align = layout.align();
let size = layout.size();
let current = self.head.get();
let align_mask = align - 1;
let aligned = (current + align_mask) & !align_mask;
let new_head = aligned + size;
if unlikely(new_head > self.size) {
return std::ptr::null_mut();
}
self.head.set(new_head);
unsafe { self.base.as_ptr().add(aligned) }
}
#[inline(always)]
pub fn alloc_value<T>(&self, value: T) -> *mut T {
let layout = Layout::new::<T>();
let ptr = self.alloc(layout) as *mut T;
if likely(!ptr.is_null()) {
unsafe { ptr.write(value) };
}
ptr
}
#[inline(always)]
pub fn alloc_slice<T>(&self, len: usize) -> *mut T {
if unlikely(len == 0) {
return std::ptr::null_mut();
}
let layout = unsafe { Layout::array::<T>(len).unwrap_unchecked() };
self.alloc(layout) as *mut T
}
#[inline(always)]
pub fn alloc_size_class(&self, size: usize) -> *mut u8 {
if let Some(class) = sizeclass::SizeClass::for_size(size) {
self.alloc(class.layout())
} else {
let layout = unsafe { Layout::from_size_align_unchecked(size, 8) };
self.alloc(layout)
}
}
#[inline(always)]
pub fn alloc_value_fast<T>(&self, value: T) -> *mut T {
let size = std::mem::size_of::<T>();
let ptr = if likely(size <= 2048) {
self.alloc_size_class(size) as *mut T
} else {
let layout = Layout::new::<T>();
self.alloc(layout) as *mut T
};
if likely(!ptr.is_null()) {
unsafe { ptr.write(value) };
}
ptr
}
#[inline(always)]
pub fn reset(&self) {
self.head.set(0);
}
#[inline(always)]
pub fn head(&self) -> usize {
self.head.get()
}
pub fn reset_to(&self, pos: usize) {
debug_assert!(pos <= self.head.get(), "Cannot reset forward");
self.head.set(pos);
}
pub fn allocated(&self) -> usize {
self.head.get()
}
pub fn remaining(&self) -> usize {
self.size - self.head.get()
}
pub fn capacity(&self) -> usize {
self.size
}
pub fn is_empty(&self) -> bool {
self.head.get() == 0
}
}
impl Drop for FrameArena {
fn drop(&mut self) {
unsafe {
let layout = Layout::from_size_align_unchecked(self.size, 64);
dealloc(self.base.as_ptr(), layout);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_arena_alloc() {
let arena = FrameArena::new(4096).unwrap();
let ptr1 = arena.alloc_value(42u32);
assert!(!ptr1.is_null());
assert_eq!(unsafe { *ptr1 }, 42);
let ptr2 = arena.alloc_value(123u64);
assert!(!ptr2.is_null());
assert_eq!(unsafe { *ptr2 }, 123);
assert!(arena.allocated() > 0);
}
#[test]
fn test_arena_reset() {
let arena = FrameArena::new(4096).unwrap();
arena.alloc_value(42u32);
arena.alloc_value(123u64);
assert!(arena.allocated() > 0);
arena.reset();
assert_eq!(arena.allocated(), 0);
}
#[test]
fn test_arena_checkpoint() {
let arena = FrameArena::new(4096).unwrap();
arena.alloc_value(42u32);
let checkpoint = arena.head();
arena.alloc_value(123u64);
arena.alloc_value(456u64);
assert!(arena.allocated() > checkpoint);
arena.reset_to(checkpoint);
assert_eq!(arena.head(), checkpoint);
}
#[test]
fn test_arena_slice() {
let arena = FrameArena::new(4096).unwrap();
let slice = arena.alloc_slice::<u32>(100);
assert!(!slice.is_null());
for i in 0..100 {
unsafe { slice.add(i).write(i as u32) };
}
for i in 0..100 {
assert_eq!(unsafe { *slice.add(i) }, i as u32);
}
}
#[test]
fn test_arena_full() {
let arena = FrameArena::new(64).unwrap();
let ptr = arena.alloc_slice::<u8>(64);
assert!(!ptr.is_null());
let ptr2 = arena.alloc_value(42u32);
assert!(ptr2.is_null());
}
}