use allocator_api2::alloc::{AllocError, Allocator, Global, Layout};
use std::ptr::NonNull;
use std::sync::{Arc, Mutex};
struct Chunk {
data: Vec<u8>,
}
impl Chunk {
fn new(size: usize) -> Self {
let layout = Layout::from_size_align(size, 16).expect("invalid layout");
let ptr = Global.allocate(layout).expect("global alloc failed").cast();
let data = unsafe { Vec::from_raw_parts(ptr.as_ptr(), 0, size) };
Self { data }
}
#[inline]
fn capacity(&self) -> usize {
self.data.capacity()
}
#[inline]
fn start(&self) -> *const u8 {
self.data.as_ptr()
}
}
struct SyncBumpInner {
chunks: Vec<Chunk>,
cursor: usize,
current_cap: usize,
}
impl SyncBumpInner {
fn new() -> Self {
Self {
chunks: Vec::new(),
cursor: 0,
current_cap: 0,
}
}
fn with_capacity(cap: usize) -> Self {
let chunk = Chunk::new(cap);
let cap = chunk.capacity();
Self {
chunks: vec![chunk],
cursor: 0,
current_cap: cap,
}
}
fn allocate(&mut self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
let size = layout.size();
let align = layout.align();
if size == 0 {
return Ok(NonNull::slice_from_raw_parts(NonNull::dangling(), 0));
}
if let Some(ptr) = self.try_alloc_in_current(align, size) {
return Ok(NonNull::slice_from_raw_parts(
NonNull::new(ptr).expect("non-null from valid chunk"),
size,
));
}
self.grow(align, size);
let ptr = self
.try_alloc_in_current(align, size)
.expect("freshly grown chunk must have room");
Ok(NonNull::slice_from_raw_parts(
NonNull::new(ptr).expect("non-null from valid chunk"),
size,
))
}
#[inline]
fn try_alloc_in_current(&mut self, align: usize, size: usize) -> Option<*mut u8> {
let chunk = self.chunks.last()?;
let base = chunk.start() as usize;
let offset = self.cursor;
let aligned_offset = (base + offset + align - 1) & !(align - 1);
let padding = aligned_offset - base - offset;
let new_cursor = offset + padding + size;
if new_cursor > self.current_cap {
return None;
}
self.cursor = new_cursor;
Some(aligned_offset as *mut u8)
}
fn grow(&mut self, _align: usize, size: usize) {
let next_size = (self.current_cap * 2).max(4096).max(size);
let chunk = Chunk::new(next_size);
self.current_cap = chunk.capacity();
self.cursor = 0;
self.chunks.push(chunk);
}
}
#[derive(Clone)]
pub struct SyncBumpArena {
inner: Arc<Mutex<SyncBumpInner>>,
}
impl SyncBumpArena {
#[inline]
#[must_use]
pub fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(SyncBumpInner::new())),
}
}
#[inline]
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
inner: Arc::new(Mutex::new(SyncBumpInner::with_capacity(capacity))),
}
}
}
impl Default for SyncBumpArena {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for SyncBumpArena {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SyncBumpArena")
.field("inner", &Arc::as_ptr(&self.inner))
.finish()
}
}
unsafe impl Allocator for SyncBumpArena {
#[inline]
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
let mut inner = self.inner.lock().expect("mutex poisoned");
inner.allocate(layout)
}
#[inline]
unsafe fn deallocate(&self, _ptr: NonNull<u8>, _layout: Layout) {
}
#[inline]
fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
let ptr = self.allocate(layout)?;
unsafe { ptr.cast::<u8>().as_ptr().write_bytes(0, layout.size()) };
Ok(ptr)
}
}
#[cfg(test)]
mod tests {
use super::*;
use allocator_api2::alloc::Layout;
#[test]
fn test_allocate_basic() {
let arena = SyncBumpArena::new();
let layout = Layout::from_size_align(64, 8).unwrap();
let ptr = arena.allocate(layout).unwrap();
assert_eq!(ptr.len(), 64);
unsafe {
ptr.cast::<u8>().as_ptr().write_bytes(0xAB, 64);
}
}
#[test]
fn test_allocate_alignment() {
let arena = SyncBumpArena::new();
for &align in &[1usize, 2, 4, 8, 16, 32, 64, 128] {
let layout = Layout::from_size_align(1, align).unwrap();
let ptr = arena.allocate(layout).unwrap();
let addr = ptr.cast::<u8>().as_ptr() as usize;
assert_eq!(addr % align, 0, "alignment {} not respected", align);
}
}
#[test]
fn test_allocate_zeroed() {
let arena = SyncBumpArena::new();
let layout = Layout::from_size_align(128, 8).unwrap();
let ptr = arena.allocate_zeroed(layout).unwrap();
let slice = unsafe { ptr.as_ref() };
assert!(
slice.iter().all(|&b| b == 0),
"allocate_zeroed returned non-zero memory"
);
}
#[test]
fn test_zero_size_allocation() {
let arena = SyncBumpArena::new();
let layout = Layout::from_size_align(0, 1).unwrap();
let ptr = arena.allocate(layout).unwrap();
assert_eq!(ptr.len(), 0);
}
#[test]
fn test_clone_shares_arena() {
let arena1 = SyncBumpArena::new();
let arena2 = arena1.clone();
let layout = Layout::from_size_align(8, 8).unwrap();
let ptr1 = arena1.allocate(layout).unwrap();
let ptr2 = arena2.allocate(layout).unwrap();
let addr1 = ptr1.cast::<u8>().as_ptr() as usize;
let addr2 = ptr2.cast::<u8>().as_ptr() as usize;
assert_ne!(addr1, addr2);
}
#[test]
fn test_deallocate_is_noop() {
let arena = SyncBumpArena::new();
let layout = Layout::from_size_align(32, 8).unwrap();
let ptr = arena.allocate(layout).unwrap();
unsafe { arena.deallocate(ptr.cast(), layout) };
unsafe {
ptr.cast::<u8>().as_ptr().write_bytes(0xCD, 32);
}
}
#[test]
fn test_chunk_growth() {
let arena = SyncBumpArena::with_capacity(64);
let layout = Layout::from_size_align(128, 8).unwrap();
let ptr = arena.allocate(layout).unwrap();
assert_eq!(ptr.len(), 128);
let layout2 = Layout::from_size_align(256, 8).unwrap();
let ptr2 = arena.allocate(layout2).unwrap();
assert_eq!(ptr2.len(), 256);
}
#[test]
fn test_many_allocations() {
let arena = SyncBumpArena::with_capacity(4096);
let layout = Layout::from_size_align(48, 8).unwrap();
let mut ptrs = Vec::new();
for _ in 0..1000 {
ptrs.push(arena.allocate(layout).unwrap());
}
let addrs: Vec<usize> = ptrs
.iter()
.map(|p| p.cast::<u8>().as_ptr() as usize)
.collect();
let unique: std::collections::HashSet<_> = addrs.iter().collect();
assert_eq!(
unique.len(),
1000,
"all 1000 allocations should be at distinct addresses"
);
}
#[test]
fn test_drop_frees_memory() {
let arena1 = SyncBumpArena::new();
let arena2 = arena1.clone();
drop(arena1);
let layout = Layout::from_size_align(16, 8).unwrap();
let _ = arena2.allocate(layout).unwrap();
drop(arena2);
}
#[test]
fn test_with_capacity() {
let arena = SyncBumpArena::with_capacity(8192);
let layout = Layout::from_size_align(4096, 8).unwrap();
let ptr = arena.allocate(layout).unwrap();
assert_eq!(ptr.len(), 4096);
}
#[test]
fn test_send_sync_bounds() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<SyncBumpArena>();
}
#[test]
fn test_concurrent_allocation() {
use std::sync::Arc as StdArc;
use std::thread;
let arena = StdArc::new(SyncBumpArena::with_capacity(4096 * 4));
let layout = Layout::from_size_align(64, 8).unwrap();
let handles: Vec<_> = (0..4)
.map(|_| {
let arena = StdArc::clone(&arena);
thread::spawn(move || {
let mut ptrs = Vec::new();
for _ in 0..100 {
ptrs.push(arena.allocate(layout).unwrap());
}
let addrs: Vec<usize> = ptrs
.iter()
.map(|p| p.cast::<u8>().as_ptr() as usize)
.collect();
let unique: std::collections::HashSet<_> = addrs.iter().collect();
assert_eq!(unique.len(), 100);
})
})
.collect();
for handle in handles {
handle.join().expect("thread should not panic");
}
}
#[test]
fn test_btreemap_with_arena() {
use arena_btreemap::BTreeMap;
let arena = SyncBumpArena::with_capacity(4096);
let mut map: BTreeMap<String, i32, SyncBumpArena> = BTreeMap::new_in(arena);
for i in 0..100 {
map.insert(format!("key_{:04}", i), i);
}
for i in 0..100 {
assert_eq!(map.get(&format!("key_{:04}", i)), Some(&i));
}
let keys: Vec<_> = map.keys().take(5).collect();
assert_eq!(keys[0], "key_0000");
assert_eq!(keys[4], "key_0004");
}
#[test]
fn test_btreemap_clone_shares_arena() {
use arena_btreemap::BTreeMap;
let arena = SyncBumpArena::with_capacity(4096);
let mut map: BTreeMap<String, i32, SyncBumpArena> = BTreeMap::new_in(arena.clone());
map.insert("a".to_string(), 1);
map.insert("b".to_string(), 2);
let map2 = map.clone();
assert_eq!(map2.get("a"), Some(&1));
assert_eq!(map2.get("b"), Some(&2));
assert_eq!(map.get("a"), Some(&1));
}
}