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.
72    pub unsafe fn realloc_aligned(
73        &self,
74        ptr: NonNull<u8>,
75        old_layout: Layout,
76        new_size: usize,
77    ) -> DomainResult<NonNull<u8>> {
78        // SAFETY: caller upholds layout match.
79        let new_ptr = unsafe { realloc(ptr.as_ptr(), old_layout, new_size) };
80        if new_ptr.is_null() {
81            return Err(DomainError::ResourceExhausted(format!(
82                "Failed to reallocate to {} bytes",
83                new_size
84            )));
85        }
86
87        // SAFETY: realloc returned non-null.
88        Ok(unsafe { NonNull::new_unchecked(new_ptr) })
89    }
90
91    /// Deallocate memory previously returned by [`AlignedAllocator::alloc_aligned`].
92    ///
93    /// # Safety
94    ///
95    /// `ptr` must have been returned by [`AlignedAllocator::alloc_aligned`]
96    /// with exactly `layout`. Double-free or mismatched layout is undefined
97    /// behavior.
98    pub unsafe fn dealloc_aligned(&self, ptr: NonNull<u8>, layout: Layout) {
99        // SAFETY: caller upholds layout match.
100        unsafe { dealloc(ptr.as_ptr(), layout) };
101    }
102}
103
104static ALIGNED_ALLOCATOR: AlignedAllocator = AlignedAllocator::new();
105
106/// Returns the process-wide aligned allocator handle.
107///
108/// The returned reference is a zero-cost singleton — [`AlignedAllocator`] is
109/// zero-sized, so callers may also construct one inline with
110/// `AlignedAllocator::new()` at no extra cost.
111pub fn aligned_allocator() -> &'static AlignedAllocator {
112    &ALIGNED_ALLOCATOR
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn test_aligned_allocation() {
121        let allocator = AlignedAllocator::new();
122
123        unsafe {
124            for alignment in [16, 32, 64, 128, 256] {
125                let ptr = allocator.alloc_aligned(1024, alignment).unwrap();
126
127                assert_eq!(
128                    ptr.as_ptr() as usize % alignment,
129                    0,
130                    "Pointer not aligned to {} bytes",
131                    alignment
132                );
133
134                let layout = Layout::from_size_align(1024, alignment).unwrap();
135                allocator.dealloc_aligned(ptr, layout);
136            }
137        }
138    }
139
140    #[test]
141    fn test_reallocation() {
142        let allocator = AlignedAllocator::new();
143
144        unsafe {
145            let alignment = 64;
146            let initial_size = 1024;
147            let new_size = 2048;
148
149            let ptr = allocator.alloc_aligned(initial_size, alignment).unwrap();
150            let layout = Layout::from_size_align(initial_size, alignment).unwrap();
151
152            std::ptr::write_bytes(ptr.as_ptr(), 0xAB, initial_size);
153
154            let new_ptr = allocator.realloc_aligned(ptr, layout, new_size).unwrap();
155
156            assert_eq!(
157                new_ptr.as_ptr() as usize % alignment,
158                0,
159                "Reallocated pointer not aligned"
160            );
161
162            let first_byte = std::ptr::read(new_ptr.as_ptr());
163            assert_eq!(first_byte, 0xAB, "Data not preserved during reallocation");
164
165            let new_layout = Layout::from_size_align(new_size, alignment).unwrap();
166            allocator.dealloc_aligned(new_ptr, new_layout);
167        }
168    }
169
170    #[test]
171    fn test_invalid_alignment() {
172        let allocator = AlignedAllocator::new();
173        unsafe {
174            assert!(allocator.alloc_aligned(1024, 0).is_err());
175            assert!(allocator.alloc_aligned(1024, 3).is_err());
176            assert!(allocator.alloc_aligned(1024, 17).is_err());
177        }
178    }
179
180    #[test]
181    fn test_aligned_allocator_singleton() {
182        let a = aligned_allocator();
183        let b = aligned_allocator();
184        assert!(std::ptr::eq(a, b));
185    }
186}