primitives/utils/codec/mod.rs
1//! Fast, architecture-independent fixed-size (de)serialization.
2//!
3//! [`InPlaceCodec`] encodes a value to (and decodes it from) a fixed-size byte window directly.
4//! It bypasses the per-element `serde` dispatch. The encoding need not match any external
5//! format (`serde`/`bincode`). It MUST be architecture-independent (fixed-width little-endian)
6//! and fast.
7
8use std::mem::MaybeUninit;
9
10use crate::errors::PrimitiveError;
11
12pub mod bincode_io;
13pub mod containers;
14pub mod legacy;
15pub mod prims;
16
17/// A type that can be (de)serialized to/from a fixed-size byte window directly.
18///
19/// The encoding MUST be architecture-independent (same bytes on any target — in practice
20/// fixed-width little-endian). Otherwise it is this codec's own format: it need NOT match the
21/// type's `serde::Serialize`/`Deserialize` nor its `bincode` encoding.
22///
23/// # Safety
24/// Implementors MUST guarantee:
25/// - `write_le_bytes` MUST initialize every one of the `ENCODED_SIZE` bytes of `out`, without
26/// reading `out` (it may be uninitialized memory).
27/// - `read_le_bytes` MUST be unbiased: given `bytes.len() == ENCODED_SIZE`, if `write_le_bytes`
28/// could have produced `bytes`, `read_le_bytes` must return the same value.
29pub unsafe trait InPlaceCodec: Sized {
30 /// Encoded width in bytes.
31 const ENCODED_SIZE: usize;
32
33 /// Write `self`'s canonical encoding into `out`, initializing every byte.
34 /// `out.len() == Self::ENCODED_SIZE`.
35 fn write_le_bytes(&self, out: &mut [MaybeUninit<u8>]);
36
37 /// Parse `self` from `bytes`, validating it. `bytes.len() == Self::ENCODED_SIZE`.
38 /// Returns an error on an invalid (e.g. non-canonical) encoding.
39 fn read_le_bytes(bytes: &[u8]) -> Result<Self, PrimitiveError>;
40
41 /// Number of consecutive elements that encode together as a "pack". `1` (the default) means no
42 /// grouping: each element is encoded independently via [`Self::write_le_bytes`]. MUST be `>=
43 /// 1`.
44 ///
45 /// Container codecs (e.g. `HeapArray<_, N>`) split a run of elements into `N / PACK` full packs
46 /// plus an `N % PACK` per-element tail. Types with `PACK > 1` MUST be `Copy` (see
47 /// [`Self::read_pack`]).
48 const PACK: usize = 1;
49
50 /// Bytes a full `PACK`-element pack occupies. Defaults to `PACK * ENCODED_SIZE` (the packed
51 /// bytes are exactly the per-element bytes laid out back-to-back — the case for a pure
52 /// vectorization like `Mersenne107`). Override with a smaller value for a sub-byte packing
53 /// (e.g. `Gf2`: `PACK = 8`, `PACK_BYTES = 1`).
54 const PACK_BYTES: usize = Self::PACK * Self::ENCODED_SIZE;
55
56 /// Encode exactly [`Self::PACK`] elements (`items.len() == PACK`) into `out`
57 /// (`out.len() == PACK_BYTES`). MUST initialize every byte.
58 ///
59 /// The default encodes the group element-by-element (valid whenever
60 /// `PACK_BYTES == PACK * ENCODED_SIZE`). Override it for a vectorized or sub-byte-packed
61 /// encoding.
62 fn write_pack(items: &[Self], out: &mut [MaybeUninit<u8>]) {
63 for (chunk, item) in out.chunks_exact_mut(Self::ENCODED_SIZE).zip(items) {
64 item.write_le_bytes(chunk);
65 }
66 }
67
68 /// Decode a full pack: read [`Self::PACK`] elements from `bytes` (`bytes.len() == PACK_BYTES`)
69 /// into `out` (`out.len() == PACK`). Returns an error on an invalid encoding.
70 ///
71 /// The default decodes element-by-element (valid whenever `PACK_BYTES == PACK * ENCODED_SIZE`).
72 /// On an error it may leave some of `out` written. The container caller drops only *completed*
73 /// packs, so any type with `PACK > 1` MUST be `Copy` (partially-written packs are not dropped).
74 fn read_pack(bytes: &[u8], out: &mut [MaybeUninit<Self>]) -> Result<(), PrimitiveError> {
75 for (chunk, slot) in bytes.chunks_exact(Self::ENCODED_SIZE).zip(out.iter_mut()) {
76 slot.write(Self::read_le_bytes(chunk)?);
77 }
78 Ok(())
79 }
80
81 /// Serialize `self` into a freshly-allocated buffer of exactly `Self::ENCODED_SIZE` bytes,
82 /// bypassing per-element `serde` dispatch.
83 fn to_inplace_bytes(&self) -> Vec<u8> {
84 let mut out = Vec::with_capacity(Self::ENCODED_SIZE);
85 let spare = &mut out.spare_capacity_mut()[..Self::ENCODED_SIZE];
86 self.write_le_bytes(spare);
87 // SAFETY: `write_le_bytes` initializes every byte of `spare` above.
88 unsafe { out.set_len(Self::ENCODED_SIZE) };
89 out
90 }
91
92 /// Deserialize `Self` from exactly `Self::ENCODED_SIZE` bytes.
93 fn from_inplace_bytes(bytes: &[u8]) -> Result<Self, PrimitiveError> {
94 if bytes.len() != Self::ENCODED_SIZE {
95 return Err(PrimitiveError::InvalidSize(Self::ENCODED_SIZE, bytes.len()));
96 }
97 Self::read_le_bytes(bytes)
98 }
99}
100
101/// Whether a trailing partial group of `rem` (`0 < rem < PACK`) elements is smaller encoded
102/// individually (`rem * ENCODED_SIZE` bytes) than padded up to a full pack (`PACK_BYTES` bytes).
103/// `rem` is a plain count, never the elements' values. The encoder and decoder therefore always
104/// agree on this without any extra tag on the wire. See
105/// [`write_packed_le_bytes`]/[`read_packed_le_bytes`].
106const fn tail_is_unpacked<T: InPlaceCodec>(rem: usize) -> bool {
107 rem * T::ENCODED_SIZE < T::PACK_BYTES
108}
109
110/// Bytes needed for a trailing partial group of `rem` (`0 <= rem < PACK`) elements: whichever of
111/// padding to a full pack or encoding `rem` elements individually is smaller (see
112/// [`tail_is_unpacked`]).
113const fn tail_size<T: InPlaceCodec>(rem: usize) -> usize {
114 if rem == 0 {
115 0
116 } else if tail_is_unpacked::<T>(rem) {
117 rem * T::ENCODED_SIZE
118 } else {
119 T::PACK_BYTES
120 }
121}
122
123/// Shared by `HeapArray<T, M>`, `hybrid_array::Array<T, N>` and `[T; N]`'s `InPlaceCodec` impls:
124/// the `PACK`-aware encoded size, `(len / PACK) * PACK_BYTES + tail_size(len % PACK)`. See
125/// [`tail_size`]. For `PACK == 1` this reduces to `T::ENCODED_SIZE * len`.
126pub(crate) const fn packed_size<T: InPlaceCodec>(len: usize) -> usize {
127 (len / T::PACK) * T::PACK_BYTES + tail_size::<T>(len % T::PACK)
128}
129
130/// Shared by `HeapArray<T, M>`, `hybrid_array::Array<T, N>` and `[T; N]`'s `InPlaceCodec` impls:
131/// write `items` as `items.len() / PACK` full packs (each `PACK_BYTES`), plus a trailing
132/// `items.len() % PACK`-element tail encoded however [`tail_size`] says is smaller: padded up to
133/// one more full pack, or element-by-element. `out.len()` MUST equal
134/// `packed_size::<T>(items.len())`.
135///
136/// `T::PACK` is a constant, so the `PACK == 1` branch is compile-time-folded to a single flat loop
137/// with no per-element `write_pack` call or iterator setup. This is the common case (field
138/// elements, points, ...).
139pub(crate) fn write_packed_le_bytes<T: InPlaceCodec>(items: &[T], out: &mut [MaybeUninit<u8>]) {
140 if T::PACK == 1 {
141 for (chunk, elem) in out.chunks_exact_mut(T::ENCODED_SIZE).zip(items) {
142 elem.write_le_bytes(chunk);
143 }
144 return;
145 }
146 let n_full = items.len() / T::PACK;
147 let rem = items.len() % T::PACK;
148 let (packed, last) = out.split_at_mut(n_full * T::PACK_BYTES);
149 for (chunk, group) in packed
150 .chunks_exact_mut(T::PACK_BYTES)
151 .zip(items.chunks_exact(T::PACK))
152 {
153 T::write_pack(group, chunk);
154 }
155 if rem == 0 {
156 return;
157 }
158 let tail_items = &items[n_full * T::PACK..];
159 if tail_is_unpacked::<T>(rem) {
160 // Encoding the tail element-by-element is smaller than padding it to a full pack.
161 for (chunk, item) in last.chunks_exact_mut(T::ENCODED_SIZE).zip(tail_items) {
162 item.write_le_bytes(chunk);
163 }
164 return;
165 }
166 // A trailing partial group is padded with duplicates of its last element up to a full
167 // `PACK`-element pack. `write_pack`'s bit-packed layout depends on every element's value,
168 // not just the count, so this lets it encode the group directly. The dummy values are
169 // never read back: `read_packed_le_bytes` decodes the padded pack and keeps only its
170 // first `rem` elements.
171 //
172 // SAFETY: `T::PACK > 1` requires `T: Copy` (see `read_pack`'s doc). Duplicating an
173 // element's bytes via `ptr::read` and letting the duplicate drop normally alongside the
174 // original (still owned by `items`) is exactly `Copy` semantics: no double-free.
175 let mut padded: Vec<T> = tail_items
176 .iter()
177 .map(|item| unsafe { std::ptr::read(item) })
178 .collect();
179 padded.resize_with(T::PACK, || unsafe { std::ptr::read(&tail_items[rem - 1]) });
180 T::write_pack(&padded, last);
181}
182
183/// Shared by `HeapArray<T, M>` and `hybrid_array::Array<T, N>`'s `InPlaceCodec` impls: the
184/// decoding counterpart of [`write_packed_le_bytes`]. Fills every one of `data`'s `data.len()`
185/// slots from `bytes` (`bytes.len()` MUST equal `packed_size::<T>(data.len())`).
186///
187/// `T` need not be `Copy`, so a partially-filled `data` on an early error return would leak (or
188/// double-free) the already-written prefix. An internal drop guard MUST drop exactly the
189/// completed prefix and nothing else. (Types with `PACK > 1` are `Copy`, so a mid-pack failure
190/// that leaves part of a pack written has nothing to drop; see `read_pack`.)
191pub(crate) fn read_packed_le_bytes<T: InPlaceCodec>(
192 bytes: &[u8],
193 data: &mut [MaybeUninit<T>],
194) -> Result<(), PrimitiveError> {
195 let mut guard = SliceDropGuard::<T>::new(data.as_mut_ptr());
196 if T::PACK == 1 {
197 for (chunk, slot) in bytes.chunks_exact(T::ENCODED_SIZE).zip(data.iter_mut()) {
198 slot.write(T::read_le_bytes(chunk)?);
199 guard.inc_len();
200 }
201 } else {
202 let n_full = data.len() / T::PACK;
203 let rem = data.len() % T::PACK;
204 let (packed, last) = bytes.split_at(n_full * T::PACK_BYTES);
205 let mut written = 0usize;
206 for chunk in packed.chunks_exact(T::PACK_BYTES) {
207 T::read_pack(chunk, &mut data[written..written + T::PACK])?;
208 written += T::PACK;
209 guard.add_len(T::PACK);
210 }
211 if rem > 0 {
212 if tail_is_unpacked::<T>(rem) {
213 // Tail was encoded element-by-element, not padded (see `write_packed_le_bytes`).
214 for chunk in last.chunks_exact(T::ENCODED_SIZE) {
215 data[written].write(T::read_le_bytes(chunk)?);
216 written += 1;
217 guard.inc_len();
218 }
219 } else {
220 // The trailing pack was padded with dummy elements up to a full `PACK`-element
221 // group on encode (see `write_packed_le_bytes`). Decode it whole, then keep only
222 // the first `rem` real elements and let the owned `Vec`'s iterator drop the dummy
223 // padding.
224 let mut padded = Box::<[T]>::new_uninit_slice(T::PACK);
225 T::read_pack(last, &mut padded)?;
226 // SAFETY: `read_pack` above succeeded, so it initialized every one of the
227 // `T::PACK` slots.
228 let padded = unsafe { padded.assume_init() };
229 for item in Vec::from(padded).into_iter().take(rem) {
230 data[written].write(item);
231 written += 1;
232 guard.inc_len();
233 }
234 }
235 }
236 }
237 // SAFETY: every slot of `data` was just written above. Every `return`/`?` above this point is
238 // an error path, so reaching here means the loops ran to completion. Forgetting the guard
239 // hands the now-fully-initialized `data` back to the caller instead of dropping it as empty.
240 std::mem::forget(guard);
241 Ok(())
242}
243
244/// Drops the initialized prefix of a `*mut MaybeUninit<T>` slice on unwind / early return.
245///
246/// Used by [`InPlaceCodec`] decoders that fill an uninitialized buffer element by element. If a
247/// mid-way decode fails, the already-written prefix MUST be dropped, and nothing else, to avoid
248/// leaks or double-frees for non-`Copy` `T`.
249struct SliceDropGuard<T> {
250 ptr: *mut MaybeUninit<T>,
251 initialized_len: usize,
252}
253
254impl<T> SliceDropGuard<T> {
255 fn new(ptr: *mut MaybeUninit<T>) -> Self {
256 Self {
257 ptr,
258 initialized_len: 0,
259 }
260 }
261
262 #[inline(always)]
263 #[allow(clippy::arithmetic_side_effects)]
264 fn inc_len(&mut self) {
265 self.initialized_len += 1;
266 }
267
268 #[inline(always)]
269 #[allow(clippy::arithmetic_side_effects)]
270 fn add_len(&mut self, n: usize) {
271 self.initialized_len += n;
272 }
273}
274
275impl<T> Drop for SliceDropGuard<T> {
276 #[inline(always)]
277 fn drop(&mut self) {
278 unsafe {
279 std::ptr::drop_in_place(std::ptr::slice_from_raw_parts_mut(
280 self.ptr.cast::<T>(),
281 self.initialized_len,
282 ));
283 }
284 }
285}