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
135            .checked_add(size)
136            .expect("arena offset overflow");
137
138        // SAFETY: We're accessing the buffer to check its length
139        let buffer = unsafe { &*self.buffer.get() };
140        assert!(
141            end_offset <= buffer.len(),
142            "arena overflow: need {} bytes, have {} remaining",
143            size,
144            buffer.len() - aligned_offset
145        );
146
147        // Bump pointer
148        self.offset.set(end_offset);
149
150        // Write value and return reference
151        // SAFETY: We've verified:
152        // - Buffer has enough space (checked above)
153        // - Pointer is properly aligned (aligned_offset)
154        // - Lifetime is tied to arena via borrow checker
155        // - UnsafeCell allows interior mutability
156        unsafe {
157            let ptr = buffer.as_ptr().add(aligned_offset) as *mut T;
158            ptr::write(ptr, value);
159            &*ptr
160        }
161    }
162
163    /// Allocate a slice in the arena
164    ///
165    /// Returns a mutable slice of `MaybeUninit<T>` with the specified length.
166    /// Caller must initialize elements before assuming them to be initialized.
167    ///
168    /// Using `MaybeUninit` ensures memory safety - uninitialized memory cannot
169    /// be accidentally read or dropped, preventing undefined behavior.
170    ///
171    /// # Performance
172    ///
173    /// Allocation overhead: ~1ns + negligible bounds check
174    ///
175    /// # Panics
176    ///
177    /// Panics if the arena is out of space.
178    ///
179    /// # Example
180    ///
181    /// ```rust
182    /// use vibesql_executor::memory::QueryArena;
183    /// use std::mem::MaybeUninit;
184    ///
185    /// let arena = QueryArena::new();
186    /// let slice = arena.alloc_slice::<i32>(100);
187    ///
188    /// // Initialize the slice
189    /// for i in 0..100 {
190    ///     slice[i] = MaybeUninit::new(i as i32);
191    /// }
192    ///
193    /// // SAFETY: All elements have been initialized above
194    /// let initialized_slice = unsafe {
195    ///     std::slice::from_raw_parts(
196    ///         slice.as_ptr() as *const i32,
197    ///         slice.len()
198    ///     )
199    /// };
200    /// assert_eq!(initialized_slice[50], 50);
201    /// ```
202    #[inline(always)]
203    #[allow(clippy::mut_from_ref)]
204    pub fn alloc_slice<T>(&self, len: usize) -> &mut [MaybeUninit<T>] {
205        if len == 0 {
206            // Return empty slice without allocating
207            return &mut [];
208        }
209
210        let offset = self.offset.get();
211        let size = mem::size_of::<T>()
212            .checked_mul(len)
213            .expect("slice size overflow");
214        let align = mem::align_of::<T>();
215
216        // Align pointer to T's alignment requirement
217        let aligned_offset = (offset + align - 1) & !(align - 1);
218
219        // Check capacity
220        let end_offset = aligned_offset
221            .checked_add(size)
222            .expect("arena offset overflow");
223
224        // SAFETY: We're accessing the buffer to check its length
225        let buffer = unsafe { &*self.buffer.get() };
226        assert!(
227            end_offset <= buffer.len(),
228            "arena overflow: need {} bytes, have {} remaining",
229            size,
230            buffer.len() - aligned_offset
231        );
232
233        // Bump pointer
234        self.offset.set(end_offset);
235
236        // Return slice of MaybeUninit<T> (safe for uninitialized memory)
237        // SAFETY: We've verified:
238        // - Buffer has enough space (checked above)
239        // - Pointer is properly aligned (aligned_offset)
240        // - Size doesn't overflow (checked above)
241        // - Lifetime is tied to arena via borrow checker
242        // - MaybeUninit<T> is safe to construct from uninitialized memory
243        // - UnsafeCell allows interior mutability
244        unsafe {
245            let ptr = buffer.as_ptr().add(aligned_offset) as *mut MaybeUninit<T>;
246            std::slice::from_raw_parts_mut(ptr, len)
247        }
248    }
249
250    /// Try to allocate a value in the arena
251    ///
252    /// Returns None if the arena is out of space.
253    ///
254    /// # Example
255    ///
256    /// ```rust
257    /// use vibesql_executor::memory::QueryArena;
258    ///
259    /// let arena = QueryArena::with_capacity(64);
260    ///
261    /// // This succeeds
262    /// assert!(arena.try_alloc(42i64).is_some());
263    ///
264    /// // Eventually this fails (arena full)
265    /// let mut count = 0;
266    /// while arena.try_alloc(count).is_some() {
267    ///     count += 1;
268    /// }
269    /// ```
270    #[inline]
271    pub fn try_alloc<T>(&self, value: T) -> Option<&T> {
272        let offset = self.offset.get();
273        let size = mem::size_of::<T>();
274        let align = mem::align_of::<T>();
275
276        let aligned_offset = (offset + align - 1) & !(align - 1);
277        let end_offset = aligned_offset.checked_add(size)?;
278
279        // SAFETY: We're accessing the buffer to check its length
280        let buffer = unsafe { &*self.buffer.get() };
281
282        if end_offset > buffer.len() {
283            return None;
284        }
285
286        self.offset.set(end_offset);
287
288        // SAFETY: Same guarantees as alloc(), but checked for space
289        // - UnsafeCell allows interior mutability
290        unsafe {
291            let ptr = buffer.as_ptr().add(aligned_offset) as *mut T;
292            ptr::write(ptr, value);
293            Some(&*ptr)
294        }
295    }
296
297    /// Reset the arena, allowing it to be reused
298    ///
299    /// This doesn't deallocate the buffer, just resets the bump pointer.
300    /// All previously allocated values are invalidated.
301    ///
302    /// # Example
303    ///
304    /// ```rust
305    /// use vibesql_executor::memory::QueryArena;
306    ///
307    /// let mut arena = QueryArena::new();
308    ///
309    /// // Allocate some values (i64 = 8 bytes)
310    /// let x = arena.alloc(42i64);
311    /// assert_eq!(arena.used_bytes(), 8);
312    ///
313    /// // Reset and reuse
314    /// arena.reset();
315    /// assert_eq!(arena.used_bytes(), 0);
316    ///
317    /// // Can allocate again
318    /// let y = arena.alloc(100i64);
319    /// ```
320    pub fn reset(&mut self) {
321        self.offset.set(0);
322        // Note: We don't need to clear the buffer since we'll overwrite it
323    }
324
325    /// Get the number of bytes used in this arena
326    ///
327    /// # Example
328    ///
329    /// ```rust
330    /// use vibesql_executor::memory::QueryArena;
331    ///
332    /// let arena = QueryArena::new();
333    ///
334    /// arena.alloc(42i64); // 8 bytes
335    /// arena.alloc(3.5f64); // 8 bytes
336    ///
337    /// assert_eq!(arena.used_bytes(), 16);
338    /// ```
339    #[inline]
340    pub fn used_bytes(&self) -> usize {
341        self.offset.get()
342    }
343
344    /// Get the total capacity of this arena in bytes
345    ///
346    /// # Example
347    ///
348    /// ```rust
349    /// use vibesql_executor::memory::QueryArena;
350    ///
351    /// let arena = QueryArena::with_capacity(1024);
352    /// assert_eq!(arena.capacity_bytes(), 1024);
353    /// ```
354    #[inline]
355    pub fn capacity_bytes(&self) -> usize {
356        // SAFETY: We're only reading the length, which is safe
357        unsafe { (*self.buffer.get()).len() }
358    }
359
360    /// Get the number of bytes remaining in this arena
361    ///
362    /// # Example
363    ///
364    /// ```rust
365    /// use vibesql_executor::memory::QueryArena;
366    ///
367    /// let arena = QueryArena::with_capacity(1024);
368    /// arena.alloc(42i64); // 8 bytes
369    ///
370    /// assert_eq!(arena.remaining_bytes(), 1024 - 8);
371    /// ```
372    #[inline]
373    pub fn remaining_bytes(&self) -> usize {
374        // SAFETY: We're only reading the length, which is safe
375        unsafe { (*self.buffer.get()).len() - self.offset.get() }
376    }
377}
378
379impl Default for QueryArena {
380    fn default() -> Self {
381        Self::new()
382    }
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388
389    #[test]
390    fn test_arena_basic_allocation() {
391        let arena = QueryArena::with_capacity(1024);
392
393        let x = arena.alloc(42i64);
394        let y = arena.alloc(3.5f64);
395
396        assert_eq!(*x, 42);
397        assert_eq!(*y, 3.5);
398    }
399
400    #[test]
401    fn test_arena_slice_allocation() {
402        let arena = QueryArena::with_capacity(4096);
403
404        let slice = arena.alloc_slice::<i32>(100);
405
406        // Initialize the slice with MaybeUninit
407        for (i, elem) in slice.iter_mut().enumerate() {
408            *elem = MaybeUninit::new(i as i32);
409        }
410
411        // SAFETY: All elements have been initialized above
412        let initialized_slice = unsafe {
413            std::slice::from_raw_parts(
414                slice.as_ptr() as *const i32,
415                slice.len()
416            )
417        };
418
419        assert_eq!(initialized_slice[50], 50);
420        assert_eq!(slice.len(), 100);
421    }
422
423    #[test]
424    fn test_arena_empty_slice() {
425        let arena = QueryArena::new();
426        let slice: &mut [MaybeUninit<i32>] = arena.alloc_slice::<i32>(0);
427        assert_eq!(slice.len(), 0);
428    }
429
430    #[test]
431    fn test_arena_reset() {
432        let mut arena = QueryArena::with_capacity(1024);
433
434        let x = arena.alloc(42i64);
435        assert_eq!(*x, 42);
436        assert_eq!(arena.used_bytes(), 8);
437
438        arena.reset();
439        assert_eq!(arena.used_bytes(), 0);
440
441        // Can allocate again after reset
442        let y = arena.alloc(100i64);
443        assert_eq!(*y, 100);
444    }
445
446    #[test]
447    fn test_arena_alignment() {
448        let arena = QueryArena::with_capacity(1024);
449
450        // Allocate misaligned type first
451        let _byte = arena.alloc(1u8);
452
453        // This should be properly aligned despite previous allocation
454        let x = arena.alloc(42i64);
455        assert_eq!(*x, 42);
456
457        // Verify alignment
458        let ptr = x as *const i64 as usize;
459        assert_eq!(ptr % std::mem::align_of::<i64>(), 0);
460    }
461
462    #[test]
463    #[should_panic(expected = "arena overflow")]
464    fn test_arena_overflow() {
465        let arena = QueryArena::with_capacity(64);
466
467        // Allocate more than capacity
468        for i in 0..1000 {
469            arena.alloc(i);
470        }
471    }
472
473    #[test]
474    fn test_try_alloc_success() {
475        let arena = QueryArena::with_capacity(1024);
476
477        let x = arena.try_alloc(42i64);
478        assert!(x.is_some());
479        assert_eq!(*x.unwrap(), 42);
480    }
481
482    #[test]
483    fn test_try_alloc_failure() {
484        let arena = QueryArena::with_capacity(8);
485
486        // First allocation succeeds
487        assert!(arena.try_alloc(42i64).is_some());
488
489        // Second allocation fails (not enough space)
490        assert!(arena.try_alloc(100i64).is_none());
491    }
492
493    #[test]
494    fn test_arena_used_bytes() {
495        let arena = QueryArena::new();
496
497        assert_eq!(arena.used_bytes(), 0);
498
499        arena.alloc(42i64); // 8 bytes
500        assert_eq!(arena.used_bytes(), 8);
501
502        arena.alloc(3.5f64); // 8 bytes
503        assert_eq!(arena.used_bytes(), 16);
504    }
505
506    #[test]
507    fn test_arena_capacity() {
508        let arena = QueryArena::with_capacity(2048);
509        assert_eq!(arena.capacity_bytes(), 2048);
510        assert_eq!(arena.remaining_bytes(), 2048);
511
512        arena.alloc(42i64);
513        assert_eq!(arena.remaining_bytes(), 2048 - 8);
514    }
515
516    #[test]
517    fn test_default_arena() {
518        let arena = QueryArena::default();
519        assert_eq!(arena.capacity_bytes(), QueryArena::DEFAULT_CAPACITY);
520    }
521
522    #[test]
523    fn test_multiple_types() {
524        let arena = QueryArena::new();
525
526        let a = arena.alloc(1u8);
527        let b = arena.alloc(2u16);
528        let c = arena.alloc(3u32);
529        let d = arena.alloc(4u64);
530        let e = arena.alloc(5.0f32);
531        let f = arena.alloc(6.0f64);
532
533        assert_eq!(*a, 1);
534        assert_eq!(*b, 2);
535        assert_eq!(*c, 3);
536        assert_eq!(*d, 4);
537        assert_eq!(*e, 5.0);
538        assert_eq!(*f, 6.0);
539    }
540}