#[cfg(not(feature = "std"))]
use alloc::{boxed::Box, vec::Vec};
use core::ptr;
use core::sync::atomic::{AtomicPtr, AtomicU32, Ordering};
#[cfg(target_pointer_width = "32")]
const BLOCK_SHIFT: u32 = 22;
#[cfg(not(target_pointer_width = "32"))]
const BLOCK_SHIFT: u32 = 26;
const BLOCK_SIZE: u32 = 1 << BLOCK_SHIFT;
const BLOCK_MASK: u32 = BLOCK_SIZE - 1;
const MAX_BLOCKS: usize = 1 << (32 - BLOCK_SHIFT);
pub struct Arena {
blocks: Box<[AtomicPtr<u8>]>,
cursor: AtomicU32,
}
impl Arena {
pub fn new() -> Self {
let mut blocks = Vec::with_capacity(MAX_BLOCKS);
for _ in 0..MAX_BLOCKS {
blocks.push(AtomicPtr::new(ptr::null_mut()));
}
Self {
blocks: blocks.into_boxed_slice(),
cursor: AtomicU32::new(1),
}
}
pub fn alloc(&self, size: u32, align: u32) -> Option<u32> {
if !align.is_power_of_two() || size == 0 || size >= BLOCK_SIZE {
return None;
}
loop {
let cur = self.cursor.load(Ordering::Acquire);
let block_idx = cur >> BLOCK_SHIFT;
let offset = cur & BLOCK_MASK;
let aligned = (offset + align - 1) & !(align - 1);
if let Some(new_end) = aligned.checked_add(size) {
if new_end < BLOCK_SIZE {
self.ensure_block(block_idx as usize);
let new_cursor = (block_idx << BLOCK_SHIFT) | new_end;
if self
.cursor
.compare_exchange_weak(cur, new_cursor, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
return Some((block_idx << BLOCK_SHIFT) | aligned);
}
} else {
let new_block = block_idx + 1;
if new_block as usize >= MAX_BLOCKS {
return None;
}
self.ensure_block(new_block as usize);
let new_cursor = new_block << BLOCK_SHIFT;
let _ = self.cursor.compare_exchange_weak(
cur,
new_cursor,
Ordering::AcqRel,
Ordering::Relaxed,
);
}
} else {
return None;
}
}
}
pub unsafe fn get_bytes(&self, offset: u32, len: u32) -> &[u8] {
let (ptr, off) = unsafe { self.decode(offset) };
debug_assert!(
off + len as usize <= BLOCK_SIZE as usize,
"get_bytes: off={off} + len={len} exceeds BLOCK_SIZE={BLOCK_SIZE} (offset={offset})",
);
unsafe { core::slice::from_raw_parts(ptr.add(off), len as usize) }
}
#[expect(
clippy::mut_from_ref,
reason = "interior mutability by design; caller guarantees exclusive access"
)]
pub unsafe fn get_bytes_mut(&self, offset: u32, len: u32) -> &mut [u8] {
let (ptr, off) = unsafe { self.decode(offset) };
unsafe { core::slice::from_raw_parts_mut(ptr.add(off), len as usize) }
}
pub unsafe fn get_atomic_u32(&self, offset: u32) -> &AtomicU32 {
let (ptr, off) = unsafe { self.decode(offset) };
#[expect(
clippy::cast_ptr_alignment,
reason = "caller guarantees 4-byte alignment via alloc(..., 4)"
)]
let atom_ptr = unsafe { ptr.add(off).cast::<u32>() };
unsafe { AtomicU32::from_ptr(atom_ptr) }
}
#[inline]
#[expect(
clippy::indexing_slicing,
reason = "block_idx < MAX_BLOCKS by construction (alloc enforces this)"
)]
unsafe fn decode(&self, offset: u32) -> (*mut u8, usize) {
let block_idx = (offset >> BLOCK_SHIFT) as usize;
let off = (offset & BLOCK_MASK) as usize;
let mut ptr = self.blocks[block_idx].load(Ordering::Acquire);
if ptr.is_null() {
for _ in 0..1000 {
core::hint::spin_loop();
ptr = self.blocks[block_idx].load(Ordering::Acquire);
if !ptr.is_null() {
return (ptr, off);
}
}
self.ensure_block(block_idx);
ptr = self.blocks[block_idx].load(Ordering::Acquire);
}
(ptr, off)
}
#[expect(
clippy::indexing_slicing,
reason = "idx < MAX_BLOCKS enforced by alloc()"
)]
fn ensure_block(&self, idx: usize) {
if self.blocks[idx].load(Ordering::Acquire).is_null() {
let layout = Self::block_layout();
let raw = unsafe { alloc::alloc::alloc(layout) };
if raw.is_null() {
alloc::alloc::handle_alloc_error(layout);
}
if self.blocks[idx]
.compare_exchange(ptr::null_mut(), raw, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
unsafe {
alloc::alloc::dealloc(raw, layout);
}
}
}
}
fn block_layout() -> alloc::alloc::Layout {
unsafe { alloc::alloc::Layout::from_size_align_unchecked(BLOCK_SIZE as usize, 4) }
}
}
impl Default for Arena {
fn default() -> Self {
Self::new()
}
}
impl Drop for Arena {
fn drop(&mut self) {
let layout = Self::block_layout();
for block in &*self.blocks {
let ptr = block.load(Ordering::Relaxed);
if !ptr.is_null() {
unsafe {
alloc::alloc::dealloc(ptr, layout);
}
}
}
}
}
#[cfg(test)]
#[allow(clippy::expect_used, reason = "tests use expect for brevity")]
#[allow(
clippy::unwrap_used,
clippy::indexing_slicing,
clippy::useless_vec,
clippy::doc_markdown,
clippy::stable_sort_primitive,
reason = "test code"
)]
mod tests;