Skip to main content

rlnc_simdx/
aligned.rs

1//! Aligned memory buffer — 64-byte aligned heap storage for hot paths.
2//!
3//! Safe public kernels accept any `&[u8]` (unaligned load/store).
4//! `AlignedBuffer` is used internally (and optionally by callers) so encoder,
5//! decoder, and matrix rows hit aligned SIMD paths by default.
6//!
7//! **Public constructors:** [`AlignedBuffer::zeroed`],
8//! [`AlignedBuffer::from_slice`].
9//! **Internal only:** `new_uninit` is `pub(crate)` so external code cannot
10//! obtain a slice over uninitialized memory.
11//!
12//! ## Usage
13//!
14//! ```rust
15//! use rlnc_simdx::AlignedBuffer;
16//!
17//! let src = AlignedBuffer::from_slice(&[0xABu8; 64]);
18//! let mut dst = AlignedBuffer::zeroed(64);
19//!
20//! assert_eq!(src.as_ptr() as usize % 64, 0);
21//! // Safe public kernel — no `unsafe` required
22//! rlnc_simdx::kernel::scale(0x03, &src, &mut dst);
23//! assert_eq!(dst.len(), 64);
24//! ```
25
26#[cfg(feature = "alloc")]
27extern crate alloc;
28
29use core::ptr::NonNull;
30
31#[cfg(feature = "alloc")]
32use alloc::alloc::{alloc, alloc_zeroed, dealloc, Layout};
33
34/// AVX-512 requires 64-byte alignment for aligned load/store instructions.
35/// Using 64 as the default covers all tiers: SSE (16), AVX2 (32), AVX-512 (64).
36pub const ALIGN: usize = 64;
37
38/// A heap-allocated byte buffer with guaranteed 64-byte alignment.
39///
40/// Implements `Deref<Target = [u8]>` and `DerefMut` for ergonomic use.
41///
42/// On `no_std + alloc` targets, uses `alloc::alloc::alloc` with a 64-byte
43/// aligned `Layout`.  On `std` targets this is equivalent.
44///
45/// # Panics
46/// Panics on allocation failure (same behaviour as `Vec::with_capacity`).
47#[cfg(feature = "alloc")]
48pub struct AlignedBuffer {
49    ptr: NonNull<u8>,
50    len: usize,
51    cap: usize, // allocated capacity in bytes (always >= len, rounded to ALIGN)
52}
53
54#[cfg(feature = "alloc")]
55impl AlignedBuffer {
56    /// Allocate an uninitialised buffer of `len` bytes, 64-byte aligned.
57    ///
58    /// **Crate-private** — must not be exposed to external safe code, which
59    /// could read uninitialised memory via [`as_slice`] / `Deref`. Callers
60    /// inside this crate must fully initialise before any read (see
61    /// [`from_slice`]).
62    ///
63    /// Prefer [`zeroed`](AlignedBuffer::zeroed) or [`from_slice`] for all
64    /// public construction paths.
65    pub(crate) fn new_uninit(len: usize) -> Self {
66        if len == 0 {
67            return Self {
68                ptr: NonNull::dangling(),
69                len: 0,
70                cap: 0,
71            };
72        }
73        let cap = Self::padded_cap(len);
74        let layout = Self::layout(cap);
75        // SAFETY: layout has non-zero size
76        let ptr = unsafe { alloc(layout) };
77        let ptr = NonNull::new(ptr).expect("allocation failed");
78        Self { ptr, len, cap }
79    }
80
81    /// Allocate a zero-initialised buffer of `len` bytes, 64-byte aligned.
82    pub fn zeroed(len: usize) -> Self {
83        if len == 0 {
84            return Self {
85                ptr: NonNull::dangling(),
86                len: 0,
87                cap: 0,
88            };
89        }
90        let cap = Self::padded_cap(len);
91        let layout = Self::layout(cap);
92        // SAFETY: layout has non-zero size
93        let ptr = unsafe { alloc_zeroed(layout) };
94        let ptr = NonNull::new(ptr).expect("allocation failed");
95        Self { ptr, len, cap }
96    }
97
98    /// Allocate and copy from `src`.
99    pub fn from_slice(src: &[u8]) -> Self {
100        let mut buf = Self::new_uninit(src.len());
101        if !src.is_empty() {
102            // SAFETY: both regions are valid for `src.len()` bytes and the new
103            // allocation cannot overlap the borrowed source. Raw copy avoids
104            // forming a byte slice over uninitialized storage before writing.
105            unsafe {
106                core::ptr::copy_nonoverlapping(src.as_ptr(), buf.as_mut_ptr(), src.len());
107            }
108        }
109        buf
110    }
111
112    /// Length of the buffer in bytes.
113    #[inline]
114    pub fn len(&self) -> usize {
115        self.len
116    }
117
118    /// True if the buffer is empty.
119    #[inline]
120    pub fn is_empty(&self) -> bool {
121        self.len == 0
122    }
123
124    /// Byte-aligned pointer to the start of the buffer.
125    #[inline]
126    pub fn as_ptr(&self) -> *const u8 {
127        self.ptr.as_ptr()
128    }
129
130    /// Mutable byte-aligned pointer to the start of the buffer.
131    #[inline]
132    pub fn as_mut_ptr(&mut self) -> *mut u8 {
133        self.ptr.as_ptr()
134    }
135
136    /// Immutable slice view.
137    #[inline]
138    pub fn as_slice(&self) -> &[u8] {
139        // SAFETY: ptr is valid for `len` bytes, properly aligned, allocated above
140        unsafe { core::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
141    }
142
143    /// Copy contents into a newly allocated `Vec<u8>` (may be unaligned).
144    ///
145    /// Useful for FFI and APIs that require `Vec`.
146    #[cfg(feature = "alloc")]
147    pub fn to_vec(&self) -> alloc::vec::Vec<u8> {
148        self.as_slice().to_vec()
149    }
150
151    /// Consume this buffer and return a `Vec<u8>` copy of the data.
152    ///
153    /// Note: alignment is not preserved in the `Vec` (heap allocator default).
154    #[cfg(feature = "alloc")]
155    pub fn into_vec(self) -> alloc::vec::Vec<u8> {
156        self.as_slice().to_vec()
157    }
158
159    /// Mutable slice view.
160    #[inline]
161    pub fn as_mut_slice(&mut self) -> &mut [u8] {
162        // SAFETY: ptr is valid for `len` bytes, uniquely owned
163        unsafe { core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
164    }
165
166    /// Fill entire buffer with `val`.
167    pub fn fill(&mut self, val: u8) {
168        self.as_mut_slice().fill(val);
169    }
170
171    /// Zero all bytes.
172    pub fn zero(&mut self) {
173        self.fill(0);
174    }
175
176    // ── internal helpers ────────────────────────────────────────────────────
177
178    #[inline]
179    fn padded_cap(len: usize) -> usize {
180        // Round up to ALIGN so the allocation itself is a multiple of ALIGN
181        len.checked_add(ALIGN - 1)
182            .map(|value| value & !(ALIGN - 1))
183            .expect("AlignedBuffer capacity overflow")
184    }
185
186    #[inline]
187    fn layout(cap: usize) -> Layout {
188        Layout::from_size_align(cap, ALIGN).expect("AlignedBuffer layout overflow")
189    }
190}
191
192// ── Deref / DerefMut ────────────────────────────────────────────────────────
193
194#[cfg(feature = "alloc")]
195impl core::ops::Deref for AlignedBuffer {
196    type Target = [u8];
197    #[inline]
198    fn deref(&self) -> &[u8] {
199        self.as_slice()
200    }
201}
202
203#[cfg(feature = "alloc")]
204impl core::ops::DerefMut for AlignedBuffer {
205    #[inline]
206    fn deref_mut(&mut self) -> &mut [u8] {
207        self.as_mut_slice()
208    }
209}
210
211// ── Drop ────────────────────────────────────────────────────────────────────
212
213#[cfg(feature = "alloc")]
214impl Drop for AlignedBuffer {
215    fn drop(&mut self) {
216        if self.cap > 0 {
217            let layout = Self::layout(self.cap);
218            // SAFETY: ptr was allocated with the same layout
219            unsafe { dealloc(self.ptr.as_ptr(), layout) };
220        }
221    }
222}
223
224// ── Send + Sync: safe because AlignedBuffer uniquely owns its allocation ────
225#[cfg(feature = "alloc")]
226unsafe impl Send for AlignedBuffer {}
227#[cfg(feature = "alloc")]
228unsafe impl Sync for AlignedBuffer {}
229
230// ── Debug / Clone ───────────────────────────────────────────────────────────
231
232#[cfg(feature = "alloc")]
233impl core::fmt::Debug for AlignedBuffer {
234    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
235        write!(
236            f,
237            "AlignedBuffer {{ len: {}, align: {ALIGN}, ptr: {:p} }}",
238            self.len,
239            self.as_ptr()
240        )
241    }
242}
243
244#[cfg(feature = "alloc")]
245impl Clone for AlignedBuffer {
246    fn clone(&self) -> Self {
247        Self::from_slice(self.as_slice())
248    }
249}
250
251// ── Tests ───────────────────────────────────────────────────────────────────
252
253#[cfg(test)]
254#[cfg(feature = "alloc")]
255mod tests {
256    use super::*;
257
258    #[test]
259    fn alignment_guarantee() {
260        for len in [1usize, 15, 16, 63, 64, 65, 1024, 65536] {
261            let buf = AlignedBuffer::zeroed(len);
262            assert_eq!(
263                buf.as_ptr() as usize % ALIGN,
264                0,
265                "len={len}: pointer not {ALIGN}-byte aligned"
266            );
267            assert_eq!(buf.len(), len);
268        }
269    }
270
271    #[test]
272    fn zero_size() {
273        let buf = AlignedBuffer::zeroed(0);
274        assert!(buf.is_empty());
275        assert_eq!(buf.len(), 0);
276    }
277
278    #[test]
279    #[should_panic(expected = "AlignedBuffer capacity overflow")]
280    fn oversized_length_panics_before_allocation() {
281        let _ = AlignedBuffer::zeroed(usize::MAX);
282    }
283
284    #[test]
285    fn from_slice_roundtrip() {
286        let data: Vec<u8> = (0u8..128).collect();
287        let buf = AlignedBuffer::from_slice(&data);
288        assert_eq!(buf.as_ptr() as usize % ALIGN, 0);
289        assert_eq!(buf.as_slice(), data.as_slice());
290    }
291
292    #[test]
293    fn clone_is_independent() {
294        let mut a = AlignedBuffer::from_slice(&[1u8, 2, 3, 4]);
295        let b = a.clone();
296        a.as_mut_slice()[0] = 0xFF;
297        assert_eq!(b.as_slice()[0], 1); // clone unaffected
298    }
299
300    #[test]
301    fn deref_works_with_kernel() {
302        let x = AlignedBuffer::from_slice(&[0x42u8; 64]);
303        let mut y = AlignedBuffer::zeroed(64);
304        // AlignedBuffer derefs to &[u8] / &mut [u8] transparently
305        crate::kernel::axpy(0x03, &x, &mut y);
306        // Result should equal scalar reference
307        let mut y_ref = vec![0u8; 64];
308        crate::kernel::scalar::axpy(0x03, &x, &mut y_ref);
309        assert_eq!(y.as_slice(), y_ref.as_slice());
310    }
311}