use std::alloc::{alloc, dealloc, Layout};
use std::cell::UnsafeCell;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Debug, Clone)]
pub struct ArenaConfig {
pub initial_capacity: usize,
pub max_capacity: usize,
pub alignment: usize,
pub grow_factor: f64,
}
impl Default for ArenaConfig {
fn default() -> Self {
Self {
initial_capacity: 256 * 1024, max_capacity: 64 * 1024 * 1024, alignment: 8,
grow_factor: 2.0,
}
}
}
impl ArenaConfig {
pub fn small() -> Self {
Self {
initial_capacity: 64 * 1024,
max_capacity: 1024 * 1024,
..Default::default()
}
}
pub fn large() -> Self {
Self {
initial_capacity: 1024 * 1024,
max_capacity: 256 * 1024 * 1024,
..Default::default()
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ArenaStats {
pub bytes_allocated: usize,
pub allocation_count: usize,
pub reset_count: usize,
pub peak_bytes: usize,
}
pub struct FragmentArena {
buffer: NonNull<u8>,
capacity: usize,
offset: AtomicUsize,
config: ArenaConfig,
stats: UnsafeCell<ArenaStats>,
system_alloc_count: AtomicUsize,
}
unsafe impl Send for FragmentArena {}
unsafe impl Sync for FragmentArena {}
impl FragmentArena {
pub fn new(capacity: usize) -> Self {
Self::with_config(ArenaConfig {
initial_capacity: capacity,
max_capacity: capacity,
..Default::default()
})
}
pub fn with_config(config: ArenaConfig) -> Self {
let capacity = config.initial_capacity;
let layout =
Layout::from_size_align(capacity, config.alignment).expect("Invalid arena layout");
let ptr = unsafe { alloc(layout) };
let buffer = NonNull::new(ptr).expect("Arena allocation failed");
Self {
buffer,
capacity,
offset: AtomicUsize::new(0),
config,
stats: UnsafeCell::new(ArenaStats::default()),
system_alloc_count: AtomicUsize::new(1),
}
}
pub fn alloc(&self, size: usize) -> *mut u8 {
if size == 0 {
return std::ptr::null_mut();
}
let alignment = self.config.alignment;
loop {
let current = self.offset.load(Ordering::Relaxed);
let aligned = (current + alignment - 1) & !(alignment - 1);
let new_offset = aligned + size;
if new_offset > self.capacity {
return std::ptr::null_mut();
}
match self.offset.compare_exchange_weak(
current,
new_offset,
Ordering::SeqCst,
Ordering::Relaxed,
) {
Ok(_) => {
unsafe {
let stats = &mut *self.stats.get();
stats.bytes_allocated += size;
stats.allocation_count += 1;
if new_offset > stats.peak_bytes {
stats.peak_bytes = new_offset;
}
}
return unsafe { self.buffer.as_ptr().add(aligned) };
},
Err(_) => {
continue;
},
}
}
}
pub fn alloc_zeroed(&self, size: usize) -> *mut u8 {
let ptr = self.alloc(size);
if !ptr.is_null() {
unsafe {
std::ptr::write_bytes(ptr, 0, size);
}
}
ptr
}
pub fn reset(&self) {
self.offset.store(0, Ordering::SeqCst);
unsafe {
let stats = &mut *self.stats.get();
stats.reset_count += 1;
stats.bytes_allocated = 0;
stats.allocation_count = 0;
}
}
pub fn base_ptr(&self) -> *mut u8 {
self.buffer.as_ptr()
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn bytes_used(&self) -> usize {
self.offset.load(Ordering::Relaxed)
}
pub fn bytes_remaining(&self) -> usize {
self.capacity.saturating_sub(self.bytes_used())
}
pub fn stats(&self) -> ArenaStats {
unsafe { (*self.stats.get()).clone() }
}
pub fn system_alloc_count(&self) -> usize {
self.system_alloc_count.load(Ordering::Relaxed)
}
}
impl Drop for FragmentArena {
fn drop(&mut self) {
let layout = Layout::from_size_align(self.capacity, self.config.alignment)
.expect("Invalid arena layout");
unsafe {
dealloc(self.buffer.as_ptr(), layout);
}
}
}
pub struct ThreadLocalArena;
impl ThreadLocalArena {
pub fn get_or_init<F>(init: F) -> &'static FragmentArena
where
F: FnOnce() -> FragmentArena,
{
thread_local! {
static ARENA: std::cell::OnceCell<FragmentArena> = std::cell::OnceCell::new();
}
ARENA.with(|cell| {
let arena = cell.get_or_init(init);
unsafe { &*(arena as *const FragmentArena) }
})
}
}
pub struct ArenaAllocator<'a> {
arena: &'a FragmentArena,
}
impl<'a> ArenaAllocator<'a> {
pub fn new(arena: &'a FragmentArena) -> Self {
Self { arena }
}
pub fn alloc(&self, size: usize) -> *mut u8 {
self.arena.alloc(size)
}
pub fn alloc_zeroed(&self, size: usize) -> *mut u8 {
self.arena.alloc_zeroed(size)
}
pub fn arena(&self) -> &FragmentArena {
self.arena
}
}
pub struct ArenaScope<'a> {
arena: &'a FragmentArena,
start_offset: usize,
}
impl<'a> ArenaScope<'a> {
pub fn new(arena: &'a FragmentArena) -> Self {
Self {
arena,
start_offset: arena.bytes_used(),
}
}
pub fn alloc(&self, size: usize) -> *mut u8 {
self.arena.alloc(size)
}
pub fn allocator(&self) -> ArenaAllocator<'a> {
ArenaAllocator::new(self.arena)
}
}
impl<'a> Drop for ArenaScope<'a> {
fn drop(&mut self) {
self.arena.offset.store(self.start_offset, Ordering::SeqCst);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_alloc() {
let arena = FragmentArena::new(4096);
let ptr = arena.alloc(100);
assert!(!ptr.is_null());
assert_eq!(arena.stats().allocation_count, 1);
}
#[test]
fn test_alloc_alignment() {
let config = ArenaConfig {
initial_capacity: 4096,
alignment: 64,
..Default::default()
};
let arena = FragmentArena::with_config(config);
for _ in 0..10 {
let ptr = arena.alloc(17); assert_eq!(ptr as usize % 64, 0);
}
}
#[test]
fn test_alloc_zeroed() {
let arena = FragmentArena::new(4096);
let ptr = arena.alloc_zeroed(100);
assert!(!ptr.is_null());
let slice = unsafe { std::slice::from_raw_parts(ptr, 100) };
assert!(slice.iter().all(|&b| b == 0));
}
#[test]
fn test_reset() {
let arena = FragmentArena::new(4096);
arena.alloc(100);
arena.alloc(200);
assert!(arena.bytes_used() >= 300);
arena.reset();
assert_eq!(arena.bytes_used(), 0);
assert_eq!(arena.stats().reset_count, 1);
}
#[test]
fn test_scope() {
let arena = FragmentArena::new(4096);
arena.alloc(100);
let before = arena.bytes_used();
{
let _scope = ArenaScope::new(&arena);
arena.alloc(200);
assert!(arena.bytes_used() >= before + 200);
}
assert_eq!(arena.bytes_used(), before);
}
}