Skip to main content

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