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