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}