use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;
use core::alloc::Layout;
use core::cell::UnsafeCell;
use core::ptr::NonNull;
use crate::error::{Error, Result};
pub struct Bump {
chunk: Box<[u8]>,
state: UnsafeCell<State>,
}
struct State {
offset: usize,
}
impl Bump {
#[inline]
#[must_use]
pub fn new() -> Self {
Self {
chunk: Vec::<u8>::new().into_boxed_slice(),
state: UnsafeCell::new(State { offset: 0 }),
}
}
#[inline]
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
let buf: Vec<u8> = vec![0; capacity];
Self {
chunk: buf.into_boxed_slice(),
state: UnsafeCell::new(State { offset: 0 }),
}
}
#[inline]
#[must_use]
pub fn chunk_capacity(&self) -> usize {
self.chunk.len()
}
#[inline]
#[must_use]
pub fn allocated_bytes(&self) -> usize {
unsafe { (*self.state.get()).offset }
}
#[inline]
#[allow(
clippy::mut_from_ref,
reason = "interior mutability via UnsafeCell; each call returns a disjoint region of the chunk"
)]
pub fn alloc<T>(&self, value: T) -> &mut T {
match self.try_alloc(value) {
Ok(reference) => reference,
Err(_) => panic!("bump arena exhausted; allocate with `with_capacity` accordingly"),
}
}
#[allow(
clippy::mut_from_ref,
reason = "interior mutability via UnsafeCell; each call returns a disjoint region of the chunk"
)]
pub fn try_alloc<T>(&self, value: T) -> Result<&mut T> {
let layout = Layout::new::<T>();
let raw = self.try_alloc_layout(layout)?;
let typed = raw.cast::<T>();
unsafe { core::ptr::write(typed.as_ptr(), value) };
Ok(unsafe { &mut *typed.as_ptr() })
}
#[inline]
pub fn reset(&mut self) {
unsafe { (*self.state.get()).offset = 0 };
}
fn try_alloc_layout(&self, layout: Layout) -> Result<NonNull<u8>> {
let state = unsafe { &mut *self.state.get() };
let base = self.chunk.as_ptr() as usize;
let cursor = match base.checked_add(state.offset) {
Some(c) => c,
None => return Err(Error::CapacityExceeded),
};
let align_mask = layout.align().wrapping_sub(1);
let aligned = match cursor.checked_add(align_mask) {
Some(a) => a & !align_mask,
None => return Err(Error::CapacityExceeded),
};
let end = match aligned.checked_add(layout.size()) {
Some(e) => e,
None => return Err(Error::CapacityExceeded),
};
let limit = base.saturating_add(self.chunk.len());
if end > limit {
return Err(Error::CapacityExceeded);
}
state.offset = end - base;
if layout.size() == 0 {
return Ok(dangling_aligned(layout.align()));
}
let ptr = aligned as *mut u8;
NonNull::new(ptr).ok_or(Error::CapacityExceeded)
}
}
impl Default for Bump {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl core::fmt::Debug for Bump {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Bump")
.field("allocated_bytes", &self.allocated_bytes())
.field("chunk_capacity", &self.chunk_capacity())
.finish()
}
}
fn dangling_aligned(align: usize) -> NonNull<u8> {
unsafe { NonNull::new_unchecked(align as *mut u8) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alloc_returns_unique_reference() {
let bump = Bump::with_capacity(32);
let a = bump.alloc(1_u32);
let b = bump.alloc(2_u32);
assert_eq!(*a, 1);
assert_eq!(*b, 2);
assert!(bump.allocated_bytes() >= 8);
}
#[test]
fn try_alloc_fails_when_full() {
let bump = Bump::with_capacity(4);
let _ = bump.alloc(1_u32);
assert!(bump.try_alloc(2_u32).is_err());
}
#[test]
fn reset_clears_offset() {
let mut bump = Bump::with_capacity(16);
let _ = bump.alloc(42_u64);
assert!(bump.allocated_bytes() > 0);
bump.reset();
assert_eq!(bump.allocated_bytes(), 0);
let _ = bump.alloc(7_u64);
assert!(bump.allocated_bytes() > 0);
}
#[test]
fn alignment_is_respected() {
let bump = Bump::with_capacity(64);
let _ = bump.alloc(1_u8);
let aligned = bump.alloc(0xdead_beef_u32);
let addr = aligned as *const u32 as usize;
assert_eq!(addr % core::mem::align_of::<u32>(), 0);
}
#[test]
fn zst_alloc_does_not_advance_offset() {
let bump = Bump::with_capacity(8);
let before = bump.allocated_bytes();
let _: &mut () = bump.alloc(());
assert_eq!(bump.allocated_bytes(), before);
}
#[test]
fn empty_bump_only_satisfies_zsts() {
let bump = Bump::new();
assert_eq!(bump.chunk_capacity(), 0);
let _: &mut () = bump.alloc(());
assert!(bump.try_alloc(1_u8).is_err());
}
}