use std::alloc::{self, Layout};
use std::ptr::NonNull;
use std::sync::{Mutex, OnceLock};
use crate::{GcBox, GcPtr, Trace};
const CHUNK_SIZE: usize = 256 * 1024;
struct Chunk {
data: NonNull<u8>,
layout: Layout,
}
unsafe impl Send for Chunk {}
struct Inner {
chunks: Vec<Chunk>,
ptr: usize,
end: usize,
}
impl Inner {
fn new() -> Self {
let layout = Layout::from_size_align(CHUNK_SIZE, 16).unwrap();
let data = unsafe {
NonNull::new(alloc::alloc(layout))
.expect("StaticArena: initial chunk allocation failed")
};
let base = data.as_ptr() as usize;
Self {
chunks: vec![Chunk { data, layout }],
ptr: base,
end: base + CHUNK_SIZE,
}
}
fn alloc_raw(&mut self, layout: Layout) -> *mut u8 {
let align = layout.align();
let size = layout.size();
let aligned = (self.ptr + align - 1) & !(align - 1);
let new_ptr = aligned + size;
if new_ptr <= self.end {
self.ptr = new_ptr;
return aligned as *mut u8;
}
self.grow(layout)
}
fn grow(&mut self, layout: Layout) -> *mut u8 {
let chunk_size = CHUNK_SIZE.max(layout.size() * 2);
let cl = Layout::from_size_align(chunk_size, layout.align().max(16)).unwrap();
let data = unsafe {
NonNull::new(alloc::alloc(cl)).expect("StaticArena: chunk allocation failed")
};
let base = data.as_ptr() as usize;
let aligned = (base + layout.align() - 1) & !(layout.align() - 1);
self.ptr = aligned + layout.size();
self.end = base + chunk_size;
self.chunks.push(Chunk { data, layout: cl });
aligned as *mut u8
}
#[cfg(debug_assertions)]
fn contains_addr(&self, addr: usize) -> bool {
for chunk in &self.chunks {
let base = chunk.data.as_ptr() as usize;
let end = base + chunk.layout.size();
if addr >= base && addr < end {
return true;
}
}
false
}
}
pub struct StaticArena {
inner: Mutex<Inner>,
}
unsafe impl Sync for StaticArena {}
impl StaticArena {
fn new() -> Self {
Self {
inner: Mutex::new(Inner::new()),
}
}
pub fn alloc<T: Trace + 'static>(&self, value: T) -> GcPtr<T> {
let layout = Layout::new::<GcBox<T>>();
let raw = self.inner.lock().unwrap().alloc_raw(layout);
let gc_box = raw as *mut GcBox<T>;
unsafe { std::ptr::write(gc_box, GcBox { value }) };
GcPtr(unsafe { NonNull::new_unchecked(gc_box) })
}
pub fn alloc_val<T: 'static>(&self, value: T) -> crate::StaticGcPtr<T> {
let layout = std::alloc::Layout::new::<T>();
let raw = self.inner.lock().unwrap().alloc_raw(layout) as *mut T;
unsafe { std::ptr::write(raw, value) };
crate::StaticGcPtr(unsafe { NonNull::new_unchecked(raw) })
}
#[cfg(debug_assertions)]
pub fn contains_addr(&self, addr: usize) -> bool {
self.inner.lock().unwrap().contains_addr(addr)
}
}
static STATIC_ARENA: OnceLock<StaticArena> = OnceLock::new();
pub fn static_arena() -> &'static StaticArena {
STATIC_ARENA.get_or_init(StaticArena::new)
}
pub fn static_alloc_val<T: 'static>(value: T) -> crate::StaticGcPtr<T> {
static_arena().alloc_val(value)
}
#[cfg(debug_assertions)]
pub fn is_static_addr(addr: usize) -> bool {
match STATIC_ARENA.get() {
Some(arena) => arena.contains_addr(addr),
None => false,
}
}