use std::{
alloc::{alloc, dealloc, Layout},
cell::Cell,
marker::PhantomData,
mem::{self, MaybeUninit},
ptr,
};
const MAX_ALIGN: usize = 16;
pub struct QueryArena {
buffer: *mut u8,
capacity: usize,
offset: Cell<usize>,
_marker: PhantomData<Cell<u8>>,
}
impl QueryArena {
pub const DEFAULT_CAPACITY: usize = 10 * 1024 * 1024;
pub fn with_capacity(bytes: usize) -> Self {
let layout = Layout::from_size_align(bytes, MAX_ALIGN).expect("invalid arena layout");
let buffer = unsafe { alloc(layout) };
if buffer.is_null() {
panic!("arena allocation failed");
}
Self { buffer, capacity: bytes, offset: Cell::new(0), _marker: PhantomData }
}
pub fn new() -> Self {
Self::with_capacity(Self::DEFAULT_CAPACITY)
}
#[inline(always)]
pub fn alloc<T>(&self, value: T) -> &T {
let offset = self.offset.get();
let size = mem::size_of::<T>();
let align = mem::align_of::<T>();
assert!(align <= MAX_ALIGN, "arena does not support alignment greater than {}", MAX_ALIGN);
let aligned_offset = (offset + align - 1) & !(align - 1);
let end_offset = aligned_offset.checked_add(size).expect("arena offset overflow");
assert!(
end_offset <= self.capacity,
"arena overflow: need {} bytes, have {} remaining",
size,
self.capacity - aligned_offset
);
self.offset.set(end_offset);
unsafe {
let ptr = self.buffer.add(aligned_offset) as *mut T;
ptr::write(ptr, value);
&*ptr
}
}
#[inline(always)]
#[allow(clippy::mut_from_ref)]
pub fn alloc_slice<T>(&self, len: usize) -> &mut [MaybeUninit<T>] {
if len == 0 {
return &mut [];
}
let offset = self.offset.get();
let size = mem::size_of::<T>().checked_mul(len).expect("slice size overflow");
let align = mem::align_of::<T>();
assert!(align <= MAX_ALIGN, "arena does not support alignment greater than {}", MAX_ALIGN);
let aligned_offset = (offset + align - 1) & !(align - 1);
let end_offset = aligned_offset.checked_add(size).expect("arena offset overflow");
assert!(
end_offset <= self.capacity,
"arena overflow: need {} bytes, have {} remaining",
size,
self.capacity - aligned_offset
);
self.offset.set(end_offset);
unsafe {
let ptr = self.buffer.add(aligned_offset) as *mut MaybeUninit<T>;
std::slice::from_raw_parts_mut(ptr, len)
}
}
#[inline]
pub fn try_alloc<T>(&self, value: T) -> Option<&T> {
let offset = self.offset.get();
let size = mem::size_of::<T>();
let align = mem::align_of::<T>();
if align > MAX_ALIGN {
return None;
}
let aligned_offset = (offset + align - 1) & !(align - 1);
let end_offset = aligned_offset.checked_add(size)?;
if end_offset > self.capacity {
return None;
}
self.offset.set(end_offset);
unsafe {
let ptr = self.buffer.add(aligned_offset) as *mut T;
ptr::write(ptr, value);
Some(&*ptr)
}
}
pub fn reset(&mut self) {
self.offset.set(0);
}
#[inline]
pub fn used_bytes(&self) -> usize {
self.offset.get()
}
#[inline]
pub fn capacity_bytes(&self) -> usize {
self.capacity
}
#[inline]
pub fn remaining_bytes(&self) -> usize {
self.capacity - self.offset.get()
}
}
impl Drop for QueryArena {
fn drop(&mut self) {
unsafe {
let layout = Layout::from_size_align_unchecked(self.capacity, MAX_ALIGN);
dealloc(self.buffer, layout);
}
}
}
unsafe impl Send for QueryArena {}
impl Default for QueryArena {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_arena_basic_allocation() {
let arena = QueryArena::with_capacity(1024);
let x = arena.alloc(42i64);
let y = arena.alloc(3.5f64);
assert_eq!(*x, 42);
assert_eq!(*y, 3.5);
}
#[test]
fn test_arena_slice_allocation() {
let arena = QueryArena::with_capacity(4096);
let slice = arena.alloc_slice::<i32>(100);
for (i, elem) in slice.iter_mut().enumerate() {
*elem = MaybeUninit::new(i as i32);
}
let initialized_slice =
unsafe { std::slice::from_raw_parts(slice.as_ptr() as *const i32, slice.len()) };
assert_eq!(initialized_slice[50], 50);
assert_eq!(slice.len(), 100);
}
#[test]
fn test_arena_empty_slice() {
let arena = QueryArena::new();
let slice: &mut [MaybeUninit<i32>] = arena.alloc_slice::<i32>(0);
assert_eq!(slice.len(), 0);
}
#[test]
fn test_arena_reset() {
let mut arena = QueryArena::with_capacity(1024);
let x = arena.alloc(42i64);
assert_eq!(*x, 42);
assert_eq!(arena.used_bytes(), 8);
arena.reset();
assert_eq!(arena.used_bytes(), 0);
let y = arena.alloc(100i64);
assert_eq!(*y, 100);
}
#[test]
fn test_arena_alignment() {
let arena = QueryArena::with_capacity(1024);
let _byte = arena.alloc(1u8);
let x = arena.alloc(42i64);
assert_eq!(*x, 42);
let ptr = x as *const i64 as usize;
assert_eq!(ptr % std::mem::align_of::<i64>(), 0);
}
#[test]
#[should_panic(expected = "arena overflow")]
fn test_arena_overflow() {
let arena = QueryArena::with_capacity(64);
for i in 0..1000 {
arena.alloc(i);
}
}
#[test]
fn test_try_alloc_success() {
let arena = QueryArena::with_capacity(1024);
let x = arena.try_alloc(42i64);
assert!(x.is_some());
assert_eq!(*x.unwrap(), 42);
}
#[test]
fn test_try_alloc_failure() {
let arena = QueryArena::with_capacity(8);
assert!(arena.try_alloc(42i64).is_some());
assert!(arena.try_alloc(100i64).is_none());
}
#[test]
fn test_arena_used_bytes() {
let arena = QueryArena::new();
assert_eq!(arena.used_bytes(), 0);
arena.alloc(42i64); assert_eq!(arena.used_bytes(), 8);
arena.alloc(3.5f64); assert_eq!(arena.used_bytes(), 16);
}
#[test]
fn test_arena_capacity() {
let arena = QueryArena::with_capacity(2048);
assert_eq!(arena.capacity_bytes(), 2048);
assert_eq!(arena.remaining_bytes(), 2048);
arena.alloc(42i64);
assert_eq!(arena.remaining_bytes(), 2048 - 8);
}
#[test]
fn test_default_arena() {
let arena = QueryArena::default();
assert_eq!(arena.capacity_bytes(), QueryArena::DEFAULT_CAPACITY);
}
#[test]
fn test_multiple_types() {
let arena = QueryArena::new();
let a = arena.alloc(1u8);
let b = arena.alloc(2u16);
let c = arena.alloc(3u32);
let d = arena.alloc(4u64);
let e = arena.alloc(5.0f32);
let f = arena.alloc(6.0f64);
assert_eq!(*a, 1);
assert_eq!(*b, 2);
assert_eq!(*c, 3);
assert_eq!(*d, 4);
assert_eq!(*e, 5.0);
assert_eq!(*f, 6.0);
}
}