Skip to main content

rustfs_erasure_codec/galois_8/
aligned.rs

1//! 64-byte-aligned shard storage for the Leopard codecs.
2//!
3//! [`AlignedShard`] backs a shard with a heap allocation whose base address is
4//! aligned to [`SHARD_ALIGNMENT`] (64 bytes). This alignment is a **cache and
5//! throughput optimisation, not a correctness requirement**: every SIMD kernel
6//! in this crate loads and stores with unaligned instructions
7//! (`_mm256_loadu_si256` / `_mm512_loadu_si512` / `vld1q_u8` /
8//! `core::ptr::read_unaligned`), so shards at any address decode correctly.
9//! Aligning to a 64-byte cache line simply avoids split-line accesses.
10//!
11//! These helpers serve both Leopard families. LeopardGF8 and LeopardGF16 are
12//! both built on `ReedSolomon<galois_8::Field>` and operate on byte-oriented
13//! shards, so `alloc_aligned` / [`alloc_aligned_shards`] produce correctly
14//! aligned buffers for either codec.
15
16extern crate alloc;
17
18use alloc::alloc::{alloc_zeroed, dealloc, handle_alloc_error};
19use alloc::vec;
20use alloc::vec::Vec;
21use core::alloc::Layout;
22use core::fmt;
23use core::iter::FromIterator;
24use core::ops::{Deref, DerefMut};
25use core::ptr::NonNull;
26use core::slice;
27
28use crate::ShardSlot;
29
30/// Byte alignment of every [`AlignedShard`] allocation (one cache line).
31///
32/// A performance knob, not a safety invariant — see the module docs. Callers
33/// that also need the shard **length** to be a multiple of this value (as the
34/// Leopard codecs require) should size shards with
35/// [`leopard_aligned_shard_len`](crate::galois_8::leopard_aligned_shard_len).
36pub const SHARD_ALIGNMENT: usize = 64;
37
38/// A shard whose backing allocation is aligned to [`SHARD_ALIGNMENT`].
39///
40/// The allocation is exactly `len` bytes (see [`AlignedShard::new_zeroed`]) —
41/// there is no rounding up and no spare capacity. Alignment is a cache/perf
42/// optimisation; correctness never depends on it (see the module docs).
43///
44/// Because `AlignedShard` implements [`AsRef`], [`AsMut`] and [`FromIterator`]
45/// over `u8`, a `Vec<Option<AlignedShard>>` is a valid input to reconstruction:
46/// missing (`None`) slots are materialised through the `FromIterator` impl, so
47/// recovered shards are 64-byte aligned like the rest.
48pub struct AlignedShard {
49    ptr: NonNull<u8>,
50    len: usize,
51}
52
53impl AlignedShard {
54    /// Allocates a zero-filled shard of **exactly** `len` bytes, 64-byte aligned.
55    ///
56    /// The allocation size equals `len` precisely: there is no round-up to the
57    /// alignment and no excess capacity. `len == 0` yields an empty,
58    /// non-allocating shard backed by a dangling (but aligned) pointer.
59    pub fn new_zeroed(len: usize) -> Self {
60        if len == 0 {
61            return Self {
62                ptr: NonNull::dangling(),
63                len: 0,
64            };
65        }
66
67        let layout = Layout::from_size_align(len, SHARD_ALIGNMENT)
68            .expect("aligned shard layout must be valid: len or alignment overflow");
69        // SAFETY: `layout` is valid (checked above). `alloc_zeroed` returns a
70        // uniquely owned, zero-filled allocation or null on OOM. The returned
71        // pointer is valid for `layout.size()` bytes.
72        let ptr = unsafe { alloc_zeroed(layout) };
73        let ptr = NonNull::new(ptr).unwrap_or_else(|| handle_alloc_error(layout));
74
75        Self { ptr, len }
76    }
77
78    pub fn from_slice(data: &[u8]) -> Self {
79        let mut shard = Self::new_zeroed(data.len());
80        shard.as_mut().copy_from_slice(data);
81        shard
82    }
83
84    pub fn len(&self) -> usize {
85        self.len
86    }
87
88    pub fn is_empty(&self) -> bool {
89        self.len == 0
90    }
91
92    pub fn as_ptr(&self) -> *const u8 {
93        self.ptr.as_ptr()
94    }
95
96    pub fn as_mut_ptr(&mut self) -> *mut u8 {
97        self.ptr.as_ptr()
98    }
99}
100
101impl Clone for AlignedShard {
102    fn clone(&self) -> Self {
103        Self::from_slice(self.as_ref())
104    }
105}
106
107impl fmt::Debug for AlignedShard {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        f.debug_struct("AlignedShard")
110            .field("len", &self.len)
111            .field("alignment", &SHARD_ALIGNMENT)
112            .finish()
113    }
114}
115
116impl Drop for AlignedShard {
117    fn drop(&mut self) {
118        if self.len == 0 {
119            return;
120        }
121
122        let layout = Layout::from_size_align(self.len, SHARD_ALIGNMENT)
123            .expect("aligned shard layout must be valid: len or alignment overflow");
124        // SAFETY: `self.ptr` was allocated from `alloc_zeroed` with the same
125        // layout in `new_zeroed`. This type owns the allocation uniquely
126        // (no cloning without explicit Clone impl that creates a new allocation).
127        unsafe {
128            dealloc(self.ptr.as_ptr(), layout);
129        }
130    }
131}
132
133impl Deref for AlignedShard {
134    type Target = [u8];
135
136    fn deref(&self) -> &Self::Target {
137        self.as_ref()
138    }
139}
140
141impl DerefMut for AlignedShard {
142    fn deref_mut(&mut self) -> &mut Self::Target {
143        self.as_mut()
144    }
145}
146
147impl AsRef<[u8]> for AlignedShard {
148    fn as_ref(&self) -> &[u8] {
149        // SAFETY: `self.ptr` points to `self.len` bytes allocated via `alloc_zeroed`
150        // with SHARD_ALIGNMENT, or is `NonNull::dangling()` when `self.len == 0`
151        // (which produces a valid empty slice). The allocation is uniquely owned
152        // by this value and outlives the returned reference.
153        unsafe { slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
154    }
155}
156
157impl AsMut<[u8]> for AlignedShard {
158    fn as_mut(&mut self) -> &mut [u8] {
159        // SAFETY: same as `as_ref`. `&mut self` guarantees unique mutable access
160        // — no other reference to the same memory can exist concurrently.
161        unsafe { slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
162    }
163}
164
165impl FromIterator<u8> for AlignedShard {
166    fn from_iter<T: IntoIterator<Item = u8>>(iter: T) -> Self {
167        let bytes: Vec<u8> = iter.into_iter().collect();
168        Self::from_slice(&bytes)
169    }
170}
171
172// SAFETY: `AlignedShard` owns a heap allocation of plain `u8` bytes with no
173// interior mutability. Moving it across threads transfers ownership and cannot
174// create aliased mutable references. The `NonNull<u8>` pointer is not shared
175// across threads — it follows the value.
176unsafe impl Send for AlignedShard {}
177// SAFETY: Shared `&AlignedShard` only exposes immutable `[u8]` slices via
178// `as_ref()`. Mutable access requires `&mut self`, which the borrow checker
179// ensures is exclusive. No interior mutability or shared mutable state exists.
180unsafe impl Sync for AlignedShard {}
181
182/// Allocates `total_shards` zero-filled [`AlignedShard`]s of `shard_len` bytes.
183///
184/// Each shard is exactly `shard_len` bytes and 64-byte aligned. Suitable for
185/// both LeopardGF8 and LeopardGF16, which share the `galois_8::Field` byte
186/// layout. To pick a `shard_len` that Leopard will accept, use
187/// [`leopard_aligned_shard_len`](crate::galois_8::leopard_aligned_shard_len).
188pub fn alloc_aligned_shards(total_shards: usize, shard_len: usize) -> Vec<AlignedShard> {
189    (0..total_shards)
190        .map(|_| AlignedShard::new_zeroed(shard_len))
191        .collect()
192}
193
194pub fn alloc_shard_slots(total_shards: usize, shard_len: usize) -> Vec<ShardSlot<Vec<u8>>> {
195    (0..total_shards)
196        .map(|_| ShardSlot::new_missing(vec![0u8; shard_len]))
197        .collect()
198}
199
200pub fn shards_to_slots<T: Clone>(shards: &[T]) -> Vec<ShardSlot<T>> {
201    shards.iter().cloned().map(ShardSlot::new_present).collect()
202}
203
204pub fn mark_missing_slots<T>(slots: &mut [ShardSlot<T>], missing_indices: &[usize]) {
205    for &idx in missing_indices {
206        if let Some(slot) = slots.get_mut(idx) {
207            slot.mark_missing();
208        }
209    }
210}
211
212impl crate::ReedSolomon<super::Field> {
213    /// Allocates one 64-byte-aligned shard per shard slot (`data + parity`),
214    /// each `shard_len` bytes.
215    ///
216    /// The buffers work with either Leopard family. Size `shard_len` with
217    /// [`leopard_aligned_shard_len`](crate::galois_8::leopard_aligned_shard_len)
218    /// so the length is a non-zero multiple of 64 as Leopard requires.
219    pub fn alloc_aligned(&self, shard_len: usize) -> Vec<AlignedShard> {
220        alloc_aligned_shards(self.total_shard_count(), shard_len)
221    }
222
223    pub fn alloc_shard_slots(&self, shard_len: usize) -> Vec<ShardSlot<Vec<u8>>> {
224        alloc_shard_slots(self.total_shard_count(), shard_len)
225    }
226}