oxiblas_core/memory/aligned_vec.rs
1//! Memory management utilities for OxiBLAS.
2//!
3//! This module provides:
4//! - Aligned memory allocation
5//! - Stack-based temporary allocation (StackReq pattern)
6//! - Cache-aware data layout utilities
7//! - Prefetch hints for cache optimization
8//! - Memory pool for temporary allocations
9//! - Custom allocator support via the `Alloc` trait
10
11use core::alloc::Layout;
12use core::mem::{align_of, size_of};
13use core::ptr::NonNull;
14
15#[cfg(not(feature = "std"))]
16use alloc::alloc::handle_alloc_error;
17#[cfg(feature = "std")]
18use std::alloc::handle_alloc_error;
19
20use super::alloc::*;
21
22// =============================================================================
23// AlignedVec - Aligned heap allocation
24// =============================================================================
25
26/// Compile-time check that an alignment const generic is a power of two, as
27/// required by [`core::alloc::Layout::from_size_align`].
28///
29/// Call sites wrap the call in an inline `const { .. }` block (stable since
30/// Rust 1.79) so the assertion is evaluated at monomorphization time: if
31/// `ALIGN` is not a power of two, compilation fails with a clear message
32/// instead of `AlignedVec` producing an invalid `Layout` (or silently
33/// rounding up) the first time it is used.
34const fn assert_align_is_power_of_two<const ALIGN: usize>() {
35 assert!(
36 ALIGN.is_power_of_two(),
37 "AlignedVec: ALIGN const generic parameter must be a power of two"
38 );
39}
40
41/// A vector with guaranteed alignment and custom allocator support.
42///
43/// Unlike `Vec<T>`, this type ensures the underlying buffer is aligned
44/// to at least `ALIGN` bytes, which is required for efficient SIMD operations.
45///
46/// # Type Parameters
47///
48/// - `T`: The element type
49/// - `ALIGN`: The minimum alignment in bytes (default: 64 for cache line alignment)
50/// - `A`: The allocator type (default: `Global`)
51///
52/// # Custom Allocators
53///
54/// You can use a custom allocator by specifying the third type parameter.
55/// Any type implementing the [`Alloc`] trait works; here `MyAlloc` simply
56/// forwards to [`Global`] to keep the example self-contained:
57///
58/// ```
59/// use core::alloc::Layout;
60/// use oxiblas_core::memory::{AlignedVec, Alloc, Global};
61///
62/// // Use global allocator (default)
63/// let vec: AlignedVec<f64> = AlignedVec::zeros(100);
64/// assert_eq!(vec.len(), 100);
65///
66/// // A custom allocator only needs to implement `Alloc`.
67/// #[derive(Clone)]
68/// struct MyAlloc(Global);
69///
70/// // SAFETY: delegates every call unchanged to `Global`, which upholds the
71/// // `Alloc` trait's safety contract.
72/// unsafe impl Alloc for MyAlloc {
73/// fn allocate(&self, layout: Layout) -> *mut u8 {
74/// self.0.allocate(layout)
75/// }
76/// fn allocate_zeroed(&self, layout: Layout) -> *mut u8 {
77/// self.0.allocate_zeroed(layout)
78/// }
79/// unsafe fn deallocate(&self, ptr: *mut u8, layout: Layout) {
80/// unsafe { self.0.deallocate(ptr, layout) }
81/// }
82/// }
83///
84/// let custom_vec: AlignedVec<f64, 64, MyAlloc> =
85/// AlignedVec::zeros_in(100, MyAlloc(Global));
86/// assert_eq!(custom_vec.len(), 100);
87/// ```
88pub struct AlignedVec<T, const ALIGN: usize = DEFAULT_ALIGN, A: Alloc = Global> {
89 ptr: NonNull<T>,
90 len: usize,
91 cap: usize,
92 alloc: A,
93}
94
95// Convenience methods using Global allocator
96impl<T, const ALIGN: usize> AlignedVec<T, ALIGN, Global> {
97 /// Creates a new empty aligned vector.
98 #[inline]
99 pub const fn new() -> Self {
100 // Compile-time proof that `ALIGN` is a power of two, as required by
101 // `core::alloc::Layout`. Evaluated at monomorphization time, so an
102 // invalid `ALIGN` fails to compile rather than panicking (or worse,
103 // producing a malformed `Layout`) the first time an instance of this
104 // type is actually allocated.
105 const { assert_align_is_power_of_two::<ALIGN>() };
106
107 AlignedVec {
108 ptr: NonNull::dangling(),
109 len: 0,
110 cap: 0,
111 alloc: Global,
112 }
113 }
114
115 /// Creates a new aligned vector with the given capacity.
116 pub fn with_capacity(capacity: usize) -> Self {
117 Self::with_capacity_in(capacity, Global)
118 }
119
120 /// Creates a new aligned vector filled with zeros.
121 ///
122 /// This is more efficient than creating and then filling, as it uses
123 /// zeroed allocation.
124 pub fn zeros(len: usize) -> Self
125 where
126 T: bytemuck::Zeroable,
127 {
128 Self::zeros_in(len, Global)
129 }
130
131 /// Creates a new aligned vector filled with a value.
132 pub fn filled(len: usize, value: T) -> Self
133 where
134 T: Clone,
135 {
136 Self::filled_in(len, value, Global)
137 }
138
139 /// Creates a new aligned vector from a slice.
140 pub fn from_slice(slice: &[T]) -> Self
141 where
142 T: Clone,
143 {
144 Self::from_slice_in(slice, Global)
145 }
146}
147
148// Methods that work with any allocator
149impl<T, const ALIGN: usize, A: Alloc> AlignedVec<T, ALIGN, A> {
150 /// Creates a new empty aligned vector with the specified allocator.
151 #[inline]
152 pub fn new_in(alloc: A) -> Self {
153 const { assert_align_is_power_of_two::<ALIGN>() };
154
155 AlignedVec {
156 ptr: NonNull::dangling(),
157 len: 0,
158 cap: 0,
159 alloc,
160 }
161 }
162
163 /// Creates a new aligned vector with the given capacity and allocator.
164 pub fn with_capacity_in(capacity: usize, alloc: A) -> Self {
165 if capacity == 0 {
166 return Self::new_in(alloc);
167 }
168
169 let layout = Self::layout_for(capacity);
170 let ptr = alloc.allocate(layout) as *mut T;
171
172 if ptr.is_null() {
173 handle_alloc_error(layout);
174 }
175
176 AlignedVec {
177 ptr: unsafe { NonNull::new_unchecked(ptr) },
178 len: 0,
179 cap: capacity,
180 alloc,
181 }
182 }
183
184 /// Creates a new aligned vector filled with zeros using the specified allocator.
185 pub fn zeros_in(len: usize, alloc: A) -> Self
186 where
187 T: bytemuck::Zeroable,
188 {
189 if len == 0 {
190 return Self::new_in(alloc);
191 }
192
193 let layout = Self::layout_for(len);
194 let ptr = alloc.allocate_zeroed(layout) as *mut T;
195
196 if ptr.is_null() {
197 handle_alloc_error(layout);
198 }
199
200 AlignedVec {
201 ptr: unsafe { NonNull::new_unchecked(ptr) },
202 len,
203 cap: len,
204 alloc,
205 }
206 }
207
208 /// Creates a new aligned vector filled with a value using the specified allocator.
209 pub fn filled_in(len: usize, value: T, alloc: A) -> Self
210 where
211 T: Clone,
212 {
213 let mut vec = Self::with_capacity_in(len, alloc);
214 for _ in 0..len {
215 vec.push(value.clone());
216 }
217 vec
218 }
219
220 /// Creates a new aligned vector from a slice using the specified allocator.
221 pub fn from_slice_in(slice: &[T], alloc: A) -> Self
222 where
223 T: Clone,
224 {
225 let mut vec = Self::with_capacity_in(slice.len(), alloc);
226 for item in slice {
227 vec.push(item.clone());
228 }
229 vec
230 }
231
232 /// Returns a reference to the allocator.
233 #[inline]
234 pub fn allocator(&self) -> &A {
235 &self.alloc
236 }
237
238 /// Returns the layout for a given capacity.
239 ///
240 /// # Panics
241 ///
242 /// Panics (via [`Self::capacity_overflow`]) if `capacity * size_of::<T>()`
243 /// overflows `usize`, or if the resulting size -- rounded up to
244 /// `ALIGN.max(align_of::<T>())` -- would exceed `isize::MAX` bytes.
245 ///
246 /// A naive `capacity * size_of::<T>()` would silently wrap around on
247 /// overflow in release builds (multiplication overflow checks are
248 /// disabled outside of `debug_assertions`), yielding a small, wrong
249 /// `size` that produces a *successfully allocated but undersized*
250 /// buffer. Callers such as [`Self::with_capacity_in`] would then record
251 /// the huge, un-wrapped `capacity` in `self.cap`, so later writes up to
252 /// that bogus capacity (e.g. via [`Self::push`] past `self.len`) would
253 /// write past the real allocation: a heap-buffer overflow. Using
254 /// `checked_mul` turns that silent memory-corruption bug into a loud,
255 /// immediate panic instead.
256 fn layout_for(capacity: usize) -> Layout {
257 const { assert_align_is_power_of_two::<ALIGN>() };
258
259 let size = match capacity.checked_mul(size_of::<T>()) {
260 Some(size) => size,
261 None => Self::capacity_overflow(),
262 };
263 let align = ALIGN.max(align_of::<T>());
264 match Layout::from_size_align(size, align) {
265 Ok(layout) => layout,
266 Err(_) => Self::capacity_overflow(),
267 }
268 }
269
270 /// Reports that `capacity` does not correspond to a valid, addressable
271 /// [`Layout`] for `T` and aborts via panic.
272 ///
273 /// This mirrors the strategy `alloc::raw_vec::RawVec` uses for oversized
274 /// capacities: rather than proceeding with a silently truncated
275 /// (wrapped) allocation size -- which would desynchronize the vector's
276 /// tracked capacity from its real allocation -- fail loudly and
277 /// immediately. Marked `#[cold]`/`#[inline(never)]` so the (exceedingly
278 /// rare) overflow path does not bloat the hot allocation path, and
279 /// implemented as a named function rather than `.unwrap()`/`.expect()`
280 /// so the panic message is specific to `AlignedVec` and its type/align
281 /// parameters.
282 #[cold]
283 #[inline(never)]
284 fn capacity_overflow() -> ! {
285 panic!(
286 "AlignedVec<{}>: capacity overflow -- requested capacity does not \
287 fit in a valid memory layout (capacity * size_of::<T>() overflows \
288 usize, or exceeds isize::MAX bytes when rounded up to align={ALIGN})",
289 core::any::type_name::<T>()
290 );
291 }
292
293 /// Returns the length of the vector.
294 #[inline]
295 pub fn len(&self) -> usize {
296 self.len
297 }
298
299 /// Returns true if the vector is empty.
300 #[inline]
301 pub fn is_empty(&self) -> bool {
302 self.len == 0
303 }
304
305 /// Returns the capacity of the vector.
306 #[inline]
307 pub fn capacity(&self) -> usize {
308 self.cap
309 }
310
311 /// Returns a pointer to the first element.
312 #[inline]
313 pub fn as_ptr(&self) -> *const T {
314 self.ptr.as_ptr()
315 }
316
317 /// Returns a mutable pointer to the first element.
318 #[inline]
319 pub fn as_mut_ptr(&mut self) -> *mut T {
320 self.ptr.as_ptr()
321 }
322
323 /// Returns a slice of the vector.
324 #[inline]
325 pub fn as_slice(&self) -> &[T] {
326 unsafe { core::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
327 }
328
329 /// Returns a mutable slice of the vector.
330 #[inline]
331 pub fn as_mut_slice(&mut self) -> &mut [T] {
332 unsafe { core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
333 }
334
335 /// Pushes a value onto the vector.
336 ///
337 /// # Panics
338 /// Panics if the vector is at capacity.
339 pub fn push(&mut self, value: T) {
340 if self.len >= self.cap {
341 self.grow();
342 }
343
344 unsafe {
345 self.ptr.as_ptr().add(self.len).write(value);
346 }
347 self.len += 1;
348 }
349
350 /// Pops a value from the vector.
351 pub fn pop(&mut self) -> Option<T> {
352 if self.len == 0 {
353 return None;
354 }
355
356 self.len -= 1;
357 unsafe { Some(self.ptr.as_ptr().add(self.len).read()) }
358 }
359
360 /// Clears the vector.
361 pub fn clear(&mut self) {
362 while self.pop().is_some() {}
363 }
364
365 /// Resizes the vector to the given length.
366 pub fn resize(&mut self, new_len: usize, value: T)
367 where
368 T: Clone,
369 {
370 if new_len > self.len {
371 self.reserve(new_len - self.len);
372 for _ in self.len..new_len {
373 self.push(value.clone());
374 }
375 } else {
376 while self.len > new_len {
377 self.pop();
378 }
379 }
380 }
381
382 /// Reserves capacity for at least `additional` more elements.
383 pub fn reserve(&mut self, additional: usize) {
384 let required = self.len + additional;
385 if required > self.cap {
386 let new_cap = required.max(self.cap * 2).max(8);
387 self.realloc(new_cap);
388 }
389 }
390
391 fn grow(&mut self) {
392 let new_cap = if self.cap == 0 { 8 } else { self.cap * 2 };
393 self.realloc(new_cap);
394 }
395
396 fn realloc(&mut self, new_cap: usize) {
397 let new_layout = Self::layout_for(new_cap);
398 let new_ptr = self.alloc.allocate(new_layout) as *mut T;
399
400 if new_ptr.is_null() {
401 handle_alloc_error(new_layout);
402 }
403
404 // Copy existing data
405 if self.cap > 0 {
406 unsafe {
407 core::ptr::copy_nonoverlapping(self.ptr.as_ptr(), new_ptr, self.len);
408 let old_layout = Self::layout_for(self.cap);
409 self.alloc
410 .deallocate(self.ptr.as_ptr() as *mut u8, old_layout);
411 }
412 }
413
414 self.ptr = unsafe { NonNull::new_unchecked(new_ptr) };
415 self.cap = new_cap;
416 }
417}
418
419impl<T, const ALIGN: usize, A: Alloc> Drop for AlignedVec<T, ALIGN, A> {
420 fn drop(&mut self) {
421 // Drop all elements
422 for i in 0..self.len {
423 unsafe {
424 core::ptr::drop_in_place(self.ptr.as_ptr().add(i));
425 }
426 }
427
428 // Deallocate
429 if self.cap > 0 {
430 let layout = Self::layout_for(self.cap);
431 unsafe {
432 self.alloc.deallocate(self.ptr.as_ptr() as *mut u8, layout);
433 }
434 }
435 }
436}
437
438impl<T, const ALIGN: usize> Default for AlignedVec<T, ALIGN, Global> {
439 fn default() -> Self {
440 Self::new()
441 }
442}
443
444impl<T: Clone, const ALIGN: usize, A: Alloc> Clone for AlignedVec<T, ALIGN, A> {
445 fn clone(&self) -> Self {
446 Self::from_slice_in(self.as_slice(), self.alloc.clone())
447 }
448}
449
450impl<T, const ALIGN: usize, A: Alloc> core::ops::Deref for AlignedVec<T, ALIGN, A> {
451 type Target = [T];
452
453 fn deref(&self) -> &Self::Target {
454 self.as_slice()
455 }
456}
457
458impl<T, const ALIGN: usize, A: Alloc> core::ops::DerefMut for AlignedVec<T, ALIGN, A> {
459 fn deref_mut(&mut self) -> &mut Self::Target {
460 self.as_mut_slice()
461 }
462}
463
464impl<T, const ALIGN: usize, A: Alloc> core::ops::Index<usize> for AlignedVec<T, ALIGN, A> {
465 type Output = T;
466
467 fn index(&self, index: usize) -> &Self::Output {
468 &self.as_slice()[index]
469 }
470}
471
472impl<T, const ALIGN: usize, A: Alloc> core::ops::IndexMut<usize> for AlignedVec<T, ALIGN, A> {
473 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
474 &mut self.as_mut_slice()[index]
475 }
476}
477
478// Safety: AlignedVec is Send/Sync if T and A are
479unsafe impl<T: Send, const ALIGN: usize, A: Alloc + Send> Send for AlignedVec<T, ALIGN, A> {}
480unsafe impl<T: Sync, const ALIGN: usize, A: Alloc + Sync> Sync for AlignedVec<T, ALIGN, A> {}
481
482#[cfg(test)]
483mod tests {
484 use super::*;
485
486 #[test]
487 fn with_capacity_allocates_correctly_aligned_and_sized_buffer() {
488 const ALIGN: usize = 64;
489 let vec: AlignedVec<f32, ALIGN> = AlignedVec::with_capacity(37);
490
491 assert_eq!(vec.capacity(), 37);
492 assert_eq!(vec.len(), 0);
493 assert_eq!(
494 vec.as_ptr() as usize % ALIGN,
495 0,
496 "buffer must be ALIGN-aligned"
497 );
498 }
499
500 #[test]
501 fn zeros_and_push_round_trip() {
502 let mut vec: AlignedVec<f64> = AlignedVec::zeros(4);
503 assert_eq!(vec.as_slice(), &[0.0, 0.0, 0.0, 0.0]);
504
505 vec.push(1.0);
506 vec.push(2.0);
507 assert_eq!(vec.len(), 6);
508 assert_eq!(&vec.as_slice()[4..], &[1.0, 2.0]);
509 }
510
511 #[test]
512 fn reserve_and_grow_preserve_existing_elements() {
513 let mut vec: AlignedVec<u64> = AlignedVec::with_capacity(2);
514 vec.push(10);
515 vec.push(20);
516 // Forces `grow` -> `realloc` -> `layout_for` with a larger capacity.
517 vec.push(30);
518 vec.reserve(64);
519
520 assert!(vec.capacity() >= 67);
521 assert_eq!(vec.as_slice(), &[10, 20, 30]);
522 }
523
524 // Regression test for: `layout_for` computing
525 // `capacity * size_of::<T>()` with an unchecked multiplication. In a
526 // release build (where integer-overflow checks are compiled out), a
527 // capacity just large enough to overflow `usize` would silently wrap
528 // around to a small `size`, so the allocator would hand back a tiny
529 // buffer while `self.cap` kept recording the huge, un-wrapped capacity
530 // requested by the caller -- a heap-buffer-overflow-in-waiting the
531 // moment anything wrote up to that bogus capacity. It must now fail
532 // loudly via `capacity_overflow` instead.
533 #[test]
534 #[should_panic(expected = "capacity overflow")]
535 fn with_capacity_overflowing_size_panics_instead_of_wrapping() {
536 // For `f64` (8 bytes), `usize::MAX / 2` multiplied by 8 overflows
537 // `usize` by a wide margin, so the old unchecked multiplication
538 // would have wrapped rather than triggering `Layout::from_size_align`'s
539 // own (unrelated) `isize::MAX` check.
540 let huge_capacity = usize::MAX / 2;
541 let _vec: AlignedVec<f64> = AlignedVec::with_capacity(huge_capacity);
542 }
543
544 // Regression test for the same bug reached via `zeros_in`, which builds
545 // its `Layout` the same way as `with_capacity_in`.
546 #[test]
547 #[should_panic(expected = "capacity overflow")]
548 fn zeros_overflowing_size_panics_instead_of_wrapping() {
549 let huge_len = usize::MAX / 2;
550 let _vec: AlignedVec<f64> = AlignedVec::zeros(huge_len);
551 }
552
553 // A capacity that does not overflow the `checked_mul` but whose size,
554 // once rounded up to `ALIGN`, exceeds `isize::MAX` must also be rejected
555 // by `Layout::from_size_align` and surfaced as `capacity_overflow`
556 // rather than propagating an `Err` (or, previously, panicking via a
557 // generic `.expect("Invalid layout")`).
558 #[test]
559 #[should_panic(expected = "capacity overflow")]
560 fn with_capacity_isize_max_exceeded_panics() {
561 // `capacity * size_of::<u8>()` does not overflow `usize` here, but
562 // it does exceed `isize::MAX`, which `Layout::from_size_align`
563 // rejects.
564 let capacity = isize::MAX as usize;
565 let _vec: AlignedVec<u8> = AlignedVec::with_capacity(capacity);
566 }
567}