Skip to main content

flatr_core/
serialize.rs

1//! # flatr/flatr_core/src/serialize.rs
2//!
3//! Traits and implementations for writing Rust values into a [`Buffer`].
4//!
5//! ## Two-trait design
6//!
7//! - [`Serialize`] — for typed values (scalars, strings, arrays, table views).
8//!   Every serializable type carries a `const MODE: DataType` that tells the
9//!   parent table whether to write the value inline or write a 32-bit forward
10//!   offset to it.
11//!
12//! - [`SerializeBytes`] — for raw byte slices.  Used internally to emit vtable
13//!   bytes and other untyped data that does not participate in the offset/inline
14//!   distinction.
15//!
16//! ## Write direction
17//!
18//! All data is written from the **high end** of the buffer toward the low end.
19//! After writing a value, `buffer.slot()` is a stable "address" for that value
20//! that survives subsequent `grow()` calls.  The `write_to_unchecked` variants
21//! skip the capacity check and must only be called when `ensure_capacity` has
22//! already been called by the caller (e.g. inside a parent table's pass 1/2).
23//!
24//! ## Inline vs Offset
25//!
26//! | `MODE`   | What is written at the field position in the table object |
27//! |----------|-----------------------------------------------------------|
28//! | `Inline` | The value's bytes directly (scalars, `#[repr(C)]` structs) |
29//! | `Offset` | A u32 forward offset to the value (strings, arrays, tables)|
30//!
31//! Forward offset = `abs_pos_of_value - abs_pos_of_offset_field`, always > 0.
32
33use crate::{DataType, buffer::Buffer, read::{ListView, ReadAt}};
34use std::mem::{size_of, align_of};
35
36// ── Serialize ─────────────────────────────────────────────────────────────────
37
38/// Trait for values that can be written into a [`Buffer`].
39///
40/// The proc-macro generates implementations for every `#[derive(Table)]` and
41/// `#[derive(Flat)]` struct; built-in implementations cover all primitive
42/// scalars, `String`, `&str`, `Vec<T>`, `&[T]`, and `ListView<T>`.
43pub trait Serialize: Sized {
44    const SIZE: usize = size_of::<Self>(); // Unions override as 5, Non scalars override as 4
45    const ALIGN: usize = align_of::<Self>(); // Non scalars override as 4
46    const ALIGNR: usize = Self::ALIGN -1;
47    const ALIGN_MASK: usize = !Self::ALIGNR;
48
49    /// Whether this value is written inline at the field position (`Inline`)
50    /// or whether a 32-bit forward offset to the value is written instead
51    /// (`Offset`).  This constant is inspected by parent arrays and tables
52    /// at compile time to choose the correct write strategy.
53    const MODE: DataType;
54
55    /// Upper bound on the number of bytes this value and its alignment padding
56    /// will consume.  Used by `write_to` and parent tables to call
57    /// `ensure_capacity` before entering the unchecked fast path.
58    fn size_hint(&self) -> usize;
59
60    /// Write this value into `buffer`, calling `ensure_capacity` first.
61    /// Returns the slot of the outermost written unit (length prefix for arrays,
62    /// value start for scalars).
63    fn write_to<B: Buffer>(&self, buffer: &mut B) -> usize;
64
65    /// Write this value into `buffer` **without** checking capacity first.
66    ///
67    /// # Safety
68    ///
69    /// The caller must guarantee that `buffer.head() > size_hint()` before
70    /// calling this method.  Violating this will cause a panic on the
71    /// slice indexing inside, or — if `debug_assert` is disabled — silent
72    /// memory corruption.
73    fn write_to_unchecked<B: Buffer>(&self, buffer: &mut B) -> usize;
74
75    /// Return `true` if this value is the default/zero/absent state.
76    ///
77    /// The proc-macro uses this to skip writing absent fields to the table
78    /// object and leave their vtable entry as 0 (absent marker).  Scalars
79    /// are absent when equal to 0; strings/arrays when empty; tables when
80    /// all their own fields are absent.
81    fn is_absent(&self) -> bool;
82
83
84    // special access method for enum/unions
85    #[inline(always)]
86    fn tag(&self) -> u8 {
87        0
88    }
89}
90
91/// Trait for writing raw byte sequences without the inline/offset distinction.
92///
93/// Used for vtable bytes (emitted by `share_vtable`) and similar cases where
94/// the data is already in its final wire form.
95pub trait SerializeBytes {
96    fn size_hint(&self) -> usize;
97    fn write_bytes_to<B: Buffer>(&self, buffer: &mut B) -> usize;
98    fn write_bytes_to_unchecked<B: Buffer>(&self, buffer: &mut B) -> usize;
99}
100
101// ── Dense bytes (&[u8]) ───────────────────────────────────────────────────────
102
103/// `SerializeBytes` for raw byte slices — no alignment, no length prefix,
104/// bytes are written verbatim from the high end of the buffer.
105impl SerializeBytes for &[u8] {
106    #[inline(always)]
107    fn size_hint(&self) -> usize { self.len() }
108    #[inline]
109    fn write_bytes_to<B: Buffer>(&self, buffer: &mut B) -> usize {
110        buffer.ensure_capacity(self.len());
111        self.write_bytes_to_unchecked(buffer)
112    }
113    #[inline]
114    fn write_bytes_to_unchecked<B: Buffer>(&self, buffer: &mut B) -> usize {
115        let len = self.len();
116        *buffer.head_mut() -= len;
117        let head = buffer.head();
118        buffer.buffer_mut()[head..head + len].copy_from_slice(self);
119        buffer.slot()
120    }
121}
122
123// ── Scalars ───────────────────────────────────────────────────────────────────
124
125/// Generates `Serialize` for all primitive integer and float types.
126///
127/// The layout for a scalar `T`:
128/// ```text
129/// head is decremented by size_of::<T>(), then masked to align_of::<T>()
130/// T's little-endian bytes are written at the resulting head position
131/// ```
132macro_rules! impl_serialize_scalar {
133    ($($t:ty),*) => {$(
134        impl Serialize for $t {
135            const MODE: DataType = DataType::Inline;
136            /// `size_of + align_of - 1` — worst-case bytes including alignment padding.
137            #[inline(always)]
138            fn size_hint(&self) -> usize { size_of::<Self>() + align_of::<Self>() - 1 }
139            #[inline]
140            fn write_to<B: Buffer>(&self, buffer: &mut B) -> usize {
141                buffer.ensure_capacity(size_of::<Self>() + align_of::<Self>() - 1);
142                self.write_to_unchecked(buffer)
143            }
144            #[inline]
145            fn write_to_unchecked<B: Buffer>(&self, buffer: &mut B) -> usize {
146                let size = size_of::<Self>();
147                let mask = align_of::<Self>() - 1;
148                *buffer.head_mut() -= size;
149                *buffer.head_mut() &= !mask;
150                let head = buffer.head();
151                buffer.buffer_mut()[head..head + size]
152                    .copy_from_slice(&self.to_le_bytes());
153                buffer.slot()
154            }
155            #[inline(always)]
156            fn is_absent(&self) -> bool { *self == (0 as $t) }
157        }
158    )*};
159}
160impl Serialize for u8 {
161    const MODE: DataType = DataType::Inline;
162    /// `size_of + align_of - 1` — worst-case bytes including alignment padding.
163    #[inline(always)]
164    fn size_hint(&self) -> usize { 1 }
165    #[inline]
166    fn write_to<B: Buffer>(&self, buffer: &mut B) -> usize {
167        buffer.ensure_capacity(1);
168        self.write_to_unchecked(buffer)
169    }
170    #[inline]
171    fn write_to_unchecked<B: Buffer>(&self, buffer: &mut B) -> usize {
172        let size = 1;
173        *buffer.head_mut() -= size;
174        let head = buffer.head();
175        buffer.buffer_mut()[head]= *self;
176        buffer.slot()
177    }
178    #[inline(always)]
179    fn is_absent(&self) -> bool { *self == 0 }
180}
181
182impl_serialize_scalar!(u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64);
183
184// ── Strings ───────────────────────────────────────────────────────────────────
185
186/// Generates `Serialize` for `&str` and `String`.
187///
188/// String layout in the buffer (high to low):
189/// ```text
190/// [u32 length prefix][UTF-8 bytes (4-byte aligned)]
191/// ```
192/// The slot returned is the slot of the length prefix.  The parent table
193/// writes a forward offset pointing to this slot.
194macro_rules! impl_serialize_str {
195    ($($t:ty),*) => {$(
196        impl Serialize for $t {
197            const SIZE: usize = 4;
198            const ALIGN: usize = 4;
199            const MODE: DataType = DataType::Offset;
200            /// `len + 11` — 4 bytes for length prefix, 4 bytes alignment, 3 bytes
201            /// worst-case padding before the u32 align step.
202            #[inline(always)]
203            fn size_hint(&self) -> usize { self.len() + 11 }
204            #[inline]
205            fn write_to<B: Buffer>(&self, buffer: &mut B) -> usize {
206                buffer.ensure_capacity(Serialize::size_hint(self));
207                self.write_to_unchecked(buffer)
208            }
209            #[inline]
210            fn write_to_unchecked<B: Buffer>(&self, buffer: &mut B) -> usize {
211                let bytes    = self.as_bytes();
212                let data_len = bytes.len();
213                // Step 1: write UTF-8 bytes at 4-byte-aligned position.
214                *buffer.head_mut() -= data_len;
215                *buffer.head_mut() &= !3;
216                let data_head = buffer.head();
217                buffer.buffer_mut()[data_head..data_head + data_len]
218                    .copy_from_slice(bytes);
219                // Step 2: write the u32 length prefix immediately before the data.
220                (data_len as u32).write_to_unchecked(buffer)
221            }
222            #[inline(always)]
223            fn is_absent(&self) -> bool { self.is_empty() }
224        }
225    )*};
226}
227
228impl_serialize_str!(&str, String);
229
230// ── Arrays (&[T] and Vec<T>) ──────────────────────────────────────────────────
231
232/// `Serialize` for `&[T]`.
233///
234/// Arrays are always `DataType::Offset` from the parent table's perspective —
235/// the table writes a forward offset to the array regardless of whether the
236/// elements are inline or offset themselves.
237///
238/// # Case A — Inline elements (`T::MODE == Inline`)
239///
240/// All elements are written as a single packed memcpy (using `from_raw_parts`
241/// to reinterpret the slice as bytes), followed by a u32 length prefix.
242/// The element size and alignment are determined at compile time via `size_of`
243/// and `align_of`.
244///
245/// # Case B — Offset elements (`T::MODE == Offset`)
246///
247/// Elements are written in reverse order (last element first, at the high end)
248/// so that element 0 ends up at the lowest address, matching iteration order.
249/// A forward-offset table (n × u32) is then written pointing to each element's
250/// slot.  Finally the u32 length prefix is written before the offset table.
251impl<T: Serialize> Serialize for &[T] {
252    const SIZE: usize = 4;
253    const ALIGN: usize = 4;
254    const MODE: DataType = DataType::Offset;
255    #[inline]
256    fn size_hint(&self) -> usize {
257        match T::MODE {
258            DataType::Inline => {
259                (self.len() * size_of::<T>()) + align_of::<T>().max(4) + 4
260            }
261            DataType::Offset => {
262                let data: usize = self.iter().map(|s| Serialize::size_hint(s)).sum();
263                4 + (self.len() * 4) + data
264            }
265            DataType::Union => {
266                let data: usize = self.iter().map(|s| Serialize::size_hint(s)).sum();
267                let offset: usize = (7+5*self.len())&!3;
268                data + offset
269            }
270        }
271    }
272
273    #[inline]
274    fn write_to<B: Buffer>(&self, buffer: &mut B) -> usize {
275        buffer.ensure_capacity(self.size_hint());
276        self.write_to_unchecked(buffer)
277    }
278
279    #[inline]
280    fn write_to_unchecked<B: Buffer>(&self, buffer: &mut B) -> usize {
281        match T::MODE {
282            DataType::Inline => {
283                let len        = self.len();
284                let total      = len * size_of::<T>();
285                let align_mask = align_of::<T>().max(4) - 1;
286                *buffer.head_mut() -= total;
287                *buffer.head_mut() &= !align_mask;
288                let head = buffer.head();
289                // Safety: T: Serialize implies T: Pod (via the Flat derive or
290                // built-in impls), so reinterpreting as bytes is valid.
291                let src = unsafe {
292                    std::slice::from_raw_parts(self.as_ptr() as *const u8, total)
293                };
294                buffer.buffer_mut()[head..head + total].copy_from_slice(src);
295                (len as u32).write_to_unchecked(buffer)
296            }
297            _ => {
298                let len = self.len();
299                let mut slots = Vec::with_capacity(len);
300                // Write elements in reverse so element 0 is at the lowest address.
301                // In &[T]::write_to_unchecked, DataType::Union arm:
302                for s in self.iter().rev() {
303                    slots.push(Serialize::write_to_unchecked(s, buffer));
304                };
305                // Align to 4 bytes before the tag section — Table payloads (vtable = 6 bytes,
306                // not a multiple of 4) leave head misaligned, causing the jump alignment step
307                // to create a gap between tags and jumps so the reader sees the wrong byte.
308                *buffer.head_mut() &= !3;
309                let union_flag = T::MODE.is_union_flag() as usize;
310                let padding = (3 & (4-(len&3))) * union_flag;
311                *buffer.head_mut() -= padding;
312                for i in (0..len*union_flag).rev() {
313                    unsafe { self.get_unchecked(i).tag().write_to_unchecked(buffer) };
314                };
315                // Write the forward-offset table after all elements.
316                // Align first so the jump reflects the actual entry position.
317                for target_slot in slots {
318                    *buffer.head_mut() -= 4;
319                    *buffer.head_mut() &= !3;
320                    let head = buffer.head();
321                    let jump = if target_slot == 0 { 0u32 }
322                        else { (buffer.slot() - target_slot) as u32 };
323                    buffer.buffer_mut()[head..head + 4]
324                        .copy_from_slice(&jump.to_le_bytes());
325                }
326                (len as u32).write_to_unchecked(buffer)
327            }
328        }
329    }
330    #[inline(always)]
331    fn is_absent(&self) -> bool { self.is_empty() }
332}
333
334/// `Serialize` for `Vec<T>` — delegates to the `&[T]` implementation.
335impl<T: Serialize> Serialize for Vec<T> {
336    const SIZE: usize = 4;
337    const MODE: DataType = DataType::Offset;
338    #[inline]
339    fn size_hint(&self) -> usize     { Serialize::size_hint(&self.as_slice()) }
340    #[inline]
341    fn write_to<B: Buffer>(&self, b: &mut B) -> usize {
342        Serialize::write_to(&self.as_slice(), b)
343    }
344    #[inline]
345    fn write_to_unchecked<B: Buffer>(&self, b: &mut B) -> usize {
346        Serialize::write_to_unchecked(&self.as_slice(), b)
347    }
348    #[inline(always)]
349    fn is_absent(&self) -> bool { self.is_empty() }
350}
351
352// ── ListView<T> ───────────────────────────────────────────────────────────────
353
354/// `Serialize` for [`ListView`] — enables zero-copy re-serialization of a
355/// view directly into a new buffer without materializing a `Vec`.
356///
357/// For inline element types, the source bytes are memcopied directly from the
358/// view's backing buffer slice — no element-by-element dispatch.  For offset
359/// types, the offset table must be rewritten because absolute positions change
360/// in the destination buffer; element values are re-serialized individually.
361impl<'a, T> Serialize for ListView<'a, T>
362where
363    T: ReadAt<'a>,
364    T::ReadOutput: Serialize,
365{
366    const SIZE: usize = 4;
367    const MODE: DataType = DataType::Offset;
368
369    #[inline]
370    fn size_hint(&self) -> usize {
371        match <T::ReadOutput as Serialize>::MODE {
372            DataType::Inline => {
373                let elem_size = size_of::<T::ReadOutput>();
374                let align     = align_of::<T::ReadOutput>().max(4);
375                4 + self.len() * elem_size + align
376            }
377            DataType::Offset => {
378                let data: usize = (0..self.len())
379                    .map(|i| Serialize::size_hint(&self.get(i)))
380                    .sum();
381                4 + self.len() * 4 + data
382            }
383            DataType::Union => {
384                let data: usize = (0..self.len())
385                    .map(|i| Serialize::size_hint(&self.get(i)))
386                    .sum();
387                let offset: usize = (7+5*self.len())&!3;
388                data + offset
389            }
390        }
391    }
392
393    #[inline]
394    fn write_to<B: Buffer>(&self, buffer: &mut B) -> usize {
395        buffer.ensure_capacity(Serialize::size_hint(self));
396        self.write_to_unchecked(buffer)
397    }
398
399    #[inline]
400    fn write_to_unchecked<B: Buffer>(&self, buffer: &mut B) -> usize {
401        match <T::ReadOutput as Serialize>::MODE {
402            DataType::Inline => {
403                let len = self.len();
404                // Fast path: one memcpy of the packed element bytes.
405                let elem_size  = size_of::<T::ReadOutput>();
406                let total      = len * elem_size;
407                let align_mask = align_of::<T::ReadOutput>().max(4) - 1;
408                *buffer.head_mut() -= total;
409                *buffer.head_mut() &= !align_mask;
410                let head = buffer.head();
411                buffer.buffer_mut()[head..head + total]
412                    .copy_from_slice(&self.buf[self.offset..self.offset + total]);
413                (len as u32).write_to_unchecked(buffer)
414            }
415            _ => {
416                let len = self.len();
417                let mut slots = Vec::with_capacity(len);
418                // Write elements in reverse so element 0 is at the lowest address.
419                for i in (0..len).rev() {
420                    slots.push(Serialize::write_to_unchecked(&self.get(i), buffer));
421                }
422                *buffer.head_mut() &= !3;
423                let union_flag =T::MODE.is_union_flag() as usize;
424                let padding = (3 & (4-(len&3))) * union_flag;
425                *buffer.head_mut() -= padding;
426                for i in (0..len*union_flag).rev() {
427                    self.get(i).tag().write_to_unchecked(buffer);
428                };
429                // Write the forward-offset table after all elements.
430                // Align first so the jump reflects the actual entry position.
431                for target_slot in slots {
432                    *buffer.head_mut() -= 4;
433                    *buffer.head_mut() &= !3;
434                    let head = buffer.head();
435                    let jump = (buffer.slot() - target_slot) as u32;
436                    buffer.buffer_mut()[head..head + 4]
437                        .copy_from_slice(&jump.to_le_bytes());
438                }
439                (len as u32).write_to_unchecked(buffer)
440            }
441        }
442    }
443    #[inline(always)]
444    fn is_absent(&self) -> bool { self.is_empty() }
445}