Skip to main content

haagenti_zstd/compress/
arena.rs

1//! Arena allocator for per-frame temporary allocations.
2//!
3//! This module provides a simple bump allocator that reduces allocation overhead
4//! during compression by reusing memory between frames.
5//!
6//! ## Benefits
7//!
8//! - **Reduced allocation overhead**: Single allocation per reset
9//! - **Cache-friendly**: Sequential memory access
10//! - **Fast reset**: O(1) reset between frames
11//!
12//! ## Usage
13//!
14//! ```ignore
15//! let mut arena = Arena::new(64 * 1024);  // 64KB arena
16//!
17//! // Allocate temporary buffers
18//! let literals = arena.alloc_slice(1024);
19//! let sequences = arena.alloc_slice(256);
20//!
21//! // Reset for next frame (O(1) operation)
22//! arena.reset();
23//! ```
24
25use core::cell::Cell;
26
27/// Default arena size (64KB) - covers most per-frame allocations.
28pub const DEFAULT_ARENA_SIZE: usize = 64 * 1024;
29
30/// A simple bump allocator for temporary allocations.
31///
32/// The arena pre-allocates a contiguous block of memory and hands out
33/// slices from it via bump pointer allocation. When the frame is complete,
34/// the arena is reset (O(1)) and the memory is reused.
35#[derive(Debug)]
36pub struct Arena {
37    /// The underlying memory buffer.
38    buffer: Vec<u8>,
39    /// Current allocation position (bump pointer).
40    /// Uses Cell for interior mutability to allow allocation from &self.
41    pos: Cell<usize>,
42    /// Peak usage tracking for diagnostics.
43    peak_usage: Cell<usize>,
44}
45
46impl Arena {
47    /// Create a new arena with the specified capacity.
48    pub fn new(capacity: usize) -> Self {
49        Self {
50            buffer: vec![0; capacity],
51            pos: Cell::new(0),
52            peak_usage: Cell::new(0),
53        }
54    }
55
56    /// Create a new arena with the default size (64KB).
57    pub fn with_default_size() -> Self {
58        Self::new(DEFAULT_ARENA_SIZE)
59    }
60
61    /// Reset the arena for reuse.
62    ///
63    /// This is an O(1) operation - it just resets the bump pointer.
64    /// The memory contents are not cleared.
65    #[inline]
66    pub fn reset(&self) {
67        // Track peak usage before reset
68        let current = self.pos.get();
69        if current > self.peak_usage.get() {
70            self.peak_usage.set(current);
71        }
72        self.pos.set(0);
73    }
74
75    /// Allocate a mutable slice of bytes.
76    ///
77    /// Returns `None` if there's not enough space in the arena.
78    /// SAFETY: This returns a mutable reference from a shared reference, which is safe
79    /// because we use interior mutability (Cell) to track the allocation position,
80    /// ensuring each region is only handed out once.
81    #[inline]
82    #[allow(clippy::mut_from_ref)]
83    pub fn alloc_slice(&self, len: usize) -> Option<&mut [u8]> {
84        let pos = self.pos.get();
85        let new_pos = pos.checked_add(len)?;
86
87        if new_pos > self.buffer.len() {
88            return None;
89        }
90
91        self.pos.set(new_pos);
92
93        // SAFETY: We're the only ones with access to this region
94        // because we just bumped the position past it.
95        unsafe {
96            let ptr = self.buffer.as_ptr().add(pos) as *mut u8;
97            Some(core::slice::from_raw_parts_mut(ptr, len))
98        }
99    }
100
101    /// Allocate a mutable slice of bytes, zeroed.
102    ///
103    /// Returns `None` if there's not enough space in the arena.
104    #[inline]
105    pub fn alloc_slice_zeroed(&self, len: usize) -> Option<&mut [u8]> {
106        let slice = self.alloc_slice(len)?;
107        slice.fill(0);
108        Some(slice)
109    }
110
111    /// Allocate a Vec-like buffer backed by the arena.
112    ///
113    /// Returns an ArenaVec that can grow up to the remaining capacity.
114    pub fn alloc_vec(&self, initial_capacity: usize) -> Option<ArenaVec<'_>> {
115        let pos = self.pos.get();
116        let max_capacity = self.buffer.len().saturating_sub(pos);
117
118        if initial_capacity > max_capacity {
119            return None;
120        }
121
122        // Reserve the initial capacity
123        self.pos.set(pos + initial_capacity);
124
125        Some(ArenaVec {
126            arena: self,
127            start: pos,
128            len: 0,
129            capacity: initial_capacity,
130        })
131    }
132
133    /// Get the current usage of the arena.
134    #[inline]
135    pub fn usage(&self) -> usize {
136        self.pos.get()
137    }
138
139    /// Get the total capacity of the arena.
140    #[inline]
141    pub fn capacity(&self) -> usize {
142        self.buffer.len()
143    }
144
145    /// Get the remaining capacity.
146    #[inline]
147    pub fn remaining(&self) -> usize {
148        self.buffer.len().saturating_sub(self.pos.get())
149    }
150
151    /// Get the peak usage (highest watermark since creation).
152    #[inline]
153    pub fn peak_usage(&self) -> usize {
154        self.peak_usage.get().max(self.pos.get())
155    }
156}
157
158/// A vector-like structure backed by arena memory.
159///
160/// This provides push/extend operations while using arena memory.
161/// When dropped, the arena space is not reclaimed (arena is bump-only).
162pub struct ArenaVec<'a> {
163    arena: &'a Arena,
164    start: usize,
165    len: usize,
166    capacity: usize,
167}
168
169impl<'a> ArenaVec<'a> {
170    /// Push a byte to the vector.
171    ///
172    /// Returns `false` if the vector is at capacity and cannot grow.
173    #[inline]
174    pub fn push(&mut self, value: u8) -> bool {
175        if self.len >= self.capacity {
176            // Try to grow
177            if !self.grow(1) {
178                return false;
179            }
180        }
181
182        // SAFETY: We have exclusive access to this region via the arena
183        unsafe {
184            let ptr = self.arena.buffer.as_ptr().add(self.start + self.len) as *mut u8;
185            *ptr = value;
186        }
187        self.len += 1;
188        true
189    }
190
191    /// Extend the vector with bytes from a slice.
192    ///
193    /// Returns `false` if there's not enough space.
194    pub fn extend_from_slice(&mut self, data: &[u8]) -> bool {
195        if self.len + data.len() > self.capacity {
196            // Try to grow
197            if !self.grow(data.len()) {
198                return false;
199            }
200        }
201
202        // SAFETY: We have exclusive access to this region via the arena
203        unsafe {
204            let ptr = self.arena.buffer.as_ptr().add(self.start + self.len) as *mut u8;
205            core::ptr::copy_nonoverlapping(data.as_ptr(), ptr, data.len());
206        }
207        self.len += data.len();
208        true
209    }
210
211    /// Try to grow the capacity.
212    fn grow(&mut self, additional: usize) -> bool {
213        let needed = self.len + additional;
214        if needed <= self.capacity {
215            return true;
216        }
217
218        // Check if we're at the end of the arena and can extend
219        let arena_pos = self.arena.pos.get();
220        let our_end = self.start + self.capacity;
221
222        if arena_pos == our_end {
223            // We're at the end, can grow in place
224            let new_capacity = (needed * 2).min(self.arena.remaining() + self.capacity);
225            if new_capacity >= needed {
226                let growth = new_capacity - self.capacity;
227                self.arena.pos.set(arena_pos + growth);
228                self.capacity = new_capacity;
229                return true;
230            }
231        }
232
233        false
234    }
235
236    /// Get the length of the vector.
237    #[inline]
238    pub fn len(&self) -> usize {
239        self.len
240    }
241
242    /// Check if the vector is empty.
243    #[inline]
244    pub fn is_empty(&self) -> bool {
245        self.len == 0
246    }
247
248    /// Get a slice of the vector contents.
249    #[inline]
250    pub fn as_slice(&self) -> &[u8] {
251        // SAFETY: We have valid data from start to start+len
252        unsafe {
253            let ptr = self.arena.buffer.as_ptr().add(self.start);
254            core::slice::from_raw_parts(ptr, self.len)
255        }
256    }
257
258    /// Convert to a regular Vec (copies the data).
259    pub fn to_vec(&self) -> Vec<u8> {
260        self.as_slice().to_vec()
261    }
262}
263
264// =============================================================================
265// Tests
266// =============================================================================
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271
272    #[test]
273    fn test_arena_creation() {
274        let arena = Arena::new(1024);
275        assert_eq!(arena.capacity(), 1024);
276        assert_eq!(arena.usage(), 0);
277        assert_eq!(arena.remaining(), 1024);
278    }
279
280    #[test]
281    fn test_arena_alloc_slice() {
282        let arena = Arena::new(1024);
283
284        let slice1 = arena.alloc_slice(100).unwrap();
285        assert_eq!(slice1.len(), 100);
286        assert_eq!(arena.usage(), 100);
287
288        let slice2 = arena.alloc_slice(200).unwrap();
289        assert_eq!(slice2.len(), 200);
290        assert_eq!(arena.usage(), 300);
291    }
292
293    #[test]
294    fn test_arena_alloc_slice_zeroed() {
295        let arena = Arena::new(1024);
296
297        // First allocate and write some data
298        let slice = arena.alloc_slice(100).unwrap();
299        slice.fill(0xFF);
300
301        arena.reset();
302
303        // Now allocate zeroed - should be zeroed
304        let zeroed = arena.alloc_slice_zeroed(100).unwrap();
305        for &byte in zeroed.iter() {
306            assert_eq!(byte, 0);
307        }
308    }
309
310    #[test]
311    fn test_arena_reset() {
312        let arena = Arena::new(1024);
313
314        let _ = arena.alloc_slice(500).unwrap();
315        assert_eq!(arena.usage(), 500);
316
317        arena.reset();
318        assert_eq!(arena.usage(), 0);
319        assert_eq!(arena.peak_usage(), 500);
320    }
321
322    #[test]
323    fn test_arena_overflow() {
324        let arena = Arena::new(100);
325
326        assert!(arena.alloc_slice(50).is_some());
327        assert!(arena.alloc_slice(60).is_none()); // Would overflow
328        assert_eq!(arena.usage(), 50); // Usage unchanged on failure
329    }
330
331    #[test]
332    fn test_arena_vec_basic() {
333        let arena = Arena::new(1024);
334
335        let mut vec = arena.alloc_vec(100).unwrap();
336        assert!(vec.is_empty());
337
338        vec.push(1);
339        vec.push(2);
340        vec.push(3);
341
342        assert_eq!(vec.len(), 3);
343        assert_eq!(vec.as_slice(), &[1, 2, 3]);
344    }
345
346    #[test]
347    fn test_arena_vec_extend() {
348        let arena = Arena::new(1024);
349
350        let mut vec = arena.alloc_vec(100).unwrap();
351        vec.extend_from_slice(b"Hello, ");
352        vec.extend_from_slice(b"World!");
353
354        assert_eq!(vec.as_slice(), b"Hello, World!");
355    }
356
357    #[test]
358    fn test_arena_vec_grow() {
359        let arena = Arena::new(1024);
360
361        let mut vec = arena.alloc_vec(10).unwrap();
362
363        // Fill past initial capacity
364        for i in 0..50u8 {
365            assert!(vec.push(i));
366        }
367
368        assert_eq!(vec.len(), 50);
369
370        // Verify contents
371        for (i, &b) in vec.as_slice().iter().enumerate() {
372            assert_eq!(b, i as u8);
373        }
374    }
375
376    #[test]
377    fn test_arena_vec_to_vec() {
378        let arena = Arena::new(1024);
379
380        let mut arena_vec = arena.alloc_vec(100).unwrap();
381        arena_vec.extend_from_slice(b"Test data");
382
383        let regular_vec = arena_vec.to_vec();
384        assert_eq!(regular_vec, b"Test data");
385    }
386
387    #[test]
388    fn test_arena_peak_usage() {
389        let arena = Arena::new(1024);
390
391        // Allocate some memory
392        let _ = arena.alloc_slice(400);
393        arena.reset();
394
395        // Allocate less
396        let _ = arena.alloc_slice(200);
397
398        // Peak should still be 400
399        assert_eq!(arena.peak_usage(), 400);
400    }
401
402    #[test]
403    fn test_default_arena_size() {
404        let arena = Arena::with_default_size();
405        assert_eq!(arena.capacity(), DEFAULT_ARENA_SIZE);
406    }
407}