Skip to main content

pjson_rs/parser/
aligned_alloc.rs

1//! Aligned memory allocation for SIMD buffer pools.
2//!
3//! Wraps `std::alloc::{alloc, dealloc, realloc}` with [`Layout`]-based
4//! alignment, suitable for AVX2 (32-byte), AVX-512 (64-byte), and NEON
5//! (16-byte) SIMD operations.
6//!
7//! All allocations route through the process-wide `#[global_allocator]`,
8//! so when the `mimalloc` feature is enabled this transparently uses mimalloc.
9
10use crate::domain::{DomainError, DomainResult};
11use std::{
12    alloc::{Layout, alloc, dealloc, realloc},
13    ptr::NonNull,
14};
15
16/// Aligned memory allocator for SIMD operations.
17///
18/// Zero-sized: holds no state. All calls delegate to the global allocator
19/// via [`std::alloc`], which routes through whatever `#[global_allocator]`
20/// is registered (mimalloc when the `mimalloc` feature is enabled, otherwise
21/// the system allocator).
22#[derive(Debug, Default, Clone, Copy)]
23pub struct AlignedAllocator;
24
25impl AlignedAllocator {
26    /// Construct a new allocator handle (zero cost).
27    pub const fn new() -> Self {
28        Self
29    }
30
31    /// Allocate `size` bytes aligned to `alignment` (must be a power of two).
32    ///
33    /// Returns a non-null pointer owned by the caller. The caller must
34    /// deallocate it via [`AlignedAllocator::dealloc_aligned`] with the same
35    /// `Layout`.
36    ///
37    /// # Safety
38    ///
39    /// The returned pointer is valid for `size` bytes. The caller is
40    /// responsible for ensuring the pointer is not used after deallocation.
41    pub unsafe fn alloc_aligned(&self, size: usize, alignment: usize) -> DomainResult<NonNull<u8>> {
42        if !alignment.is_power_of_two() {
43            return Err(DomainError::InvalidInput(format!(
44                "Alignment {} is not a power of 2",
45                alignment
46            )));
47        }
48
49        let layout = Layout::from_size_align(size, alignment)
50            .map_err(|e| DomainError::InvalidInput(format!("Invalid layout: {}", e)))?;
51
52        // SAFETY: layout is valid (size and alignment validated above).
53        let ptr = unsafe { alloc(layout) };
54        if ptr.is_null() {
55            return Err(DomainError::ResourceExhausted(format!(
56                "Failed to allocate {} bytes with alignment {}",
57                size, alignment
58            )));
59        }
60
61        // SAFETY: alloc returned non-null.
62        Ok(unsafe { NonNull::new_unchecked(ptr) })
63    }
64
65    /// Reallocate to `new_size`, preserving the original alignment.
66    ///
67    /// # Safety
68    ///
69    /// `ptr` must have been returned by [`AlignedAllocator::alloc_aligned`]
70    /// with `old_layout`. After this call, `ptr` is no longer valid —
71    /// use the returned pointer instead. `new_size` must be greater than
72    /// zero, and, rounded up to the nearest multiple of `old_layout`'s
73    /// alignment, must not overflow `isize::MAX` — these are preconditions
74    /// of [`std::alloc::realloc`] that this function does not itself
75    /// validate; callers must check them.
76    pub unsafe fn realloc_aligned(
77        &self,
78        ptr: NonNull<u8>,
79        old_layout: Layout,
80        new_size: usize,
81    ) -> DomainResult<NonNull<u8>> {
82        // SAFETY: caller upholds layout match.
83        let new_ptr = unsafe { realloc(ptr.as_ptr(), old_layout, new_size) };
84        if new_ptr.is_null() {
85            return Err(DomainError::ResourceExhausted(format!(
86                "Failed to reallocate to {} bytes",
87                new_size
88            )));
89        }
90
91        // SAFETY: realloc returned non-null.
92        Ok(unsafe { NonNull::new_unchecked(new_ptr) })
93    }
94
95    /// Deallocate memory previously returned by [`AlignedAllocator::alloc_aligned`].
96    ///
97    /// # Safety
98    ///
99    /// `ptr` must have been returned by [`AlignedAllocator::alloc_aligned`]
100    /// with exactly `layout`. Double-free or mismatched layout is undefined
101    /// behavior.
102    pub unsafe fn dealloc_aligned(&self, ptr: NonNull<u8>, layout: Layout) {
103        // SAFETY: caller upholds layout match.
104        unsafe { dealloc(ptr.as_ptr(), layout) };
105    }
106}
107
108static ALIGNED_ALLOCATOR: AlignedAllocator = AlignedAllocator::new();
109
110/// Returns the process-wide aligned allocator handle.
111///
112/// The returned reference is a zero-cost singleton — [`AlignedAllocator`] is
113/// zero-sized, so callers may also construct one inline with
114/// `AlignedAllocator::new()` at no extra cost.
115pub fn aligned_allocator() -> &'static AlignedAllocator {
116    &ALIGNED_ALLOCATOR
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_aligned_allocation() {
125        let allocator = AlignedAllocator::new();
126
127        unsafe {
128            for alignment in [16, 32, 64, 128, 256] {
129                let ptr = allocator.alloc_aligned(1024, alignment).unwrap();
130
131                assert_eq!(
132                    ptr.as_ptr() as usize % alignment,
133                    0,
134                    "Pointer not aligned to {} bytes",
135                    alignment
136                );
137
138                let layout = Layout::from_size_align(1024, alignment).unwrap();
139                allocator.dealloc_aligned(ptr, layout);
140            }
141        }
142    }
143
144    #[test]
145    fn test_reallocation() {
146        let allocator = AlignedAllocator::new();
147
148        unsafe {
149            let alignment = 64;
150            let initial_size = 1024;
151            let new_size = 2048;
152
153            let ptr = allocator.alloc_aligned(initial_size, alignment).unwrap();
154            let layout = Layout::from_size_align(initial_size, alignment).unwrap();
155
156            std::ptr::write_bytes(ptr.as_ptr(), 0xAB, initial_size);
157
158            let new_ptr = allocator.realloc_aligned(ptr, layout, new_size).unwrap();
159
160            assert_eq!(
161                new_ptr.as_ptr() as usize % alignment,
162                0,
163                "Reallocated pointer not aligned"
164            );
165
166            let first_byte = std::ptr::read(new_ptr.as_ptr());
167            assert_eq!(first_byte, 0xAB, "Data not preserved during reallocation");
168
169            let new_layout = Layout::from_size_align(new_size, alignment).unwrap();
170            allocator.dealloc_aligned(new_ptr, new_layout);
171        }
172    }
173
174    #[test]
175    fn test_invalid_alignment() {
176        let allocator = AlignedAllocator::new();
177        unsafe {
178            assert!(allocator.alloc_aligned(1024, 0).is_err());
179            assert!(allocator.alloc_aligned(1024, 3).is_err());
180            assert!(allocator.alloc_aligned(1024, 17).is_err());
181        }
182    }
183
184    #[test]
185    fn test_aligned_allocator_singleton() {
186        let a = aligned_allocator();
187        let b = aligned_allocator();
188        assert!(std::ptr::eq(a, b));
189    }
190}