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}