vibesql_executor/memory/
arena.rs

1//! Arena allocator for query-scoped memory allocation
2//!
3//! This module provides a bump-pointer arena allocator that eliminates
4//! malloc/free overhead by allocating from a pre-allocated buffer.
5//!
6//! # Performance
7//!
8//! - Allocation: ~1ns (vs ~100ns for malloc)
9//! - Deallocation: 0ns (arena dropped at end)
10//! - Cache locality: sequential allocations in contiguous memory
11//! - No fragmentation: single buffer reused per query
12//!
13//! # Safety
14//!
15//! All allocations are tied to the arena's lifetime via Rust's borrow checker.
16//! References returned cannot outlive the arena.
17
18use std::cell::{Cell, UnsafeCell};
19use std::marker::PhantomData;
20use std::mem::{self, MaybeUninit};
21use std::ptr;
22
23/// Arena allocator for query-scoped allocations
24///
25/// Uses a bump-pointer strategy to allocate from a pre-allocated buffer.
26/// All allocations are freed when the arena is reset or dropped.
27///
28/// # Example
29///
30/// ```rust
31/// use vibesql_executor::memory::QueryArena;
32/// use std::mem::MaybeUninit;
33///
34/// let arena = QueryArena::with_capacity(1024);
35///
36/// // Allocate a value
37/// let x = arena.alloc(42i64);
38/// assert_eq!(*x, 42);
39///
40/// // Allocate a slice (returns uninitialized memory)
41/// let slice = arena.alloc_slice::<i32>(10);
42/// for i in 0..10 {
43///     slice[i] = MaybeUninit::new(i as i32);
44/// }
45/// ```
46pub struct QueryArena {
47    /// Pre-allocated buffer for all allocations
48    buffer: UnsafeCell<Vec<u8>>,
49    /// Current offset into the buffer (bump pointer)
50    offset: Cell<usize>,
51    /// Phantom data to ensure proper variance
52    _marker: PhantomData<Cell<u8>>,
53}
54
55impl QueryArena {
56    /// Default arena size: 10MB
57    ///
58    /// Based on TPC-H Q6 analysis:
59    /// - 60K rows × 16 columns = 960K SqlValues
60    /// - Intermediate results: ~2MB
61    /// - Expression temporaries: ~1MB
62    /// - Safety margin: 7MB
63    pub const DEFAULT_CAPACITY: usize = 10 * 1024 * 1024; // 10MB
64
65    /// Create a new arena with the specified capacity in bytes
66    ///
67    /// # Arguments
68    ///
69    /// * `bytes` - Number of bytes to pre-allocate
70    ///
71    /// # Example
72    ///
73    /// ```rust
74    /// use vibesql_executor::memory::QueryArena;
75    ///
76    /// // 1MB arena
77    /// let arena = QueryArena::with_capacity(1024 * 1024);
78    /// ```
79    pub fn with_capacity(bytes: usize) -> Self {
80        Self {
81            buffer: UnsafeCell::new(vec![0u8; bytes]),
82            offset: Cell::new(0),
83            _marker: PhantomData,
84        }
85    }
86
87    /// Create a new arena with the default capacity (10MB)
88    ///
89    /// # Example
90    ///
91    /// ```rust
92    /// use vibesql_executor::memory::QueryArena;
93    ///
94    /// let arena = QueryArena::new();
95    /// ```
96    pub fn new() -> Self {
97        Self::with_capacity(Self::DEFAULT_CAPACITY)
98    }
99
100    /// Allocate a value in the arena
101    ///
102    /// Returns a reference to the allocated value with lifetime tied to the arena.
103    ///
104    /// # Performance
105    ///
106    /// Allocation overhead: ~1ns (vs ~100ns for heap allocation)
107    ///
108    /// # Panics
109    ///
110    /// Panics if the arena is out of space. Use `try_alloc` for fallible allocation.
111    ///
112    /// # Example
113    ///
114    /// ```rust
115    /// use vibesql_executor::memory::QueryArena;
116    ///
117    /// let arena = QueryArena::new();
118    /// let x = arena.alloc(42i64);
119    /// let y = arena.alloc(3.5f64);
120    ///
121    /// assert_eq!(*x, 42);
122    /// assert_eq!(*y, 3.5);
123    /// ```
124    #[inline(always)]
125    pub fn alloc<T>(&self, value: T) -> &T {
126        let offset = self.offset.get();
127        let size = mem::size_of::<T>();
128        let align = mem::align_of::<T>();
129
130        // Align pointer to T's alignment requirement
131        let aligned_offset = (offset + align - 1) & !(align - 1);
132
133        // Check capacity
134        let end_offset = aligned_offset.checked_add(size).expect("arena offset overflow");
135
136        // SAFETY: We're accessing the buffer to check its length
137        let buffer = unsafe { &*self.buffer.get() };
138        assert!(
139            end_offset <= buffer.len(),
140            "arena overflow: need {} bytes, have {} remaining",
141            size,
142            buffer.len() - aligned_offset
143        );
144
145        // Bump pointer
146        self.offset.set(end_offset);
147
148        // Write value and return reference
149        // SAFETY: We've verified:
150        // - Buffer has enough space (checked above)
151        // - Pointer is properly aligned (aligned_offset)
152        // - Lifetime is tied to arena via borrow checker
153        // - UnsafeCell allows interior mutability
154        unsafe {
155            let ptr = buffer.as_ptr().add(aligned_offset) as *mut T;
156            ptr::write(ptr, value);
157            &*ptr
158        }
159    }
160
161    /// Allocate a slice in the arena
162    ///
163    /// Returns a mutable slice of `MaybeUninit<T>` with the specified length.
164    /// Caller must initialize elements before assuming them to be initialized.
165    ///
166    /// Using `MaybeUninit` ensures memory safety - uninitialized memory cannot
167    /// be accidentally read or dropped, preventing undefined behavior.
168    ///
169    /// # Performance
170    ///
171    /// Allocation overhead: ~1ns + negligible bounds check
172    ///
173    /// # Panics
174    ///
175    /// Panics if the arena is out of space.
176    ///
177    /// # Example
178    ///
179    /// ```rust
180    /// use vibesql_executor::memory::QueryArena;
181    /// use std::mem::MaybeUninit;
182    ///
183    /// let arena = QueryArena::new();
184    /// let slice = arena.alloc_slice::<i32>(100);
185    ///
186    /// // Initialize the slice
187    /// for i in 0..100 {
188    ///     slice[i] = MaybeUninit::new(i as i32);
189    /// }
190    ///
191    /// // SAFETY: All elements have been initialized above
192    /// let initialized_slice = unsafe {
193    ///     std::slice::from_raw_parts(
194    ///         slice.as_ptr() as *const i32,
195    ///         slice.len()
196    ///     )
197    /// };
198    /// assert_eq!(initialized_slice[50], 50);
199    /// ```
200    #[inline(always)]
201    #[allow(clippy::mut_from_ref)]
202    pub fn alloc_slice<T>(&self, len: usize) -> &mut [MaybeUninit<T>] {
203        if len == 0 {
204            // Return empty slice without allocating
205            return &mut [];
206        }
207
208        let offset = self.offset.get();
209        let size = mem::size_of::<T>().checked_mul(len).expect("slice size overflow");
210        let align = mem::align_of::<T>();
211
212        // Align pointer to T's alignment requirement
213        let aligned_offset = (offset + align - 1) & !(align - 1);
214
215        // Check capacity
216        let end_offset = aligned_offset.checked_add(size).expect("arena offset overflow");
217
218        // SAFETY: We're accessing the buffer to check its length
219        let buffer = unsafe { &*self.buffer.get() };
220        assert!(
221            end_offset <= buffer.len(),
222            "arena overflow: need {} bytes, have {} remaining",
223            size,
224            buffer.len() - aligned_offset
225        );
226
227        // Bump pointer
228        self.offset.set(end_offset);
229
230        // Return slice of MaybeUninit<T> (safe for uninitialized memory)
231        // SAFETY: We've verified:
232        // - Buffer has enough space (checked above)
233        // - Pointer is properly aligned (aligned_offset)
234        // - Size doesn't overflow (checked above)
235        // - Lifetime is tied to arena via borrow checker
236        // - MaybeUninit<T> is safe to construct from uninitialized memory
237        // - UnsafeCell allows interior mutability
238        unsafe {
239            let ptr = buffer.as_ptr().add(aligned_offset) as *mut MaybeUninit<T>;
240            std::slice::from_raw_parts_mut(ptr, len)
241        }
242    }
243
244    /// Try to allocate a value in the arena
245    ///
246    /// Returns None if the arena is out of space.
247    ///
248    /// # Example
249    ///
250    /// ```rust
251    /// use vibesql_executor::memory::QueryArena;
252    ///
253    /// let arena = QueryArena::with_capacity(64);
254    ///
255    /// // This succeeds
256    /// assert!(arena.try_alloc(42i64).is_some());
257    ///
258    /// // Eventually this fails (arena full)
259    /// let mut count = 0;
260    /// while arena.try_alloc(count).is_some() {
261    ///     count += 1;
262    /// }
263    /// ```
264    #[inline]
265    pub fn try_alloc<T>(&self, value: T) -> Option<&T> {
266        let offset = self.offset.get();
267        let size = mem::size_of::<T>();
268        let align = mem::align_of::<T>();
269
270        let aligned_offset = (offset + align - 1) & !(align - 1);
271        let end_offset = aligned_offset.checked_add(size)?;
272
273        // SAFETY: We're accessing the buffer to check its length
274        let buffer = unsafe { &*self.buffer.get() };
275
276        if end_offset > buffer.len() {
277            return None;
278        }
279
280        self.offset.set(end_offset);
281
282        // SAFETY: Same guarantees as alloc(), but checked for space
283        // - UnsafeCell allows interior mutability
284        unsafe {
285            let ptr = buffer.as_ptr().add(aligned_offset) as *mut T;
286            ptr::write(ptr, value);
287            Some(&*ptr)
288        }
289    }
290
291    /// Reset the arena, allowing it to be reused
292    ///
293    /// This doesn't deallocate the buffer, just resets the bump pointer.
294    /// All previously allocated values are invalidated.
295    ///
296    /// # Example
297    ///
298    /// ```rust
299    /// use vibesql_executor::memory::QueryArena;
300    ///
301    /// let mut arena = QueryArena::new();
302    ///
303    /// // Allocate some values (i64 = 8 bytes)
304    /// let x = arena.alloc(42i64);
305    /// assert_eq!(arena.used_bytes(), 8);
306    ///
307    /// // Reset and reuse
308    /// arena.reset();
309    /// assert_eq!(arena.used_bytes(), 0);
310    ///
311    /// // Can allocate again
312    /// let y = arena.alloc(100i64);
313    /// ```
314    pub fn reset(&mut self) {
315        self.offset.set(0);
316        // Note: We don't need to clear the buffer since we'll overwrite it
317    }
318
319    /// Get the number of bytes used in this arena
320    ///
321    /// # Example
322    ///
323    /// ```rust
324    /// use vibesql_executor::memory::QueryArena;
325    ///
326    /// let arena = QueryArena::new();
327    ///
328    /// arena.alloc(42i64); // 8 bytes
329    /// arena.alloc(3.5f64); // 8 bytes
330    ///
331    /// assert_eq!(arena.used_bytes(), 16);
332    /// ```
333    #[inline]
334    pub fn used_bytes(&self) -> usize {
335        self.offset.get()
336    }
337
338    /// Get the total capacity of this arena in bytes
339    ///
340    /// # Example
341    ///
342    /// ```rust
343    /// use vibesql_executor::memory::QueryArena;
344    ///
345    /// let arena = QueryArena::with_capacity(1024);
346    /// assert_eq!(arena.capacity_bytes(), 1024);
347    /// ```
348    #[inline]
349    pub fn capacity_bytes(&self) -> usize {
350        // SAFETY: We're only reading the length, which is safe
351        unsafe { (*self.buffer.get()).len() }
352    }
353
354    /// Get the number of bytes remaining in this arena
355    ///
356    /// # Example
357    ///
358    /// ```rust
359    /// use vibesql_executor::memory::QueryArena;
360    ///
361    /// let arena = QueryArena::with_capacity(1024);
362    /// arena.alloc(42i64); // 8 bytes
363    ///
364    /// assert_eq!(arena.remaining_bytes(), 1024 - 8);
365    /// ```
366    #[inline]
367    pub fn remaining_bytes(&self) -> usize {
368        // SAFETY: We're only reading the length, which is safe
369        unsafe { (*self.buffer.get()).len() - self.offset.get() }
370    }
371}
372
373impl Default for QueryArena {
374    fn default() -> Self {
375        Self::new()
376    }
377}
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382
383    #[test]
384    fn test_arena_basic_allocation() {
385        let arena = QueryArena::with_capacity(1024);
386
387        let x = arena.alloc(42i64);
388        let y = arena.alloc(3.5f64);
389
390        assert_eq!(*x, 42);
391        assert_eq!(*y, 3.5);
392    }
393
394    #[test]
395    fn test_arena_slice_allocation() {
396        let arena = QueryArena::with_capacity(4096);
397
398        let slice = arena.alloc_slice::<i32>(100);
399
400        // Initialize the slice with MaybeUninit
401        for (i, elem) in slice.iter_mut().enumerate() {
402            *elem = MaybeUninit::new(i as i32);
403        }
404
405        // SAFETY: All elements have been initialized above
406        let initialized_slice =
407            unsafe { std::slice::from_raw_parts(slice.as_ptr() as *const i32, slice.len()) };
408
409        assert_eq!(initialized_slice[50], 50);
410        assert_eq!(slice.len(), 100);
411    }
412
413    #[test]
414    fn test_arena_empty_slice() {
415        let arena = QueryArena::new();
416        let slice: &mut [MaybeUninit<i32>] = arena.alloc_slice::<i32>(0);
417        assert_eq!(slice.len(), 0);
418    }
419
420    #[test]
421    fn test_arena_reset() {
422        let mut arena = QueryArena::with_capacity(1024);
423
424        let x = arena.alloc(42i64);
425        assert_eq!(*x, 42);
426        assert_eq!(arena.used_bytes(), 8);
427
428        arena.reset();
429        assert_eq!(arena.used_bytes(), 0);
430
431        // Can allocate again after reset
432        let y = arena.alloc(100i64);
433        assert_eq!(*y, 100);
434    }
435
436    #[test]
437    fn test_arena_alignment() {
438        let arena = QueryArena::with_capacity(1024);
439
440        // Allocate misaligned type first
441        let _byte = arena.alloc(1u8);
442
443        // This should be properly aligned despite previous allocation
444        let x = arena.alloc(42i64);
445        assert_eq!(*x, 42);
446
447        // Verify alignment
448        let ptr = x as *const i64 as usize;
449        assert_eq!(ptr % std::mem::align_of::<i64>(), 0);
450    }
451
452    #[test]
453    #[should_panic(expected = "arena overflow")]
454    fn test_arena_overflow() {
455        let arena = QueryArena::with_capacity(64);
456
457        // Allocate more than capacity
458        for i in 0..1000 {
459            arena.alloc(i);
460        }
461    }
462
463    #[test]
464    fn test_try_alloc_success() {
465        let arena = QueryArena::with_capacity(1024);
466
467        let x = arena.try_alloc(42i64);
468        assert!(x.is_some());
469        assert_eq!(*x.unwrap(), 42);
470    }
471
472    #[test]
473    fn test_try_alloc_failure() {
474        let arena = QueryArena::with_capacity(8);
475
476        // First allocation succeeds
477        assert!(arena.try_alloc(42i64).is_some());
478
479        // Second allocation fails (not enough space)
480        assert!(arena.try_alloc(100i64).is_none());
481    }
482
483    #[test]
484    fn test_arena_used_bytes() {
485        let arena = QueryArena::new();
486
487        assert_eq!(arena.used_bytes(), 0);
488
489        arena.alloc(42i64); // 8 bytes
490        assert_eq!(arena.used_bytes(), 8);
491
492        arena.alloc(3.5f64); // 8 bytes
493        assert_eq!(arena.used_bytes(), 16);
494    }
495
496    #[test]
497    fn test_arena_capacity() {
498        let arena = QueryArena::with_capacity(2048);
499        assert_eq!(arena.capacity_bytes(), 2048);
500        assert_eq!(arena.remaining_bytes(), 2048);
501
502        arena.alloc(42i64);
503        assert_eq!(arena.remaining_bytes(), 2048 - 8);
504    }
505
506    #[test]
507    fn test_default_arena() {
508        let arena = QueryArena::default();
509        assert_eq!(arena.capacity_bytes(), QueryArena::DEFAULT_CAPACITY);
510    }
511
512    #[test]
513    fn test_multiple_types() {
514        let arena = QueryArena::new();
515
516        let a = arena.alloc(1u8);
517        let b = arena.alloc(2u16);
518        let c = arena.alloc(3u32);
519        let d = arena.alloc(4u64);
520        let e = arena.alloc(5.0f32);
521        let f = arena.alloc(6.0f64);
522
523        assert_eq!(*a, 1);
524        assert_eq!(*b, 2);
525        assert_eq!(*c, 3);
526        assert_eq!(*d, 4);
527        assert_eq!(*e, 5.0);
528        assert_eq!(*f, 6.0);
529    }
530}