Skip to main content

bhc_arena/
lib.rs

1//! Arena allocators for efficient compiler data structure allocation.
2//!
3//! This crate provides arena allocators that enable fast allocation
4//! of compiler data structures with automatic bulk deallocation.
5
6#![warn(missing_docs)]
7
8use std::cell::Cell;
9
10pub use bumpalo::Bump;
11pub use typed_arena::Arena as TypedArena;
12
13/// A thread-local arena for fast, scoped allocations.
14///
15/// All allocations from this arena are freed when the arena is dropped.
16/// This is ideal for per-function or per-module compiler passes.
17#[derive(Debug)]
18pub struct Arena {
19    bump: Bump,
20    bytes_allocated: Cell<usize>,
21}
22
23impl Arena {
24    /// Create a new empty arena.
25    #[must_use]
26    pub fn new() -> Self {
27        Self {
28            bump: Bump::new(),
29            bytes_allocated: Cell::new(0),
30        }
31    }
32
33    /// Create a new arena with the specified capacity.
34    #[must_use]
35    pub fn with_capacity(capacity: usize) -> Self {
36        Self {
37            bump: Bump::with_capacity(capacity),
38            bytes_allocated: Cell::new(0),
39        }
40    }
41
42    /// Allocate a value in the arena.
43    pub fn alloc<T>(&self, val: T) -> &mut T {
44        self.bytes_allocated
45            .set(self.bytes_allocated.get() + std::mem::size_of::<T>());
46        self.bump.alloc(val)
47    }
48
49    /// Allocate a slice in the arena.
50    pub fn alloc_slice<T: Copy>(&self, slice: &[T]) -> &mut [T] {
51        self.bytes_allocated
52            .set(self.bytes_allocated.get() + std::mem::size_of_val(slice));
53        self.bump.alloc_slice_copy(slice)
54    }
55
56    /// Allocate a string in the arena.
57    pub fn alloc_str(&self, s: &str) -> &str {
58        self.bytes_allocated
59            .set(self.bytes_allocated.get() + s.len());
60        self.bump.alloc_str(s)
61    }
62
63    /// Allocate an iterator's items in the arena.
64    pub fn alloc_from_iter<T, I>(&self, iter: I) -> &mut [T]
65    where
66        I: IntoIterator<Item = T>,
67        I::IntoIter: ExactSizeIterator,
68    {
69        let result = self.bump.alloc_slice_fill_iter(iter);
70        self.bytes_allocated
71            .set(self.bytes_allocated.get() + std::mem::size_of_val(result));
72        result
73    }
74
75    /// Get the total bytes allocated in this arena.
76    #[must_use]
77    pub fn bytes_allocated(&self) -> usize {
78        self.bytes_allocated.get()
79    }
80
81    /// Get the allocated bytes including overhead.
82    #[must_use]
83    pub fn allocated_bytes_including_metadata(&self) -> usize {
84        self.bump.allocated_bytes()
85    }
86
87    /// Reset the arena, deallocating all values.
88    ///
89    /// # Safety
90    ///
91    /// All references to arena-allocated values become invalid after this call.
92    pub unsafe fn reset(&mut self) {
93        self.bump.reset();
94        self.bytes_allocated.set(0);
95    }
96}
97
98impl Default for Arena {
99    fn default() -> Self {
100        Self::new()
101    }
102}
103
104/// A dropless arena for types that don't need drop.
105///
106/// This is more efficient than `Arena` for types that implement `Copy`
107/// or otherwise don't need destructors to run.
108#[derive(Debug)]
109pub struct DroplessArena {
110    bump: Bump,
111}
112
113impl DroplessArena {
114    /// Create a new dropless arena.
115    #[must_use]
116    pub fn new() -> Self {
117        Self { bump: Bump::new() }
118    }
119
120    /// Allocate a value in the arena.
121    ///
122    /// The value's destructor will never be called.
123    pub fn alloc<T>(&self, val: T) -> &mut T {
124        self.bump.alloc(val)
125    }
126
127    /// Allocate a slice from an iterator.
128    pub fn alloc_from_iter<T, I>(&self, iter: I) -> &mut [T]
129    where
130        I: IntoIterator<Item = T>,
131        I::IntoIter: ExactSizeIterator,
132    {
133        self.bump.alloc_slice_fill_iter(iter)
134    }
135}
136
137impl Default for DroplessArena {
138    fn default() -> Self {
139        Self::new()
140    }
141}
142
143/// A sync arena that can be shared across threads.
144#[derive(Debug)]
145pub struct SyncArena {
146    bump: parking_lot::Mutex<Bump>,
147}
148
149impl SyncArena {
150    /// Create a new sync arena.
151    #[must_use]
152    pub fn new() -> Self {
153        Self {
154            bump: parking_lot::Mutex::new(Bump::new()),
155        }
156    }
157
158    /// Allocate a value in the arena.
159    pub fn alloc<T: Send>(&self, val: T) -> &T {
160        // Safety: The arena lives longer than any references we hand out,
161        // and we're using a mutex for synchronization.
162        let bump = self.bump.lock();
163        let ptr = bump.alloc(val) as *const T;
164        unsafe { &*ptr }
165    }
166}
167
168impl Default for SyncArena {
169    fn default() -> Self {
170        Self::new()
171    }
172}
173
174// Safety: SyncArena uses internal synchronization
175unsafe impl Send for SyncArena {}
176unsafe impl Sync for SyncArena {}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn test_arena_allocation() {
184        let arena = Arena::new();
185        let x = arena.alloc(42);
186        let y = arena.alloc("hello");
187
188        assert_eq!(*x, 42);
189        assert_eq!(*y, "hello");
190    }
191
192    #[test]
193    fn test_arena_slice() {
194        let arena = Arena::new();
195        let slice = arena.alloc_slice(&[1, 2, 3, 4, 5]);
196
197        assert_eq!(slice, &[1, 2, 3, 4, 5]);
198    }
199
200    #[test]
201    fn test_arena_from_iter() {
202        let arena = Arena::new();
203        let slice = arena.alloc_from_iter(0..5);
204
205        assert_eq!(slice, &[0, 1, 2, 3, 4]);
206    }
207}