Skip to main content

beve/
ext.rs

1use crate::de::from_slice;
2use crate::fast::{BeveTypedSlice, to_vec_complex_slice, write_bool_slice, write_typed_slice};
3use crate::header::{ARRAY_UNSIGNED, EXT_MATRICES, TYPE_TYPED_ARRAY, make_extension_header};
4use crate::size::write_size;
5use crate::value::Value;
6use core::any::TypeId;
7use half::{bf16, f16};
8use serde::{Deserialize, Serialize, de, ser};
9
10pub(crate) const NT_RAW_VALUE: &str = "__beve_raw_value";
11pub(crate) const NT_COMPLEX: &str = "__beve_complex";
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum MatrixDecodeMode {
15    Materialized,
16    Raw,
17}
18
19#[derive(Debug, Clone, PartialEq)]
20pub enum DecodedMatrix<T> {
21    Materialized(MatrixOwned<T>),
22    Raw(RawMatrix),
23}
24
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26pub struct RawMatrix {
27    pub layout: MatrixLayout,
28    pub extents: Vec<usize>,
29    pub value: Value,
30}
31
32struct RawBytes<'a>(&'a [u8]);
33
34impl Serialize for RawBytes<'_> {
35    fn serialize<S: serde::Serializer>(&self, s: S) -> core::result::Result<S::Ok, S::Error> {
36        s.serialize_bytes(self.0)
37    }
38}
39
40#[repr(C)]
41#[derive(Debug, Clone, Copy, PartialEq)]
42pub struct Complex<T> {
43    pub re: T,
44    pub im: T,
45}
46
47// SAFETY: `Complex<T>` is `#[repr(C)]` over two `T` fields with no padding, so it
48// is zeroable / all-bits-valid exactly when `T` is. These let `Complex<scalar>`
49// be decoded through the bulk `complex_array::*` path (which requires
50// `AnyBitPattern`); `bytemuck` provides the blanket `Pod: AnyBitPattern`.
51unsafe impl<T: bytemuck::Zeroable> bytemuck::Zeroable for Complex<T> {}
52unsafe impl<T: bytemuck::Pod> bytemuck::Pod for Complex<T> {}
53
54/// A type the bulk complex-array helpers may read as raw bytes: two contiguous
55/// [`Self::Component`] scalars, real part first.
56///
57/// Implementing this is how a foreign complex type — `num_complex::Complex<T>`,
58/// or your own `#[repr(C)]` pair — reaches [`crate::complex`]'s
59/// `serialize_with` helpers and [`crate::complex_array`]'s `serde(with)`
60/// modules. [`Complex`] implements it for every scalar BEVE encodes, and the
61/// `num-complex` feature adds `num_complex::Complex<T>`.
62///
63/// `Component` is an associated type so the helpers can name it in their
64/// bounds: `complex::f32_array` requires `Component = f32`, so a same-width
65/// class cannot cross.
66///
67/// # Safety
68///
69/// `Self` must have the layout `#[repr(C)] struct { re: Component, im: Component }`:
70/// two `Component` values, real first, no padding, every byte initialized. The
71/// helpers read `size_of_val(slice)` bytes out of a `&[Self]`, so padding leaks
72/// uninitialized memory into the output and a pointer field writes its address.
73///
74/// `Self` must also share `Component`'s alignment. A plain `#[repr(C)]` pair
75/// does; `#[repr(packed)]` satisfies everything above but drops to align 1, and
76/// the helpers build a `&[Complex<Component>]` over your slice.
77///
78/// Size and alignment are checked at run time. Padding and field order cannot
79/// be checked and are the implementor's promise.
80///
81/// # A padded type needs no impl, and gets none
82///
83/// `#[repr(C)] struct { re: i16, tag: u8 }` is size 4, align 2 — identical to
84/// `Complex<i16>` — so no run-time check can reject it. Absent an `unsafe impl`
85/// it does not compile:
86///
87/// ```compile_fail
88/// #[derive(Clone, Copy)]
89/// #[repr(C)]
90/// struct Padded {
91///     re: i16,
92///     tag: u8,
93/// }
94///
95/// fn encode<S: serde::Serializer>(data: &[Padded], s: S) {
96///     let _ = beve::complex::i16_array(data, s);
97/// }
98/// ```
99///
100/// # A same-width class is refused
101///
102/// Pairs of `i32` cannot reach the `f32` helper, which would otherwise emit the
103/// payload unchanged under an `f32` class tag:
104///
105/// ```compile_fail
106/// #[derive(Clone, Copy)]
107/// #[repr(C)]
108/// struct IntPair {
109///     re: i32,
110///     im: i32,
111/// }
112///
113/// // SAFETY: `#[repr(C)]` over two `i32`, real first.
114/// unsafe impl beve::ComplexElement for IntPair {
115///     type Component = i32;
116/// }
117///
118/// fn encode<S: serde::Serializer>(data: &[IntPair], s: S) {
119///     let _ = beve::complex::f32_array(data, s);
120/// }
121/// ```
122///
123/// The same type reaches its own class's helper:
124///
125/// ```
126/// #[derive(Clone, Copy)]
127/// #[repr(C)]
128/// struct IntPair {
129///     re: i32,
130///     im: i32,
131/// }
132///
133/// // SAFETY: `#[repr(C)]` over two `i32`, real first.
134/// unsafe impl beve::ComplexElement for IntPair {
135///     type Component = i32;
136/// }
137///
138/// fn encode<S: serde::Serializer>(data: &[IntPair], s: S) {
139///     let _ = beve::complex::i32_array(data, s);
140/// }
141/// ```
142pub unsafe trait ComplexElement {
143    /// The scalar type of the real and imaginary parts.
144    type Component;
145}
146
147/// One `ComplexElement` impl per scalar with a bulk complex-array helper, for
148/// [`Complex`] and (under `num-complex`) `num_complex::Complex`.
149///
150/// Enumerated, not blanket over `T: BeveTypedSlice`: that trait is public and
151/// safe, so a blanket impl would extend this crate's unsafe promise to any
152/// downstream type implementing it, padding and all. `f16`/`bf16` are absent
153/// because no `complex::f16_array` helper exists to reach.
154macro_rules! impl_complex_element {
155    ($($scalar:ty),* $(,)?) => {
156        $(
157            // SAFETY: `#[repr(C)]` over two `$scalar`, real first. Equal-width
158            // scalars leave no padding, every bit pattern of a fixed-width
159            // scalar is initialized, and the pair's alignment is the scalar's.
160            unsafe impl ComplexElement for Complex<$scalar> {
161                type Component = $scalar;
162            }
163
164            // SAFETY: `num_complex::Complex<T>` is `#[repr(C)]`, `re` then `im`
165            // — the same shape and alignment as this crate's `Complex<T>`.
166            #[cfg(feature = "num-complex")]
167            #[cfg_attr(docsrs, doc(cfg(feature = "num-complex")))]
168            unsafe impl ComplexElement for num_complex::Complex<$scalar> {
169                type Component = $scalar;
170            }
171
172            // The run-time asserts only catch a wrong `Component` where the
173            // widths differ. Pin the layout of both impls here instead.
174            const _: () = {
175                assert!(
176                    core::mem::size_of::<Complex<$scalar>>() == 2 * core::mem::size_of::<$scalar>()
177                );
178                assert!(
179                    core::mem::align_of::<Complex<$scalar>>() == core::mem::align_of::<$scalar>()
180                );
181            };
182            #[cfg(feature = "num-complex")]
183            const _: () = {
184                assert!(
185                    core::mem::size_of::<num_complex::Complex<$scalar>>()
186                        == 2 * core::mem::size_of::<$scalar>()
187                );
188                assert!(
189                    core::mem::align_of::<num_complex::Complex<$scalar>>()
190                        == core::mem::align_of::<$scalar>()
191                );
192            };
193        )*
194    };
195}
196
197impl_complex_element!(f32, f64, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
198
199/// Generates `Serialize` for `Complex<$scalar>` using a single `NT_COMPLEX` newtype.
200/// Payload layout: `[class: u8, byte_code: u8, re_le_bytes..., im_le_bytes...]`
201macro_rules! impl_complex_serialize {
202    ($scalar:ty) => {
203        impl Serialize for Complex<$scalar> {
204            fn serialize<S: serde::Serializer>(
205                &self,
206                s: S,
207            ) -> core::result::Result<S::Ok, S::Error> {
208                const ELEM: usize = core::mem::size_of::<$scalar>();
209                const TOTAL: usize = 2 + ELEM * 2;
210                let mut bytes = [0u8; TOTAL];
211                bytes[0] = <$scalar as BeveTypedSlice>::CLASS;
212                bytes[1] = <$scalar as BeveTypedSlice>::BYTE_CODE;
213                bytes[2..2 + ELEM].copy_from_slice(&self.re.to_le_bytes());
214                bytes[2 + ELEM..TOTAL].copy_from_slice(&self.im.to_le_bytes());
215                s.serialize_newtype_struct(NT_COMPLEX, &RawBytes(&bytes))
216            }
217        }
218    };
219}
220
221impl_complex_serialize!(f16);
222impl_complex_serialize!(bf16);
223impl_complex_serialize!(f32);
224impl_complex_serialize!(f64);
225impl_complex_serialize!(i8);
226impl_complex_serialize!(i16);
227impl_complex_serialize!(i32);
228impl_complex_serialize!(i64);
229impl_complex_serialize!(i128);
230impl_complex_serialize!(u8);
231impl_complex_serialize!(u16);
232impl_complex_serialize!(u32);
233impl_complex_serialize!(u64);
234impl_complex_serialize!(u128);
235
236impl<'de, T> serde::Deserialize<'de> for Complex<T>
237where
238    T: serde::Deserialize<'de>,
239{
240    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> core::result::Result<Self, D::Error> {
241        struct V<T>(core::marker::PhantomData<T>);
242        impl<'de, T> serde::de::Visitor<'de> for V<T>
243        where
244            T: serde::Deserialize<'de>,
245        {
246            type Value = Complex<T>;
247            fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
248                write!(f, "complex number as [re, im]")
249            }
250            fn visit_seq<A: serde::de::SeqAccess<'de>>(
251                self,
252                mut a: A,
253            ) -> core::result::Result<Self::Value, A::Error> {
254                let re: T = a
255                    .next_element()?
256                    .ok_or_else(|| serde::de::Error::custom("missing real"))?;
257                let im: T = a
258                    .next_element()?
259                    .ok_or_else(|| serde::de::Error::custom("missing imag"))?;
260                Ok(Complex { re, im })
261            }
262        }
263        d.deserialize_seq(V(core::marker::PhantomData))
264    }
265}
266
267/// A contiguous slice of complex values, serialized as a BEVE complex array
268/// with a single bulk write of the borrowed payload — the complex counterpart
269/// of [`TypedSlice`].
270///
271/// Both families of complex-array helpers route through this type, so a field
272/// using either gets the bulk path with no further opt-in: the [`complex`]
273/// `serialize_with` helpers, and the encode half of the
274/// [`complex_array`](crate::complex_array) `serde(with)` helpers.
275///
276/// The encoded bytes are identical to serializing the same values one at a
277/// time, so this is a throughput change and not a format change; the
278/// `complex_slice_bulk_matches_element_wise` tests pin that.
279///
280/// An **empty** slice keeps the element-wise encoding (a generic empty array,
281/// not a zero-length complex array), which is what this wrapper has always
282/// written. That differs from [`TypedSlice`], whose empty form is a typed empty
283/// array, and from [`crate::to_writer_complex_slice`], whose empty form is a
284/// zero-length complex array. All three decode back to an empty `Vec`, but the
285/// distinction is visible to consumers that read the element type off the
286/// header — notably the MATLAB export, where a typed empty array becomes a cell
287/// and a generic one becomes `[]`.
288pub struct ComplexSlice<'a, T>(pub &'a [Complex<T>]);
289
290/// Shared `Serialize` body for every `ComplexSlice<T>`: hand the interleaved
291/// `(re, im)` payload to the serializer as borrowed bytes tagged by `name`, so
292/// beve emits it with one `write_all` instead of a `serialize_element` per
293/// sample. The tagged form is beve-private, which costs nothing in portability
294/// that this wrapper had to begin with: `Complex<T>`'s own `Serialize` already
295/// emits a beve-private newtype, so the element-wise path was never portable to
296/// another binary format either.
297///
298/// Three cases keep the element-wise path, and each is load-bearing:
299/// human-readable formats (JSON and friends must still see a sequence),
300/// big-endian targets (the in-memory bytes are not little-endian), and the
301/// empty slice (see [`ComplexSlice`]'s note on empty encoding).
302#[inline]
303pub(crate) fn serialize_complex_slice<S, T>(
304    slice: &[Complex<T>],
305    name: &'static str,
306    s: S,
307) -> core::result::Result<S::Ok, S::Error>
308where
309    S: serde::Serializer,
310    // Not decoration: `BeveTypedSlice` is what makes the reinterpret below
311    // sound, by restricting `T` to the fixed-width scalars whose every bit
312    // pattern is valid. Without it the bound would be `Complex<T>: Serialize`,
313    // which a padded or niche-carrying `T` could satisfy.
314    T: BeveTypedSlice,
315    Complex<T>: Serialize,
316{
317    // `cfg!` rather than `#[cfg]` so the bulk arm is type-checked on every
318    // target; it folds to a constant, so big-endian still compiles it out.
319    if cfg!(target_endian = "little") && !s.is_human_readable() && !slice.is_empty() {
320        // SAFETY: `Complex<T>` is `#[repr(C)]` over two `T`, and `T: BeveTypedSlice`
321        // is a fixed-width scalar with every bit pattern valid, so there is no
322        // padding and the slice's bytes are exactly the interleaved `(re, im)`
323        // payload. Mirrors `fast::to_writer_complex_slice`. The borrowed view is
324        // only read inside `serialize_newtype_struct`, during which `slice` stays
325        // alive.
326        let payload: &[u8] = unsafe {
327            core::slice::from_raw_parts(slice.as_ptr() as *const u8, core::mem::size_of_val(slice))
328        };
329        return s.serialize_newtype_struct(name, &RawBytes(payload));
330    }
331    let mut seq = s.serialize_seq(Some(slice.len()))?;
332    for c in slice {
333        ser::SerializeSeq::serialize_element(&mut seq, c)?;
334    }
335    ser::SerializeSeq::end(seq)
336}
337
338/// Generates, per scalar type: the complex-array newtype-name constant, the
339/// `Serialize for ComplexSlice<'_, T>` impl, and an arm of the shared
340/// [`complex_array_tag`] lookup — the same shape as
341/// [`impl_typed_slice_serialize`], and for the same reason: both serializers
342/// consult one table instead of hand-maintaining a per-type dispatch.
343macro_rules! impl_complex_slice_serialize {
344    ($( ($scalar:ty, $nt_const:ident, $nt_name:literal) ),* $(,)?) => {
345        $(
346            pub(crate) const $nt_const: &str = $nt_name;
347
348            impl<'a> Serialize for ComplexSlice<'a, $scalar> {
349                fn serialize<S: serde::Serializer>(
350                    &self,
351                    s: S,
352                ) -> core::result::Result<S::Ok, S::Error> {
353                    serialize_complex_slice(self.0, $nt_const, s)
354                }
355            }
356        )*
357
358        /// Map a beve complex-array newtype name to its
359        /// `(class, byte_code, scalar_size)`, where `scalar_size` is the width of
360        /// one *component* — the same shape [`typed_array_tag`] returns, and the
361        /// same units, so the two cannot be confused for each other.
362        ///
363        /// This is the one table for the complex markers, read by both directions:
364        /// [`ComplexSlice`] and the [`crate::complex_array`] helpers set these
365        /// names on the way out, and both deserializers match them on the way in.
366        /// A writer needing the width of a whole complex VALUE doubles it through
367        /// `ser::complex_elem_bytes` rather than carrying a second, differently
368        /// scaled copy of the width here.
369        pub(crate) fn complex_array_tag(name: &str) -> Option<(u8, u8, usize)> {
370            match name {
371                $(
372                    $nt_const => Some((
373                        <$scalar as BeveTypedSlice>::CLASS,
374                        <$scalar as BeveTypedSlice>::BYTE_CODE,
375                        <$scalar as BeveTypedSlice>::ELEM_SIZE,
376                    )),
377                )*
378                _ => None,
379            }
380        }
381    };
382}
383
384impl_complex_slice_serialize! {
385    (f16,  NT_COMPLEX_ARRAY_F16,  "__beve_complex_array_f16"),
386    (bf16, NT_COMPLEX_ARRAY_BF16, "__beve_complex_array_bf16"),
387    (f32,  NT_COMPLEX_ARRAY_F32,  "__beve_complex_array_f32"),
388    (f64,  NT_COMPLEX_ARRAY_F64,  "__beve_complex_array_f64"),
389    (i8,   NT_COMPLEX_ARRAY_I8,   "__beve_complex_array_i8"),
390    (i16,  NT_COMPLEX_ARRAY_I16,  "__beve_complex_array_i16"),
391    (i32,  NT_COMPLEX_ARRAY_I32,  "__beve_complex_array_i32"),
392    (i64,  NT_COMPLEX_ARRAY_I64,  "__beve_complex_array_i64"),
393    (i128, NT_COMPLEX_ARRAY_I128, "__beve_complex_array_i128"),
394    (u8,   NT_COMPLEX_ARRAY_U8,   "__beve_complex_array_u8"),
395    (u16,  NT_COMPLEX_ARRAY_U16,  "__beve_complex_array_u16"),
396    (u32,  NT_COMPLEX_ARRAY_U32,  "__beve_complex_array_u32"),
397    (u64,  NT_COMPLEX_ARRAY_U64,  "__beve_complex_array_u64"),
398    (u128, NT_COMPLEX_ARRAY_U128, "__beve_complex_array_u128"),
399}
400
401// -------- Typed numeric slices (opt-in zero-copy serde bulk path) --------
402
403/// An opt-in wrapper that serializes a contiguous numeric slice `&[T]` as a BEVE
404/// typed array via a single bulk write, rather than element-by-element.
405///
406/// serde delivers sequence elements one at a time and never exposes the backing
407/// slice, so a plain `Vec<T>` / `&[T]` field cannot be bulk-written
408/// automatically; like `serde_bytes` for `&[u8]`, this wrapper is the opt-in.
409/// Use it as a derived-struct field to get the bulk path through
410/// [`crate::to_writer_streaming`] / [`crate::to_vec`]:
411///
412/// ```rust
413/// use serde::Serialize;
414///
415/// #[derive(Serialize)]
416/// struct Frame<'a> {
417///     samples: beve::TypedSlice<'a, f64>,
418/// }
419///
420/// let data = vec![1.0f64, 2.0, 3.0];
421/// let mut buf = Vec::new();
422/// beve::to_writer_streaming(&mut buf, &Frame { samples: beve::TypedSlice(&data) }).unwrap();
423/// ```
424///
425/// On little-endian targets the payload is handed to the serializer as borrowed
426/// bytes and written with one `write_all` (no copy, no allocation); on big-endian
427/// targets a non-empty slice falls back to the per-element sequence path. For a
428/// non-empty slice the encoded bytes are identical to serializing the equivalent
429/// `Vec<T>` on either target. For an *empty* slice they differ, on every target:
430/// `TypedSlice` still emits a typed array of the element's type, whereas a bare
431/// empty `Vec<T>` has no element from which to detect the type and encodes as a
432/// generic empty array. Both decode back to an empty `Vec<T>`.
433pub struct TypedSlice<'a, T>(pub &'a [T]);
434
435/// Shared `Serialize` body for every `TypedSlice<T>`: on little-endian, borrow the
436/// slice as bytes and tag it by `name`; on big-endian, fall back to the
437/// per-element sequence path (except an empty slice, which has no bytes to convert
438/// and so takes the little-endian typed-array path on every target).
439#[inline]
440pub(crate) fn serialize_typed_slice<S, T>(
441    slice: &[T],
442    name: &'static str,
443    s: S,
444) -> core::result::Result<S::Ok, S::Error>
445where
446    S: serde::Serializer,
447    T: Serialize,
448{
449    // Human-readable formats (JSON, ...) get the portable element-wise sequence,
450    // so a field using these helpers still round-trips through them. The bulk
451    // newtype/raw-bytes form is meaningful only to beve (and other binary
452    // formats that don't special-case the marker would mis-read it).
453    if s.is_human_readable() {
454        use serde::ser::SerializeSeq;
455        let mut seq = s.serialize_seq(Some(slice.len()))?;
456        for v in slice {
457            seq.serialize_element(v)?;
458        }
459        return seq.end();
460    }
461    #[cfg(target_endian = "little")]
462    {
463        // Sound: `BeveTypedSlice` types are fixed-width no-padding scalars with
464        // every bit pattern valid; this mirrors the `fast` module's
465        // reinterpret-as-bytes. The borrowed view is only read inside
466        // `serialize_newtype_struct`, during which `slice` stays alive.
467        let payload: &[u8] = unsafe {
468            core::slice::from_raw_parts(slice.as_ptr() as *const u8, core::mem::size_of_val(slice))
469        };
470        s.serialize_newtype_struct(name, &RawBytes(payload))
471    }
472    #[cfg(not(target_endian = "little"))]
473    {
474        // On big-endian the in-memory bytes are not little-endian, so a non-empty
475        // slice is converted element-by-element through the sequence path. An
476        // *empty* slice has no payload bytes to convert, so it takes the same
477        // typed-array newtype path as little-endian, keeping `TypedSlice`
478        // byte-identical to `to_writer_typed_slice` (a typed empty array, never a
479        // generic one) on every target.
480        if slice.is_empty() {
481            return s.serialize_newtype_struct(name, &RawBytes(&[]));
482        }
483        use serde::ser::SerializeSeq;
484        let mut seq = s.serialize_seq(Some(slice.len()))?;
485        for v in slice {
486            seq.serialize_element(v)?;
487        }
488        seq.end()
489    }
490}
491
492/// Generates, per scalar type: the typed-array newtype-name constant, the
493/// `Serialize for TypedSlice<'_, T>` impl, and an arm of the shared
494/// [`typed_array_tag`] lookup. The lookup is the single source of truth that both
495/// serializers consult, so neither hand-maintains a per-type dispatch table.
496macro_rules! impl_typed_slice_serialize {
497    ($( ($scalar:ty, $nt_const:ident, $nt_name:literal) ),* $(,)?) => {
498        $(
499            pub(crate) const $nt_const: &str = $nt_name;
500
501            impl<'a> Serialize for TypedSlice<'a, $scalar> {
502                fn serialize<S: serde::Serializer>(
503                    &self,
504                    s: S,
505                ) -> core::result::Result<S::Ok, S::Error> {
506                    serialize_typed_slice(self.0, $nt_const, s)
507                }
508            }
509        )*
510
511        /// Map a beve typed-array newtype name (the tag [`TypedSlice`] sets via
512        /// `serialize_newtype_struct`) to its `(class, byte_code, elem_size)`.
513        ///
514        /// `elem_size` is the true element width, which is **not** always
515        /// `1 << byte_code` (e.g. `bf16` uses `byte_code` 0 but is 2 bytes), so
516        /// the writing sink divides the borrowed payload length by it to recover
517        /// the element count for the SIZE prefix.
518        pub(crate) fn typed_array_tag(name: &str) -> Option<(u8, u8, usize)> {
519            match name {
520                $(
521                    $nt_const => Some((
522                        <$scalar as BeveTypedSlice>::CLASS,
523                        <$scalar as BeveTypedSlice>::BYTE_CODE,
524                        <$scalar as BeveTypedSlice>::ELEM_SIZE,
525                    )),
526                )*
527                _ => None,
528            }
529        }
530    };
531}
532
533impl_typed_slice_serialize! {
534    (i8,   NT_TYPED_ARRAY_I8,   "__beve_typed_array_i8"),
535    (i16,  NT_TYPED_ARRAY_I16,  "__beve_typed_array_i16"),
536    (i32,  NT_TYPED_ARRAY_I32,  "__beve_typed_array_i32"),
537    (i64,  NT_TYPED_ARRAY_I64,  "__beve_typed_array_i64"),
538    (i128, NT_TYPED_ARRAY_I128, "__beve_typed_array_i128"),
539    (u8,   NT_TYPED_ARRAY_U8,   "__beve_typed_array_u8"),
540    (u16,  NT_TYPED_ARRAY_U16,  "__beve_typed_array_u16"),
541    (u32,  NT_TYPED_ARRAY_U32,  "__beve_typed_array_u32"),
542    (u64,  NT_TYPED_ARRAY_U64,  "__beve_typed_array_u64"),
543    (u128, NT_TYPED_ARRAY_U128, "__beve_typed_array_u128"),
544    (f32,  NT_TYPED_ARRAY_F32,  "__beve_typed_array_f32"),
545    (f64,  NT_TYPED_ARRAY_F64,  "__beve_typed_array_f64"),
546    (f16,  NT_TYPED_ARRAY_F16,  "__beve_typed_array_f16"),
547    (bf16, NT_TYPED_ARRAY_BF16, "__beve_typed_array_bf16"),
548}
549
550/// Serde `serialize_with` helpers for foreign complex types (e.g. `num_complex::Complex`)
551/// that implement [`ComplexElement`] (two contiguous `Component` fields: re then im).
552///
553/// These are only needed for foreign types. `beve::Complex<T>` serializes correctly
554/// without any annotation.
555///
556/// Available helpers: `f32_array`, `f64_array`, `i8_array`, `i16_array`, `i32_array`,
557/// `i64_array`, `i128_array`, `u8_array`, `u16_array`, `u32_array`, `u64_array`, `u128_array`.
558///
559/// Each takes a [`ComplexElement`] whose `Component` is the named scalar. A
560/// foreign type needs an `unsafe impl` of that trait, which the `num-complex`
561/// feature provides for `num_complex::Complex<T>`.
562///
563/// Encode only. To bulk-*decode* the same field as well, annotate it with
564/// [`crate::complex_array`]'s `serde(with)` module instead of this one.
565///
566/// # Example
567/// ```ignore
568/// #[serde(serialize_with = "beve::complex::f32_array")]
569/// pub buffer: Vec<num_complex::Complex<f32>>,
570/// ```
571pub mod complex {
572    use super::*;
573
574    macro_rules! complex_array_fn {
575        ($name:ident, $scalar:ty) => {
576            #[doc = concat!(
577                                        "Serialize a slice of complex `", stringify!($scalar),
578                                        "` as one BEVE complex array."
579                                    )]
580            ///
581            /// Takes any [`ComplexElement`] whose `Component` is this scalar —
582            /// [`Complex`], `num_complex::Complex` under the `num-complex`
583            /// feature, or your own type. The component is part of the bound,
584            /// so a same-width class cannot slip through.
585            pub fn $name<S: serde::Serializer, T>(
586                data: &[T],
587                serializer: S,
588            ) -> core::result::Result<S::Ok, S::Error>
589            where
590                T: ComplexElement<Component = $scalar>,
591            {
592                // Backstops against a wrong `unsafe impl`. Alignment matters
593                // because `#[repr(packed)]` meets the rest of the contract yet
594                // drops to align 1, and the reference below must be aligned
595                // even when `data` is empty (`as_ptr` is then a dangling
596                // pointer aligned only for `T`).
597                assert_eq!(
598                    core::mem::size_of::<T>(),
599                    core::mem::size_of::<Complex<$scalar>>(),
600                    concat!(
601                        "beve::complex::",
602                        stringify!($name),
603                        ": ComplexElement impl has the wrong element size",
604                    )
605                );
606                assert_eq!(
607                    core::mem::align_of::<T>(),
608                    core::mem::align_of::<Complex<$scalar>>(),
609                    concat!(
610                        "beve::complex::",
611                        stringify!($name),
612                        ": ComplexElement impl has the wrong element alignment",
613                    )
614                );
615                // SAFETY: `T: ComplexElement<Component = $scalar>` promises `T`
616                // is two initialized `$scalar`, real first — the layout of
617                // `Complex<$scalar>`, whose width and alignment the asserts
618                // above confirm. The lifetime is the caller's `data`.
619                let slice: &[Complex<$scalar>] = unsafe {
620                    core::slice::from_raw_parts(
621                        data.as_ptr() as *const Complex<$scalar>,
622                        data.len(),
623                    )
624                };
625                ComplexSlice(slice).serialize(serializer)
626            }
627        };
628    }
629
630    complex_array_fn!(f32_array, f32);
631    complex_array_fn!(f64_array, f64);
632    complex_array_fn!(i8_array, i8);
633    complex_array_fn!(i16_array, i16);
634    complex_array_fn!(i32_array, i32);
635    complex_array_fn!(i64_array, i64);
636    complex_array_fn!(i128_array, i128);
637    complex_array_fn!(u8_array, u8);
638    complex_array_fn!(u16_array, u16);
639    complex_array_fn!(u32_array, u32);
640    complex_array_fn!(u64_array, u64);
641    complex_array_fn!(u128_array, u128);
642}
643
644#[derive(Debug, Clone, Copy, PartialEq, Eq)]
645pub enum MatrixLayout {
646    Right,
647    Left,
648}
649
650impl Serialize for MatrixLayout {
651    fn serialize<S: serde::Serializer>(&self, s: S) -> core::result::Result<S::Ok, S::Error> {
652        let layout = match self {
653            MatrixLayout::Right => "layout_right",
654            MatrixLayout::Left => "layout_left",
655        };
656        s.serialize_str(layout)
657    }
658}
659
660impl<'de> Deserialize<'de> for MatrixLayout {
661    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> core::result::Result<Self, D::Error> {
662        struct V;
663        impl<'de> serde::de::Visitor<'de> for V {
664            type Value = MatrixLayout;
665            fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
666                write!(f, "matrix layout string")
667            }
668            fn visit_str<E: serde::de::Error>(
669                self,
670                v: &str,
671            ) -> core::result::Result<Self::Value, E> {
672                match v {
673                    "layout_right" | "row_major" | "right" => Ok(MatrixLayout::Right),
674                    "layout_left" | "column_major" | "left" => Ok(MatrixLayout::Left),
675                    _ => Err(E::custom("invalid matrix layout")),
676                }
677            }
678        }
679        d.deserialize_str(V)
680    }
681}
682
683pub struct Matrix<'a, T> {
684    pub layout: MatrixLayout,
685    pub extents: &'a [usize],
686    pub data: &'a [T],
687}
688
689#[derive(Debug, Clone, PartialEq)]
690pub struct MatrixOwned<T> {
691    pub layout: MatrixLayout,
692    pub extents: Vec<usize>,
693    pub data: Vec<T>,
694}
695
696impl<T> MatrixOwned<T> {
697    pub fn as_borrowed(&self) -> Matrix<'_, T> {
698        Matrix {
699            layout: self.layout,
700            extents: &self.extents,
701            data: &self.data,
702        }
703    }
704}
705
706impl<'a, T: Clone> From<Matrix<'a, T>> for MatrixOwned<T> {
707    fn from(value: Matrix<'a, T>) -> Self {
708        Self {
709            layout: value.layout,
710            extents: value.extents.to_vec(),
711            data: value.data.to_vec(),
712        }
713    }
714}
715
716impl<'de, T> Deserialize<'de> for MatrixOwned<T>
717where
718    T: Deserialize<'de>,
719{
720    fn deserialize<D: serde::Deserializer<'de>>(
721        deserializer: D,
722    ) -> core::result::Result<Self, D::Error> {
723        #[derive(Deserialize)]
724        struct MatrixRepr<T> {
725            layout: MatrixLayout,
726            extents: Vec<usize>,
727            value: Vec<T>,
728        }
729
730        let repr = MatrixRepr::deserialize(deserializer)?;
731        validate_matrix_shape(&repr.extents, repr.value.len()).map_err(de::Error::custom)?;
732        Ok(Self {
733            layout: repr.layout,
734            extents: repr.extents,
735            data: repr.value,
736        })
737    }
738}
739
740impl<T> Serialize for MatrixOwned<T>
741where
742    T: Serialize + 'static,
743{
744    fn serialize<S: serde::Serializer>(&self, s: S) -> core::result::Result<S::Ok, S::Error> {
745        self.as_borrowed().serialize(s)
746    }
747}
748
749impl<'a, T> Serialize for Matrix<'a, T>
750where
751    T: Serialize + 'static,
752{
753    fn serialize<S: serde::Serializer>(&self, s: S) -> core::result::Result<S::Ok, S::Error> {
754        if let Some(bytes) = try_encode_matrix_extension(self.layout, self.extents, self.data)
755            .map_err(ser::Error::custom)?
756        {
757            return s.serialize_newtype_struct(NT_RAW_VALUE, &RawBytes(&bytes));
758        }
759        serialize_matrix_as_map(self.layout, self.extents, self.data, s)
760    }
761}
762
763fn serialize_matrix_as_map<S, T: Serialize>(
764    layout: MatrixLayout,
765    extents: &[usize],
766    data: &[T],
767    s: S,
768) -> core::result::Result<S::Ok, S::Error>
769where
770    S: serde::Serializer,
771{
772    let mut map = s.serialize_map(Some(3))?;
773    ser::SerializeMap::serialize_entry(&mut map, "layout", &layout)?;
774    ser::SerializeMap::serialize_entry(&mut map, "extents", &extents)?;
775    ser::SerializeMap::serialize_entry(&mut map, "value", &data)?;
776    ser::SerializeMap::end(map)
777}
778
779fn validate_matrix_shape(extents: &[usize], data_len: usize) -> core::result::Result<(), String> {
780    if extents.is_empty() {
781        return Err("matrix extents cannot be empty".into());
782    }
783    if extents.contains(&0) {
784        return Err("matrix dimensions cannot be zero".into());
785    }
786    let expected = extents.iter().try_fold(1usize, |acc, &extent| {
787        acc.checked_mul(extent)
788            .ok_or_else(|| "matrix extents overflow".to_string())
789    })?;
790    if expected != data_len {
791        return Err(format!(
792            "matrix data length {} does not match product of extents {}",
793            data_len, expected
794        ));
795    }
796    Ok(())
797}
798
799fn write_unsigned_typed_header(out: &mut Vec<u8>, byte_code: u8, len: usize) {
800    let header = ((byte_code & 0b111) << 5) | ((ARRAY_UNSIGNED & 0b11) << 3) | TYPE_TYPED_ARRAY;
801    out.push(header);
802    write_size(len as u64, out);
803}
804
805fn write_matrix_extents(out: &mut Vec<u8>, extents: &[usize]) {
806    let max_extent = extents.iter().copied().max().unwrap_or(0);
807    if u8::try_from(max_extent).is_ok() {
808        write_unsigned_typed_header(out, 0, extents.len());
809        for &extent in extents {
810            out.push(extent as u8);
811        }
812    } else if u16::try_from(max_extent).is_ok() {
813        write_unsigned_typed_header(out, 1, extents.len());
814        for &extent in extents {
815            out.extend_from_slice(&(extent as u16).to_le_bytes());
816        }
817    } else if u32::try_from(max_extent).is_ok() {
818        write_unsigned_typed_header(out, 2, extents.len());
819        for &extent in extents {
820            out.extend_from_slice(&(extent as u32).to_le_bytes());
821        }
822    } else {
823        write_unsigned_typed_header(out, 3, extents.len());
824        for &extent in extents {
825            out.extend_from_slice(&(extent as u64).to_le_bytes());
826        }
827    }
828}
829
830fn try_encode_matrix_extension<T: Serialize + 'static>(
831    layout: MatrixLayout,
832    extents: &[usize],
833    data: &[T],
834) -> core::result::Result<Option<Vec<u8>>, String> {
835    validate_matrix_shape(extents, data.len())?;
836
837    let mut out = Vec::new();
838    out.push(make_extension_header(EXT_MATRICES));
839    out.push(match layout {
840        MatrixLayout::Right => 0u8,
841        MatrixLayout::Left => 1u8,
842    });
843    write_matrix_extents(&mut out, extents);
844
845    macro_rules! write_typed_value {
846        ($ty:ty) => {
847            if TypeId::of::<T>() == TypeId::of::<$ty>() {
848                let typed = unsafe { &*(data as *const [T] as *const [$ty]) };
849                write_typed_slice(&mut out, typed);
850                return Ok(Some(out));
851            }
852        };
853    }
854
855    write_typed_value!(f64);
856    write_typed_value!(f32);
857    write_typed_value!(f16);
858    write_typed_value!(bf16);
859    write_typed_value!(i8);
860    write_typed_value!(i16);
861    write_typed_value!(i32);
862    write_typed_value!(i64);
863    write_typed_value!(i128);
864    write_typed_value!(u8);
865    write_typed_value!(u16);
866    write_typed_value!(u32);
867    write_typed_value!(u64);
868    write_typed_value!(u128);
869
870    if TypeId::of::<T>() == TypeId::of::<bool>() {
871        let typed = unsafe { &*(data as *const [T] as *const [bool]) };
872        write_bool_slice(&mut out, typed);
873        return Ok(Some(out));
874    }
875
876    macro_rules! write_complex_value {
877        ($scalar:ty) => {
878            if TypeId::of::<T>() == TypeId::of::<Complex<$scalar>>() {
879                let typed = unsafe { &*(data as *const [T] as *const [Complex<$scalar>]) };
880                out.extend_from_slice(&to_vec_complex_slice(typed));
881                return Ok(Some(out));
882            }
883        };
884    }
885
886    write_complex_value!(f32);
887    write_complex_value!(f64);
888    write_complex_value!(i8);
889    write_complex_value!(i16);
890    write_complex_value!(i32);
891    write_complex_value!(i64);
892    write_complex_value!(i128);
893    write_complex_value!(u8);
894    write_complex_value!(u16);
895    write_complex_value!(u32);
896    write_complex_value!(u64);
897    write_complex_value!(u128);
898
899    Ok(None)
900}
901
902pub fn decode_matrix_slice<T: serde::de::DeserializeOwned>(
903    bytes: &[u8],
904    mode: MatrixDecodeMode,
905) -> crate::Result<DecodedMatrix<T>> {
906    match mode {
907        MatrixDecodeMode::Materialized => {
908            let matrix: MatrixOwned<T> = from_slice(bytes)?;
909            Ok(DecodedMatrix::Materialized(matrix))
910        }
911        MatrixDecodeMode::Raw => {
912            let matrix: RawMatrix = from_slice(bytes)?;
913            Ok(DecodedMatrix::Raw(matrix))
914        }
915    }
916}