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}