use crate::core_alloc::{AllocError, Allocator, Layout};
use core::cell::Cell;
use core::mem::MaybeUninit;
use core::ptr::{self, NonNull};
use core::sync::atomic::{AtomicUsize, Ordering};
#[cfg(debug_assertions)]
use core::sync::atomic::AtomicU64;
use crate::fallback::C_ALLOCATOR;
#[repr(C)]
struct Block {
next: *mut Block,
pprev: *mut *mut Block,
payload: *mut u8,
}
const _: () = assert!(core::mem::size_of::<Block>() == 24);
const HEADER_LEN: usize = 24;
#[cfg(debug_assertions)]
#[inline]
fn debug_thread_stamp() -> u64 {
static NEXT: AtomicU64 = AtomicU64::new(1);
std::thread_local!(static ID: u64 = NEXT.fetch_add(1, Ordering::Relaxed));
ID.with(|id| *id)
}
pub struct StdArena {
owns: bool,
live_head: Cell<*mut Block>,
live_tail: Cell<*mut *mut Block>,
quarantine_head: Cell<*mut Block>,
live_bytes: AtomicUsize,
#[cfg(debug_assertions)]
owning_thread: AtomicU64,
}
unsafe impl Send for StdArena {}
unsafe impl Sync for StdArena {}
impl Default for StdArena {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl StdArena {
#[inline]
pub fn new() -> Self {
Self {
owns: true,
live_head: Cell::new(ptr::null_mut()),
live_tail: Cell::new(ptr::null_mut()),
quarantine_head: Cell::new(ptr::null_mut()),
live_bytes: AtomicUsize::new(0),
#[cfg(debug_assertions)]
owning_thread: AtomicU64::new(debug_thread_stamp()),
}
}
#[inline]
pub fn init() -> Self {
Self::new()
}
#[inline]
pub fn borrowing_default() -> Self {
Self {
owns: false,
live_head: Cell::new(ptr::null_mut()),
live_tail: Cell::new(ptr::null_mut()),
quarantine_head: Cell::new(ptr::null_mut()),
live_bytes: AtomicUsize::new(0),
#[cfg(debug_assertions)]
owning_thread: AtomicU64::new(0),
}
}
#[inline(always)]
fn assert_owning_thread(&self) {
#[cfg(debug_assertions)]
{
let owner = self.owning_thread.load(Ordering::Relaxed);
if owner == 0 {
return;
}
let cur = debug_thread_stamp();
debug_assert_eq!(
owner, cur,
"StdArena: allocation on thread {cur}, but arena is owned by \
thread {owner} (arena alloc paths are single-threaded)"
);
}
}
#[inline]
fn live_head_slot(&self) -> *mut *mut Block {
ptr::from_ref(&self.live_head).cast_mut().cast::<*mut Block>()
}
#[inline]
fn quarantine_head_slot(&self) -> *mut *mut Block {
ptr::from_ref(&self.quarantine_head)
.cast_mut()
.cast::<*mut Block>()
}
#[inline]
pub fn allocated_bytes(&self) -> usize {
self.live_bytes.load(Ordering::Relaxed)
}
#[cold]
#[inline(never)]
pub fn reset(&mut self) {
debug_assert!(
self.owns,
"StdArena::reset() on a borrowing_default() arena — its blocks \
live for the process lifetime and must not be bulk-freed"
);
unsafe {
self.free_chain(self.live_head.take());
self.free_chain(self.quarantine_head.take());
}
self.live_bytes.store(0, Ordering::Relaxed);
self.live_tail.set(self.live_head_slot());
#[cfg(debug_assertions)]
self.owning_thread
.store(debug_thread_stamp(), Ordering::Relaxed);
}
#[inline]
pub fn reset_retain_with_limit(&mut self, limit: usize) -> bool {
if !self.owns {
self.reset();
return false;
}
if self.allocated_bytes() <= limit {
let live = self.live_head.get();
if !live.is_null() {
let live_tail_slot = self.live_tail.replace(self.live_head_slot());
self.live_head.set(ptr::null_mut());
unsafe {
(*live).pprev = self.quarantine_head_slot();
let old_quarantine = self.quarantine_head.get();
*live_tail_slot = old_quarantine;
if !old_quarantine.is_null() {
(*old_quarantine).pprev = live_tail_slot;
}
}
self.quarantine_head.set(live);
}
#[cfg(debug_assertions)]
self.owning_thread
.store(debug_thread_stamp(), Ordering::Relaxed);
return true;
}
unsafe {
self.free_chain(self.live_head.take());
self.free_chain(self.quarantine_head.take());
}
self.live_bytes.store(0, Ordering::Relaxed);
self.live_tail.set(self.live_head_slot());
#[cfg(debug_assertions)]
self.owning_thread
.store(debug_thread_stamp(), Ordering::Relaxed);
false
}
#[inline]
pub fn gc(&self) {}
#[inline]
pub fn help_catch_memory_issues(&self) {}
#[inline]
pub fn resize_in_place(&self, _ptr: NonNull<u8>, _old_len: usize, _new_len: usize) -> bool {
false
}
fn remap(&self, ptr: NonNull<u8>, old_len: usize, new_len: usize, align: usize) -> *mut u8 {
let new = self.alloc_block(new_len, align, false);
if new.is_null() {
return ptr::null_mut();
}
unsafe {
ptr::copy_nonoverlapping(ptr.as_ptr(), new, old_len.min(new_len));
self.free_payload(ptr, old_len);
}
new
}
#[inline]
fn alloc_block(&self, size: usize, align: usize, zeroed: bool) -> *mut u8 {
self.assert_owning_thread();
if align <= 8 {
let total = HEADER_LEN + size;
let base = self.raw_calloc(total, zeroed);
if base.is_null() {
return ptr::null_mut();
}
unsafe { (*base.cast::<Block>()).payload = ptr::null_mut() };
unsafe { self.link_block(base.cast::<Block>()) };
self.live_bytes.fetch_add(size, Ordering::Relaxed);
unsafe { base.add(HEADER_LEN) }
} else {
let storage_len = align + size; let storage = self.raw_calloc(storage_len, zeroed);
if storage.is_null() {
return ptr::null_mut();
}
let header = self.raw_alloc(HEADER_LEN).cast::<Block>();
if header.is_null() {
unsafe { self.raw_free(storage) };
return ptr::null_mut();
}
let user = unsafe { storage.add(align) };
unsafe {
(*header).payload = storage;
ptr::write(user.sub(8).cast::<*mut Block>(), header);
self.link_block(header);
}
self.live_bytes.fetch_add(size, Ordering::Relaxed);
user
}
}
unsafe fn link_block(&self, block: *mut Block) {
unsafe {
let prev_head = self.live_head.get();
(*block).next = prev_head;
(*block).pprev = self.live_head_slot();
if !prev_head.is_null() {
(*prev_head).pprev = ptr::addr_of_mut!((*block).next);
} else {
self.live_tail.set(ptr::addr_of_mut!((*block).next));
}
self.live_head.set(block);
}
}
#[inline]
fn raw_alloc(&self, total: usize) -> *mut u8 {
match C_ALLOCATOR.raw_alloc(total, crate::Alignment::from_byte_units(16), 0) {
Some(p) => p,
None => ptr::null_mut(),
}
}
#[inline]
fn raw_calloc(&self, total: usize, zeroed: bool) -> *mut u8 {
if zeroed {
unsafe { libc::calloc(1, total).cast() }
} else {
self.raw_alloc(total)
}
}
#[inline]
unsafe fn raw_free(&self, base: *mut u8) {
unsafe { libc::free(base.cast()) }
}
unsafe fn unlink_block(&self, node: *mut Block) {
unsafe {
let next = (*node).next;
*(*node).pprev = next;
if !next.is_null() {
(*next).pprev = (*node).pprev;
}
if self.live_tail.get() == ptr::addr_of_mut!((*node).next) {
self.live_tail.set((*node).pprev);
}
}
}
unsafe fn free_payload(&self, ptr: NonNull<u8>, size: usize) {
let user = ptr.as_ptr();
let word = unsafe { ptr::read(user.sub(8).cast::<*mut Block>()) };
let (header, storage) = if word.is_null() {
let base = unsafe { user.sub(HEADER_LEN).cast::<Block>() };
(base, ptr::null_mut())
} else {
let header = word;
let storage = unsafe { (*header).payload };
(header, storage)
};
unsafe { self.unlink_block(header) };
let _ = self
.live_bytes
.try_update(Ordering::Relaxed, Ordering::Relaxed, |v| v.checked_sub(size));
unsafe {
if !storage.is_null() {
self.raw_free(storage);
}
self.raw_free(header.cast::<u8>());
}
}
unsafe fn free_chain(&self, head: *mut Block) {
let mut cur = head;
while !cur.is_null() {
let node = unsafe { &*cur };
let next = node.next;
unsafe {
if !node.payload.is_null() {
self.raw_free(node.payload);
}
self.raw_free(cur.cast::<u8>());
}
cur = next;
}
}
#[inline]
pub fn alloc_layout(&self, layout: Layout) -> NonNull<u8> {
let p = self.alloc_block(layout.size(), layout.align(), false);
NonNull::new(p).unwrap_or_else(|| crate::out_of_memory())
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc<T>(&self, val: T) -> &mut T {
let p = self.alloc_layout(Layout::new::<T>()).cast::<T>();
unsafe {
p.as_ptr().write(val);
&mut *p.as_ptr()
}
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_str(&self, s: &str) -> &mut str {
let bytes = self.alloc_slice_copy(s.as_bytes());
unsafe { core::str::from_utf8_unchecked_mut(bytes) }
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_slice_copy<T: Copy>(&self, src: &[T]) -> &mut [T] {
let layout = Layout::for_value(src);
let dst = self.alloc_layout(layout).cast::<T>();
unsafe {
ptr::copy_nonoverlapping(src.as_ptr(), dst.as_ptr(), src.len());
core::slice::from_raw_parts_mut(dst.as_ptr(), src.len())
}
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_slice_clone<T: Clone>(&self, src: &[T]) -> &mut [T] {
self.alloc_slice_fill_iter(src.iter().cloned())
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_slice_fill_default<T: Default>(&self, len: usize) -> &mut [T] {
self.alloc_slice_fill_with(len, |_| T::default())
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_slice_fill_copy<T: Copy>(&self, len: usize, value: T) -> &mut [T] {
self.alloc_slice_fill_with(len, |_| value)
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_slice_fill_with<T, F>(&self, len: usize, mut f: F) -> &mut [T]
where
F: FnMut(usize) -> T,
{
let layout = Layout::array::<T>(len).unwrap_or_else(|_| crate::out_of_memory());
let dst = self.alloc_layout(layout).cast::<T>();
unsafe {
for i in 0..len {
dst.as_ptr().add(i).write(f(i));
}
core::slice::from_raw_parts_mut(dst.as_ptr(), len)
}
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_slice_fill_iter<T, I>(&self, iter: I) -> &mut [T]
where
I: IntoIterator<Item = T>,
I::IntoIter: ExactSizeIterator,
{
let mut iter = iter.into_iter();
let len = iter.len();
self.alloc_slice_fill_with(len, |_| {
iter.next()
.expect("ExactSizeIterator under-reported length")
})
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_uninit_slice<T>(&self, len: usize) -> &mut [MaybeUninit<T>] {
let layout = Layout::array::<T>(len).unwrap_or_else(|_| crate::out_of_memory());
let dst = self.alloc_layout(layout).cast::<MaybeUninit<T>>();
unsafe { core::slice::from_raw_parts_mut(dst.as_ptr(), len) }
}
#[inline]
pub fn std_allocator(&self) -> crate::StdAllocator {
crate::StdAllocator {
ptr: ptr::from_ref(self).cast_mut().cast(),
vtable: &HEAP_ALLOCATOR_VTABLE,
}
}
#[inline]
pub fn is_instance(alloc: &crate::StdAllocator) -> bool {
core::ptr::eq(alloc.vtable, &raw const HEAP_ALLOCATOR_VTABLE)
}
#[inline]
pub fn std_vtables() -> [&'static crate::AllocatorVTable; 1] {
[&HEAP_ALLOCATOR_VTABLE]
}
}
impl Drop for StdArena {
fn drop(&mut self) {
if self.owns {
unsafe {
self.free_chain(self.live_head.take());
self.free_chain(self.quarantine_head.take());
}
self.live_bytes.store(0, Ordering::Relaxed);
}
}
}
#[inline(always)]
fn alloc_result(p: *mut u8, size: usize) -> Result<NonNull<[u8]>, AllocError> {
NonNull::new(p)
.map(|p| NonNull::slice_from_raw_parts(p, size))
.ok_or(AllocError)
}
unsafe impl Allocator for &StdArena {
#[inline]
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
alloc_result(
self.alloc_block(layout.size(), layout.align(), false),
layout.size(),
)
}
#[inline]
fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
alloc_result(
self.alloc_block(layout.size(), layout.align(), true),
layout.size(),
)
}
#[inline]
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
unsafe { self.free_payload(ptr, layout.size()) }
}
#[inline]
unsafe fn grow(
&self,
ptr: NonNull<u8>,
old: Layout,
new: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
alloc_result(
self.remap(ptr, old.size(), new.size(), new.align()),
new.size(),
)
}
#[inline]
unsafe fn grow_zeroed(
&self,
ptr: NonNull<u8>,
old: Layout,
new: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
let p = self.remap(ptr, old.size(), new.size(), new.align());
let p = NonNull::new(p).ok_or(AllocError)?;
unsafe { ptr::write_bytes(p.as_ptr().add(old.size()), 0, new.size() - old.size()) };
Ok(NonNull::slice_from_raw_parts(p, new.size()))
}
#[inline]
unsafe fn shrink(
&self,
ptr: NonNull<u8>,
old: Layout,
new: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
alloc_result(
self.remap(ptr, old.size(), new.size(), new.align()),
new.size(),
)
}
}
unsafe fn vtable_alloc(
ctx: *mut core::ffi::c_void,
len: usize,
a: crate::Alignment,
_ra: usize,
) -> *mut u8 {
let arena = unsafe { &*ctx.cast::<StdArena>() };
arena.alloc_block(len, a.to_byte_units(), false)
}
unsafe fn vtable_resize(
ctx: *mut core::ffi::c_void,
_buf: &mut [u8],
_a: crate::Alignment,
_new_len: usize,
_ra: usize,
) -> bool {
let _ = ctx;
false
}
unsafe fn vtable_remap(
ctx: *mut core::ffi::c_void,
buf: &mut [u8],
a: crate::Alignment,
new_len: usize,
_ra: usize,
) -> *mut u8 {
let arena = unsafe { &*ctx.cast::<StdArena>() };
arena.remap(
unsafe { NonNull::new_unchecked(buf.as_mut_ptr()) },
buf.len(),
new_len,
a.to_byte_units(),
)
}
unsafe fn vtable_free(
ctx: *mut core::ffi::c_void,
buf: &mut [u8],
_a: crate::Alignment,
_ra: usize,
) {
unsafe {
(*ctx.cast::<StdArena>()).free_payload(NonNull::new_unchecked(buf.as_mut_ptr()), buf.len())
}
}
pub(crate) static HEAP_ALLOCATOR_VTABLE: crate::AllocatorVTable = crate::AllocatorVTable {
alloc: vtable_alloc,
resize: vtable_resize,
remap: vtable_remap,
free: vtable_free,
};
pub struct ArenaString<'a> {
buf: crate::core_alloc::AllocVec<u8, &'a StdArena>,
}
impl<'a> ArenaString<'a> {
#[inline]
pub fn new_in(arena: &'a StdArena) -> Self {
Self {
buf: crate::core_alloc::AllocVec::new_in(arena),
}
}
#[inline]
pub fn with_capacity_in(cap: usize, arena: &'a StdArena) -> Self {
Self {
buf: crate::core_alloc::AllocVec::with_capacity_in(cap, arena),
}
}
#[inline]
pub fn from_str_in(s: &str, arena: &'a StdArena) -> Self {
let mut buf = crate::core_alloc::AllocVec::with_capacity_in(s.len(), arena);
buf.extend_from_slice(s.as_bytes());
Self { buf }
}
#[inline]
pub fn push_str(&mut self, s: &str) {
self.buf.extend_from_slice(s.as_bytes());
}
#[inline]
pub fn as_str(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(&self.buf) }
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
&self.buf
}
#[inline]
pub fn len(&self) -> usize {
self.buf.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
#[inline]
pub fn into_bump_str(self) -> &'a str {
let bytes = self.buf.into_bump_slice();
unsafe { core::str::from_utf8_unchecked(bytes) }
}
}
impl core::fmt::Write for ArenaString<'_> {
#[inline]
fn write_str(&mut self, s: &str) -> core::fmt::Result {
self.buf.extend_from_slice(s.as_bytes());
Ok(())
}
}
pub trait ArenaVecExt<'a, T> {
fn from_iter_in<I: IntoIterator<Item = T>>(iter: I, arena: &'a StdArena) -> Self;
fn into_bump_slice(self) -> &'a [T];
fn into_bump_slice_mut(self) -> &'a mut [T];
fn bump(&self) -> &'a StdArena;
}
impl<'a, T> ArenaVecExt<'a, T> for crate::core_alloc::AllocVec<T, &'a StdArena> {
#[inline]
fn from_iter_in<I: IntoIterator<Item = T>>(iter: I, arena: &'a StdArena) -> Self {
let iter = iter.into_iter();
let (lo, _) = iter.size_hint();
let mut v = crate::core_alloc::AllocVec::with_capacity_in(lo, arena);
v.extend(iter);
v
}
#[inline]
fn into_bump_slice(self) -> &'a [T] {
&*self.leak()
}
#[inline]
fn into_bump_slice_mut(self) -> &'a mut [T] {
self.leak()
}
#[inline]
fn bump(&self) -> &'a StdArena {
*self.allocator()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core_alloc::AllocVec;
fn assert_send_sync<T: Send + Sync>() {}
fn talloc(arena: &StdArena, size: usize, align: usize) -> NonNull<[u8]> {
arena
.allocate(Layout::from_size_align(size, align).unwrap())
.unwrap()
}
unsafe fn tfree(arena: &StdArena, p: NonNull<[u8]>, size: usize, align: usize) {
let ptr = NonNull::new(p.as_ptr().cast::<u8>()).unwrap();
unsafe { arena.deallocate(ptr, Layout::from_size_align(size, align).unwrap()) }
}
#[test]
fn send_sync_static_asserts() {
assert_send_sync::<StdArena>();
}
#[test]
fn single_alloc_lifecycle_and_reuse_after_reset() {
let mut arena = StdArena::new();
let mut p = talloc(&arena, 64, 8);
unsafe {
p.as_mut().fill(0xAB);
tfree(&arena, p, 64, 8);
}
arena.reset();
assert_eq!(arena.allocated_bytes(), 0);
let b = talloc(&arena, 64, 8);
assert_eq!(b.len(), 64);
unsafe { tfree(&arena, b, 64, 8) };
assert_eq!(arena.allocated_bytes(), 0);
}
#[test]
fn dual_alloc_lifecycle_align_16_and_32() {
let mut arena = StdArena::new();
let mut a = talloc(&arena, 100, 16);
let mut b = talloc(&arena, 100, 32);
unsafe {
a.as_mut().fill(0x11);
b.as_mut().fill(0x22);
assert!(a.as_ref().iter().all(|&x| x == 0x11));
assert!(b.as_ref().iter().all(|&x| x == 0x22));
assert_eq!(arena.allocated_bytes(), 200);
tfree(&arena, a, 100, 16);
tfree(&arena, b, 100, 32);
}
assert_eq!(arena.allocated_bytes(), 0);
arena.reset();
assert_eq!(arena.allocated_bytes(), 0);
}
#[test]
fn deallocate_is_immediate_real_free() {
let arena = StdArena::new();
let p = talloc(&arena, 128, 8);
assert_eq!(arena.allocated_bytes(), 128);
unsafe { tfree(&arena, p, 128, 8) };
assert_eq!(arena.allocated_bytes(), 0);
}
#[test]
fn dealloc_unlink_keeps_reset_off_freed_memory() {
let mut arena = StdArena::new();
let mut live = Vec::new();
for i in 0..16 {
let p = talloc(&arena, 48, if i % 2 == 0 { 8 } else { 16 });
if i % 3 == 0 {
unsafe { tfree(&arena, p, 48, if i % 2 == 0 { 8 } else { 16 }) };
} else {
live.push(p);
}
}
assert_eq!(arena.allocated_bytes(), 16 * 48 - (6 * 48));
arena.reset();
assert_eq!(arena.allocated_bytes(), 0);
drop(live);
}
#[test]
fn head_and_tail_node_unlink() {
let mut arena = StdArena::new();
let a = talloc(&arena, 32, 8); let b = talloc(&arena, 32, 8); let c = talloc(&arena, 32, 8); assert_eq!(arena.allocated_bytes(), 96);
unsafe { tfree(&arena, c, 32, 8) };
assert_eq!(arena.allocated_bytes(), 64);
unsafe { tfree(&arena, a, 32, 8) };
assert_eq!(arena.allocated_bytes(), 32);
unsafe { tfree(&arena, b, 32, 8) };
assert_eq!(arena.allocated_bytes(), 0);
let d = talloc(&arena, 32, 8);
unsafe { tfree(&arena, d, 32, 8) };
arena.reset();
}
#[test]
fn cross_arena_dealloc_via_transfer_contract() {
let a = StdArena::new();
let b = StdArena::new();
let p = talloc(&a, 64, 16);
unsafe { tfree(&b, p, 64, 16) };
let mut a = a;
a.reset();
let c = StdArena::new();
let q = talloc(&a, 64, 16);
let handle = c.std_allocator();
let qs = unsafe { core::slice::from_raw_parts_mut(q.as_ptr().cast::<u8>(), 64) };
handle.raw_free(qs, crate::Alignment::from_byte_units(16), 0);
a.reset();
}
#[test]
fn retain_under_limit_splices_whole_chain() {
let mut arena = StdArena::new();
let p1 = talloc(&arena, 100, 8);
let p2 = talloc(&arena, 100, 8);
let p3 = talloc(&arena, 100, 8);
assert_eq!(arena.allocated_bytes(), 300);
assert!(arena.reset_retain_with_limit(500));
assert_eq!(arena.allocated_bytes(), 300, "retain must not reclaim");
let fresh = talloc(&arena, 100, 8);
assert_eq!(arena.allocated_bytes(), 400);
unsafe { tfree(&arena, fresh, 100, 8) };
arena.reset();
assert_eq!(arena.allocated_bytes(), 0);
let _ = (p1, p2, p3); }
#[test]
fn retain_over_limit_frees_both_chains() {
let mut arena = StdArena::new();
let p1 = talloc(&arena, 100, 8);
let p2 = talloc(&arena, 100, 8);
assert_eq!(arena.allocated_bytes(), 200);
assert!(!arena.reset_retain_with_limit(150));
assert_eq!(arena.allocated_bytes(), 0);
let _ = (p1, p2); let p = talloc(&arena, 64, 8);
unsafe { tfree(&arena, p, 64, 8) };
}
#[test]
fn retain_fast_path_returns_true_and_keeps_live_chain() {
let mut arena = StdArena::new();
let p = talloc(&arena, 64, 8);
assert!(arena.reset_retain_with_limit(1 << 20));
assert_eq!(arena.allocated_bytes(), 64);
unsafe { tfree(&arena, p, 64, 8) };
assert_eq!(arena.allocated_bytes(), 0);
}
#[test]
fn stale_handle_dealloc_of_quarantined_block() {
let mut arena = StdArena::new();
let other = StdArena::new();
let p1 = talloc(&arena, 100, 16);
let p2 = talloc(&arena, 100, 16);
assert!(arena.reset_retain_with_limit(1 << 20)); assert_eq!(arena.allocated_bytes(), 200);
unsafe { tfree(&other, p1, 100, 16) };
assert_eq!(arena.allocated_bytes(), 200);
arena.reset(); assert_eq!(arena.allocated_bytes(), 0);
let _ = p2;
}
#[test]
fn grow_preserves_data_through_many_reallocations() {
let arena = StdArena::new();
let mut v: AllocVec<u8, &StdArena> = AllocVec::new_in(&arena);
for i in 0..100_000u32 {
v.push((i % 251) as u8);
}
assert_eq!(v.len(), 100_000);
for (i, b) in v.iter().enumerate() {
assert_eq!(*b, (i as u32 % 251) as u8);
}
drop(v);
assert_eq!(arena.allocated_bytes(), 0);
}
#[test]
fn allocate_zeroed_and_grow_zeroed() {
let arena = &StdArena::new();
let old = Layout::from_size_align(32, 16).unwrap();
let new = Layout::from_size_align(64, 16).unwrap();
let p = arena.allocate_zeroed(old).unwrap();
unsafe {
assert!(p.as_ref().iter().all(|&b| b == 0));
}
let ptr = p.as_ptr().cast::<u8>();
let grown = unsafe { arena.grow_zeroed(NonNull::new(ptr).unwrap(), old, new) }.unwrap();
unsafe {
let bytes = grown.as_ref();
assert!(bytes[..32].iter().all(|&b| b == 0), "prefix must survive grow");
assert!(bytes[32..].iter().all(|&b| b == 0), "grow_zeroed tail must be zero");
}
unsafe { arena.deallocate(NonNull::new(grown.as_ptr().cast::<u8>()).unwrap(), new) };
}
#[test]
fn bumpalo_surface_roundtrip() {
let arena = StdArena::new();
let n = arena.alloc(42u64);
assert_eq!(*n, 42);
assert_eq!(arena.alloc_str("bao"), "bao");
let slice = arena.alloc_slice_copy(&[1u32, 2, 3]);
assert_eq!(slice, &[1, 2, 3]);
let filled = arena.alloc_slice_fill_iter(0..4u8);
assert_eq!(filled, &[0, 1, 2, 3]);
let uninit = arena.alloc_uninit_slice::<u64>(4);
for (i, slot) in uninit.iter_mut().enumerate() {
slot.write(i as u64 * 7);
}
let init: &[u64] = unsafe { &*(uninit as *const [MaybeUninit<u64>] as *const [u64]) };
assert_eq!(init, &[0, 7, 14, 21]);
}
#[test]
fn arena_string_and_formatting() {
let arena = StdArena::new();
let mut s = ArenaString::new_in(&arena);
core::fmt::Write::write_fmt(&mut s, format_args!("{}-{}", 12, "ab")).unwrap();
s.push_str("!");
assert_eq!(s.as_str(), "12-ab!");
assert_eq!(s.len(), 6);
assert!(!s.is_empty());
let leaked: &str = s.into_bump_str();
assert_eq!(leaked, "12-ab!");
let from = ArenaString::from_str_in("xyz", &arena);
assert_eq!(from.as_bytes(), b"xyz");
}
#[test]
fn vec_ext_from_iter_and_leak() {
let arena = StdArena::new();
let v: AllocVec<i32, &StdArena> = ArenaVecExt::from_iter_in(0..5i32, &arena);
assert_eq!(&*v, &[0, 1, 2, 3, 4]);
assert_eq!(v.bump() as *const StdArena, &arena as *const StdArena);
let leaked = v.into_bump_slice();
assert_eq!(leaked, &[0, 1, 2, 3, 4]);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "borrowing_default")]
fn borrowing_default_forbids_reset() {
let mut arena = StdArena::borrowing_default();
arena.reset();
}
#[test]
fn borrowing_default_allocates_and_drops() {
let arena = StdArena::borrowing_default();
let p = talloc(&arena, 32, 8);
unsafe { tfree(&arena, p, 32, 8) };
drop(arena); }
#[test]
fn resize_in_place_is_false_and_remap_preserves_prefix() {
let arena = &StdArena::new();
let mut p = talloc(arena, 32, 8);
assert!(!arena.resize_in_place(NonNull::new(p.as_ptr().cast::<u8>()).unwrap(), 32, 64));
unsafe { p.as_mut()[..8].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]) };
let np = arena.remap(
NonNull::new(p.as_ptr().cast::<u8>()).unwrap(),
32,
64,
8,
);
assert!(!np.is_null());
unsafe { assert_eq!(*np.cast::<[u8; 8]>(), [1, 2, 3, 4, 5, 6, 7, 8]) };
unsafe {
arena.deallocate(
NonNull::new(np).unwrap(),
Layout::from_size_align(64, 8).unwrap(),
)
};
}
#[test]
fn std_allocator_vtable_identity() {
let arena = StdArena::new();
let handle = arena.std_allocator();
assert!(StdArena::is_instance(&handle));
assert_eq!(StdArena::std_vtables().len(), 1);
let p = handle
.raw_alloc(24, crate::Alignment::from_byte_units(16), 0)
.unwrap();
let slice = unsafe { core::slice::from_raw_parts_mut(p, 24) };
assert!(!handle.raw_resize(slice, crate::Alignment::from_byte_units(16), 48, 0));
handle.raw_free(slice, crate::Alignment::from_byte_units(16), 0);
assert_eq!(arena.allocated_bytes(), 0);
}
#[test]
fn zero_sized_layout_roundtrip() {
let arena = StdArena::new();
let p = talloc(&arena, 0, 1);
unsafe { tfree(&arena, p, 0, 1) };
assert_eq!(arena.allocated_bytes(), 0);
}
#[test]
fn many_allocations_free_interleaved_then_reset() {
let mut arena = StdArena::new();
let mut held = Vec::new();
for i in 0..1000u32 {
let p = talloc(&arena, (i % 64 + 1) as usize, if i % 4 == 0 { 16 } else { 8 });
if i % 3 == 0 {
unsafe {
tfree(&arena, p, (i % 64 + 1) as usize, if i % 4 == 0 { 16 } else { 8 })
};
} else {
held.push(p);
}
}
arena.reset();
assert_eq!(arena.allocated_bytes(), 0);
drop(held);
}
}