use alloc::boxed::Box as StdBox;
use core::fmt;
use core::mem::MaybeUninit;
use core::num::NonZero;
use core::ptr::NonNull;
use core::sync::atomic::{self, AtomicUsize};
use allocator_api2::alloc::Allocator;
use bytesbuf::BytesBuf;
use bytesbuf::mem::{Block, BlockRef, BlockRefDynamic, BlockRefVTable, BlockSize, Memory};
use crate::{Arc, Arena};
impl<A: Allocator + Clone + Send + Sync + 'static> Memory for Arena<A> {
fn reserve(&self, min_bytes: usize) -> BytesBuf {
let Some(min_bytes_nz) = NonZero::new(min_bytes) else {
return BytesBuf::new();
};
let len_u32 = BlockSize::try_from(min_bytes_nz.get())
.expect("multitude::Arena::reserve: min_bytes exceeds u32::MAX, which is the bytesbuf block size limit");
let len_nz = unsafe { NonZero::new_unchecked(len_u32) };
let arc: Arc<[MaybeUninit<u8>], A> = self.alloc_uninit_slice_arc::<u8>(min_bytes_nz.get());
let ptr = {
let slice_ptr: *const [MaybeUninit<u8>] = Arc::as_ptr(&arc);
unsafe { NonNull::new_unchecked(slice_ptr.cast::<MaybeUninit<u8>>().cast_mut()) }
};
let state = StdBox::new(ArenaBlockState::<A> {
_arc: arc,
ref_count: AtomicUsize::new(1),
});
let state_ptr = NonNull::from(StdBox::leak(state));
let block_ref = unsafe { BlockRef::new(state_ptr, vtable::<A>()) };
let block = unsafe { Block::new(ptr, len_nz, block_ref) };
BytesBuf::from_blocks([block])
}
}
struct ArenaBlockState<A: Allocator + Clone + Send + Sync + 'static> {
_arc: Arc<[MaybeUninit<u8>], A>,
ref_count: AtomicUsize,
}
impl<A: Allocator + Clone + Send + Sync + 'static> fmt::Debug for ArenaBlockState<A> {
#[cfg_attr(coverage_nightly, coverage(off))]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ArenaBlockState")
.field("ref_count", &self.ref_count.load(atomic::Ordering::Relaxed))
.finish_non_exhaustive()
}
}
unsafe impl<A: Allocator + Clone + Send + Sync + 'static> BlockRefDynamic for ArenaBlockState<A> {
type State = Self;
fn clone(state_ptr: NonNull<Self::State>) -> NonNull<Self::State> {
#[cfg_attr(coverage_nightly, coverage(off))]
#[inline(never)]
#[cold]
fn refcount_overflow() -> ! {
crate::internal::constants::refcount_overflow_abort()
}
let state = unsafe { state_ptr.as_ref() };
let prev = state.ref_count.fetch_add(1, atomic::Ordering::Relaxed);
if prev == usize::MAX {
refcount_overflow();
}
state_ptr
}
fn drop(state_ptr: NonNull<Self::State>) {
let state = unsafe { state_ptr.as_ref() };
if state.ref_count.fetch_sub(1, atomic::Ordering::Release) != 1 {
return;
}
atomic::fence(atomic::Ordering::Acquire);
drop(unsafe { StdBox::from_raw(state_ptr.as_ptr()) });
}
}
fn vtable<A: Allocator + Clone + Send + Sync + 'static>() -> &'static BlockRefVTable<ArenaBlockState<A>> {
&const { BlockRefVTable::<ArenaBlockState<A>>::from_trait() }
}
#[cfg(test)]
mod tests {
use allocator_api2::alloc::Global;
use super::*;
#[test]
#[should_panic(expected = "refcount overflow")]
fn clone_aborts_on_refcount_overflow() {
let arena = Arena::new();
let arc: Arc<[MaybeUninit<u8>], Global> = arena.alloc_uninit_slice_arc::<u8>(4);
let mut state = StdBox::new(ArenaBlockState::<Global> {
_arc: arc,
ref_count: AtomicUsize::new(usize::MAX),
});
let state_ptr = NonNull::from(&mut *state);
let _ = <ArenaBlockState<Global> as BlockRefDynamic>::clone(state_ptr);
}
}