Skip to main content

subms_arena_allocator/
lib.rs

1//! Fixed-capacity bump-pointer arena. Allocate fixed-layout values into a
2//! single pre-sized byte buffer; `reset()` rewinds the bump cursor so the
3//! next request can reuse the entire arena.
4//!
5//! **Drop is NOT run on items in the arena.** Anything with a non-trivial
6//! `Drop` (e.g. `String`, `Vec`) will leak its heap allocation if you store
7//! it in this arena. [`Bump::alloc_copy`] is constrained to `Copy` types as
8//! the safe public surface.
9//!
10//! Base is single-chunk and fixed-capacity. When the chunk is exhausted
11//! `alloc_*` panics and `try_alloc_*` returns `None`. Opt into the
12//! `growable` feature for the auto-grow variant.
13//!
14//! ```
15//! use subms_arena_allocator::Bump;
16//! let mut a = Bump::with_capacity(1024);
17//! let x: &mut u32 = a.alloc_copy(42u32);
18//! assert_eq!(*x, 42);
19//! a.reset();
20//! ```
21
22use std::alloc::{Layout, alloc, dealloc};
23use std::ptr;
24
25/// Fixed-capacity bump-pointer arena.
26pub struct Bump {
27    ptr: *mut u8,
28    layout: Layout,
29    cursor: usize,
30}
31
32impl Bump {
33    /// New empty arena with a 4 KiB chunk.
34    pub fn new() -> Self {
35        Self::with_capacity(4096)
36    }
37
38    /// New arena, pre-allocating a single chunk of `capacity` bytes
39    /// (promoted to a 64-byte floor and 16-byte alignment).
40    pub fn with_capacity(capacity: usize) -> Self {
41        let capacity = capacity.max(64);
42        let layout = Layout::from_size_align(capacity, 16).expect("layout");
43        let ptr = unsafe { alloc(layout) };
44        assert!(!ptr.is_null(), "OOM allocating arena chunk");
45        Self {
46            ptr,
47            layout,
48            cursor: 0,
49        }
50    }
51
52    /// Allocate a `Copy` value. Panics if the arena is out of room.
53    pub fn alloc_copy<T: Copy>(&mut self, value: T) -> &mut T {
54        let cursor = self.cursor;
55        let cap = self.layout.size();
56        match self.try_alloc_copy(value) {
57            Some(r) => r,
58            None => panic!(
59                "Bump out of capacity: cursor={} layout_size={} requested={}",
60                cursor,
61                cap,
62                std::mem::size_of::<T>(),
63            ),
64        }
65    }
66
67    /// Fallible alloc. Returns `None` if the arena can't fit the value
68    /// at its natural alignment.
69    pub fn try_alloc_copy<T: Copy>(&mut self, value: T) -> Option<&mut T> {
70        let layout = Layout::new::<T>();
71        let p = self.try_alloc_raw(layout)?;
72        unsafe {
73            ptr::write(p as *mut T, value);
74            Some(&mut *(p as *mut T))
75        }
76    }
77
78    /// Allocate `layout.size()` bytes aligned to `layout.align()`.
79    /// Panics if the request doesn't fit.
80    pub fn alloc_raw(&mut self, layout: Layout) -> *mut u8 {
81        let cursor = self.cursor;
82        let cap = self.layout.size();
83        let requested = layout.size();
84        match self.try_alloc_raw(layout) {
85            Some(p) => p,
86            None => panic!(
87                "Bump out of capacity: cursor={cursor} layout_size={cap} requested={requested}",
88            ),
89        }
90    }
91
92    /// Fallible raw alloc. Returns `None` if the request doesn't fit.
93    pub fn try_alloc_raw(&mut self, layout: Layout) -> Option<*mut u8> {
94        let size = layout.size();
95        let align = layout.align();
96        let base = self.ptr as usize;
97        let aligned_abs = align_up(base + self.cursor, align);
98        let aligned = aligned_abs - base;
99        let end = aligned.checked_add(size)?;
100        if end > self.layout.size() {
101            return None;
102        }
103        self.cursor = end;
104        Some(unsafe { self.ptr.add(aligned) })
105    }
106
107    /// Rewind to empty. The buffer is retained for reuse.
108    pub fn reset(&mut self) {
109        self.cursor = 0;
110    }
111
112    /// Bytes used so far in the current chunk.
113    pub fn used(&self) -> usize {
114        self.cursor
115    }
116
117    /// Total capacity of the single backing chunk.
118    pub fn capacity(&self) -> usize {
119        self.layout.size()
120    }
121
122    /// Backwards-compatible alias for [`capacity`].
123    pub fn total_capacity(&self) -> usize {
124        self.capacity()
125    }
126}
127
128impl Default for Bump {
129    fn default() -> Self {
130        Self::new()
131    }
132}
133
134impl Drop for Bump {
135    fn drop(&mut self) {
136        unsafe { dealloc(self.ptr, self.layout) };
137    }
138}
139
140/// `(p + align - 1) & !(align - 1)` - rounds up to the next aligned address.
141#[inline]
142pub(crate) fn align_up(p: usize, align: usize) -> usize {
143    debug_assert!(align.is_power_of_two(), "alignment must be a power of two");
144    (p + align - 1) & !(align - 1)
145}
146
147#[cfg(feature = "harness")]
148pub mod recipe;
149
150#[cfg(any(
151    feature = "typed",
152    feature = "growable",
153    feature = "stats",
154    feature = "aligned",
155    feature = "freelist",
156))]
157pub mod features;
158
159#[cfg(feature = "aligned")]
160pub use features::aligned::AlignedBump;
161#[cfg(feature = "freelist")]
162pub use features::freelist::FreelistBump;
163#[cfg(feature = "growable")]
164pub use features::growable::GrowableBump;
165#[cfg(feature = "stats")]
166pub use features::stats::{BumpStats, StatsBump};
167#[cfg(feature = "typed")]
168pub use features::typed::TypedArena;