Skip to main content

kowito_json/serialize/
mod.rs

1#![allow(unsafe_op_in_unsafe_fn)]
2
3use std::string::String;
4use std::vec::Vec;
5
6#[cfg(target_arch = "aarch64")]
7use core::arch::aarch64::*;
8
9// ---------------------------------------------------------------------------
10// Compile-time escape lookup table
11// ---------------------------------------------------------------------------
12// Each entry is `0` if the byte is JSON-safe (no escaping needed), or the
13// ASCII code of the *escape character* that follows the backslash (e.g. `n`
14// for newline, `u` as a sentinel for the generic \u00XX path).
15const fn build_escape_table() -> [u8; 256] {
16    let mut t = [0u8; 256];
17    // All C0 control characters need escaping
18    let mut i = 0u8;
19    // We use a while loop because for-loops aren't stable in const fn yet
20    loop {
21        if i >= 0x20 {
22            break;
23        }
24        t[i as usize] = b'u'; // generic \u00XX fallback
25        i += 1;
26    }
27    // Overwrite the named ones with their short escape letter
28    t[b'"' as usize] = b'"';
29    t[b'\\' as usize] = b'\\';
30    t[b'\n' as usize] = b'n';
31    t[b'\r' as usize] = b'r';
32    t[b'\t' as usize] = b't';
33    t[0x08] = b'b'; // backspace
34    t[0x0C] = b'f'; // form-feed
35    t
36}
37
38/// Pre-computed escape table (256 bytes, lives in read-only data section).
39pub static ESCAPE_TABLE: [u8; 256] = build_escape_table();
40
41// ---------------------------------------------------------------------------
42// Core fast-path string writer — SIMD escape-mask helpers
43// ---------------------------------------------------------------------------
44
45// Positional bitmask for NEON movemask via pairwise reduction.
46#[cfg(target_arch = "aarch64")]
47static BITMASK: [u8; 16] = [1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128];
48
49/// Single-vector NEON movemask: bit j of the result is set iff lane j is 0xFF.
50/// Cost: 1 vld1q (hoisted) + 1 vand + 3 vpaddq + 1 fmov ≈ 5 instructions.
51#[cfg(target_arch = "aarch64")]
52#[inline(always)]
53unsafe fn neon_movemask_u8x16(v: uint8x16_t) -> u16 {
54    let bm = vld1q_u8(BITMASK.as_ptr());
55    let t = vandq_u8(v, bm);
56    let p1 = vpaddq_u8(t, t);
57    let p2 = vpaddq_u8(p1, p1);
58    let p3 = vpaddq_u8(p2, p2);
59    vgetq_lane_u64(vreinterpretq_u64_u8(p3), 0) as u16
60}
61
62/// Bulk movemask for four 16-byte comparison result vectors.
63/// Returns u64 packed: bits 0-15=c0, 16-31=c1, 32-47=c2, 48-63=c3.
64/// Cost: 4 vand + 3 vpaddq + 1 fmov = 8 instructions total.
65#[cfg(target_arch = "aarch64")]
66#[inline(always)]
67unsafe fn bulk_movemask_4x16(
68    c0: uint8x16_t,
69    c1: uint8x16_t,
70    c2: uint8x16_t,
71    c3: uint8x16_t,
72) -> u64 {
73    let bm = vld1q_u8(BITMASK.as_ptr());
74    let t0 = vandq_u8(c0, bm);
75    let t1 = vandq_u8(c1, bm);
76    let t2 = vandq_u8(c2, bm);
77    let t3 = vandq_u8(c3, bm);
78    let p01 = vpaddq_u8(t0, t1);
79    let p23 = vpaddq_u8(t2, t3);
80    let p0123 = vpaddq_u8(p01, p23);
81    let r = vpaddq_u8(p0123, p0123);
82    vgetq_lane_u64(vreinterpretq_u64_u8(r), 0)
83}
84
85/// 64-byte escape mask: bit j set ⟺ byte[ptr+j] is `"`, `\`, or control (<0x20).
86#[cfg(target_arch = "aarch64")]
87#[inline(always)]
88unsafe fn escape_mask_neon_x64(ptr: *const u8) -> u64 {
89    let v0 = vld1q_u8(ptr);
90    let v1 = vld1q_u8(ptr.add(16));
91    let v2 = vld1q_u8(ptr.add(32));
92    let v3 = vld1q_u8(ptr.add(48));
93    let quote = vdupq_n_u8(b'"');
94    let bslash = vdupq_n_u8(b'\\');
95    let ctrl = vdupq_n_u8(0x20);
96    let m0 = vorrq_u8(
97        vorrq_u8(vceqq_u8(v0, quote), vceqq_u8(v0, bslash)),
98        vcltq_u8(v0, ctrl),
99    );
100    let m1 = vorrq_u8(
101        vorrq_u8(vceqq_u8(v1, quote), vceqq_u8(v1, bslash)),
102        vcltq_u8(v1, ctrl),
103    );
104    let m2 = vorrq_u8(
105        vorrq_u8(vceqq_u8(v2, quote), vceqq_u8(v2, bslash)),
106        vcltq_u8(v2, ctrl),
107    );
108    let m3 = vorrq_u8(
109        vorrq_u8(vceqq_u8(v3, quote), vceqq_u8(v3, bslash)),
110        vcltq_u8(v3, ctrl),
111    );
112    bulk_movemask_4x16(m0, m1, m2, m3)
113}
114
115/// 32-byte escape mask.
116#[cfg(target_arch = "aarch64")]
117#[inline(always)]
118unsafe fn escape_mask_neon_x32(ptr: *const u8) -> u32 {
119    let v0 = vld1q_u8(ptr);
120    let v1 = vld1q_u8(ptr.add(16));
121    let quote = vdupq_n_u8(b'"');
122    let bslash = vdupq_n_u8(b'\\');
123    let ctrl = vdupq_n_u8(0x20);
124    let m0 = vorrq_u8(
125        vorrq_u8(vceqq_u8(v0, quote), vceqq_u8(v0, bslash)),
126        vcltq_u8(v0, ctrl),
127    );
128    let m1 = vorrq_u8(
129        vorrq_u8(vceqq_u8(v1, quote), vceqq_u8(v1, bslash)),
130        vcltq_u8(v1, ctrl),
131    );
132    (neon_movemask_u8x16(m0) as u32) | ((neon_movemask_u8x16(m1) as u32) << 16)
133}
134
135/// 16-byte escape mask.
136#[cfg(target_arch = "aarch64")]
137#[inline(always)]
138unsafe fn escape_mask_neon_x16(ptr: *const u8) -> u16 {
139    let v = vld1q_u8(ptr);
140    let quote = vceqq_u8(v, vdupq_n_u8(b'"'));
141    let bslash = vceqq_u8(v, vdupq_n_u8(b'\\'));
142    let ctrl = vcltq_u8(v, vdupq_n_u8(0x20));
143    neon_movemask_u8x16(vorrq_u8(vorrq_u8(quote, bslash), ctrl))
144}
145
146/// 64-byte escape mask (two AVX2 registers).
147#[cfg(target_arch = "x86_64")]
148#[target_feature(enable = "avx2")]
149unsafe fn escape_mask_avx2_x2(ptr: *const u8) -> u64 {
150    use core::arch::x86_64::*;
151    let d0 = _mm256_loadu_si256(ptr as *const __m256i);
152    let d1 = _mm256_loadu_si256(ptr.add(32) as *const __m256i);
153    let q = _mm256_set1_epi8(b'"' as i8);
154    let b = _mm256_set1_epi8(b'\\' as i8);
155    let c = _mm256_set1_epi8(0x20);
156    let m0 = _mm256_or_si256(
157        _mm256_cmpeq_epi8(d0, q),
158        _mm256_or_si256(_mm256_cmpeq_epi8(d0, b), _mm256_cmpgt_epi8(c, d0)),
159    );
160    let m1 = _mm256_or_si256(
161        _mm256_cmpeq_epi8(d1, q),
162        _mm256_or_si256(_mm256_cmpeq_epi8(d1, b), _mm256_cmpgt_epi8(c, d1)),
163    );
164    (_mm256_movemask_epi8(m0) as u32 as u64) | ((_mm256_movemask_epi8(m1) as u32 as u64) << 32)
165}
166
167/// 32-byte escape mask (single AVX2 register).
168#[cfg(target_arch = "x86_64")]
169#[target_feature(enable = "avx2")]
170unsafe fn escape_mask_avx2(ptr: *const u8) -> u32 {
171    use core::arch::x86_64::*;
172    let data = _mm256_loadu_si256(ptr as *const __m256i);
173    let q = _mm256_set1_epi8(b'"' as i8);
174    let b = _mm256_set1_epi8(b'\\' as i8);
175    let c = _mm256_set1_epi8(0x20);
176    let mask = _mm256_or_si256(
177        _mm256_cmpeq_epi8(data, q),
178        _mm256_or_si256(_mm256_cmpeq_epi8(data, b), _mm256_cmpgt_epi8(c, data)),
179    );
180    _mm256_movemask_epi8(mask) as u32
181}
182
183// ---------------------------------------------------------------------------
184// Bitmask-draining macros (shared by write_str_escape & _raw)
185// ---------------------------------------------------------------------------
186
187/// Drain escape bitmask → bulk-copy clean runs via extend_from_slice,
188/// then write escape sequence for each flagged byte.
189macro_rules! drain_escape_vec {
190    ($buf:expr, $bytes:expr, $start:expr, $base:expr, $mask:expr) => {{
191        let mut m = $mask;
192        while m != 0 {
193            let tz = m.trailing_zeros() as usize;
194            m &= m.wrapping_sub(1);
195            let pos = $base + tz;
196            if $start < pos {
197                $buf.extend_from_slice(unsafe { $bytes.get_unchecked($start..pos) });
198            }
199            let byte = unsafe { *$bytes.get_unchecked(pos) };
200            let esc = unsafe { *ESCAPE_TABLE.get_unchecked(byte as usize) };
201            match esc {
202                b'u' => {
203                    $buf.extend_from_slice(b"\\u00");
204                    let hi = (byte >> 4) & 0xF;
205                    let lo = byte & 0xF;
206                    $buf.push(if hi < 10 { b'0' + hi } else { b'a' + hi - 10 });
207                    $buf.push(if lo < 10 { b'0' + lo } else { b'a' + lo - 10 });
208                }
209                c => {
210                    $buf.push(b'\\');
211                    $buf.push(c);
212                }
213            }
214            $start = pos + 1;
215
216            // Manual unroll: process second bit if present
217            if m != 0 {
218                let tz = m.trailing_zeros() as usize;
219                m &= m.wrapping_sub(1);
220                let pos = $base + tz;
221                if $start < pos {
222                    $buf.extend_from_slice(unsafe { $bytes.get_unchecked($start..pos) });
223                }
224                let byte = unsafe { *$bytes.get_unchecked(pos) };
225                let esc = unsafe { *ESCAPE_TABLE.get_unchecked(byte as usize) };
226                match esc {
227                    b'u' => {
228                        $buf.extend_from_slice(b"\\u00");
229                        let hi = (byte >> 4) & 0xF;
230                        let lo = byte & 0xF;
231                        $buf.push(if hi < 10 { b'0' + hi } else { b'a' + hi - 10 });
232                        $buf.push(if lo < 10 { b'0' + lo } else { b'a' + lo - 10 });
233                    }
234                    c => {
235                        $buf.push(b'\\');
236                        $buf.push(c);
237                    }
238                }
239                $start = pos + 1;
240            }
241        }
242    }};
243}
244
245/// Drain escape bitmask using raw pointer arithmetic.
246macro_rules! drain_escape_raw {
247    ($curr:expr, $bytes:expr, $start:expr, $base:expr, $mask:expr) => {{
248        let mut m = $mask;
249        while m != 0 {
250            let tz = m.trailing_zeros() as usize;
251            m &= m.wrapping_sub(1);
252            let pos = $base + tz;
253            if $start < pos {
254                let chunk = $bytes.get_unchecked($start..pos);
255                std::ptr::copy_nonoverlapping(chunk.as_ptr(), $curr, chunk.len());
256                $curr = $curr.add(chunk.len());
257            }
258            let byte = *$bytes.get_unchecked(pos);
259            let esc = *ESCAPE_TABLE.get_unchecked(byte as usize);
260            match esc {
261                b'u' => {
262                    std::ptr::copy_nonoverlapping(b"\\u00".as_ptr(), $curr, 4);
263                    $curr = $curr.add(4);
264                    let hi = (byte >> 4) & 0xF;
265                    let lo = byte & 0xF;
266                    *$curr = if hi < 10 { b'0' + hi } else { b'a' + hi - 10 };
267                    $curr = $curr.add(1);
268                    *$curr = if lo < 10 { b'0' + lo } else { b'a' + lo - 10 };
269                    $curr = $curr.add(1);
270                }
271                c => {
272                    *$curr = b'\\';
273                    $curr = $curr.add(1);
274                    *$curr = c;
275                    $curr = $curr.add(1);
276                }
277            }
278            $start = pos + 1;
279
280            // Manual unroll: process second bit if present
281            if m != 0 {
282                let tz = m.trailing_zeros() as usize;
283                m &= m.wrapping_sub(1);
284                let pos = $base + tz;
285                if $start < pos {
286                    let chunk = $bytes.get_unchecked($start..pos);
287                    std::ptr::copy_nonoverlapping(chunk.as_ptr(), $curr, chunk.len());
288                    $curr = $curr.add(chunk.len());
289                }
290                let byte = *$bytes.get_unchecked(pos);
291                let esc = *ESCAPE_TABLE.get_unchecked(byte as usize);
292                match esc {
293                    b'u' => {
294                        std::ptr::copy_nonoverlapping(b"\\u00".as_ptr(), $curr, 4);
295                        $curr = $curr.add(4);
296                        let hi = (byte >> 4) & 0xF;
297                        let lo = byte & 0xF;
298                        *$curr = if hi < 10 { b'0' + hi } else { b'a' + hi - 10 };
299                        $curr = $curr.add(1);
300                        *$curr = if lo < 10 { b'0' + lo } else { b'a' + lo - 10 };
301                        $curr = $curr.add(1);
302                    }
303                    c => {
304                        *$curr = b'\\';
305                        $curr = $curr.add(1);
306                        *$curr = c;
307                        $curr = $curr.add(1);
308                    }
309                }
310                $start = pos + 1;
311            }
312        }
313    }};
314}
315
316// ---------------------------------------------------------------------------
317// String writers
318// ---------------------------------------------------------------------------
319
320/// Write `bytes` as a JSON string into `buf`.
321///
322/// SIMD (NEON / AVX2) produces a bitmask of **all** escape positions in each
323/// 64/32/16-byte chunk. Clean runs between escapes are bulk-copied; only the
324/// flagged bytes get the escape logic.
325#[inline]
326pub fn write_str_escape(buf: &mut Vec<u8>, bytes: &[u8]) {
327    buf.push(b'"');
328    let len = bytes.len();
329    let mut i = 0usize;
330    let mut start = 0usize;
331    buf.reserve(len + 2);
332
333    #[cfg(target_arch = "aarch64")]
334    {
335        while i + 64 <= len {
336            let mask = unsafe { escape_mask_neon_x64(bytes.as_ptr().add(i)) };
337            if mask == 0 {
338                i += 64;
339                continue;
340            }
341            drain_escape_vec!(buf, bytes, start, i, mask);
342            i += 64;
343        }
344        while i + 32 <= len {
345            let mask = unsafe { escape_mask_neon_x32(bytes.as_ptr().add(i)) } as u64;
346            if mask == 0 {
347                i += 32;
348                continue;
349            }
350            drain_escape_vec!(buf, bytes, start, i, mask);
351            i += 32;
352        }
353        while i + 16 <= len {
354            let mask = unsafe { escape_mask_neon_x16(bytes.as_ptr().add(i)) } as u64;
355            if mask == 0 {
356                i += 16;
357                continue;
358            }
359            drain_escape_vec!(buf, bytes, start, i, mask);
360            i += 16;
361        }
362    }
363
364    #[cfg(target_arch = "x86_64")]
365    {
366        if is_x86_feature_detected!("avx2") {
367            while i + 64 <= len {
368                let mask = unsafe { escape_mask_avx2_x2(bytes.as_ptr().add(i)) };
369                if mask == 0 {
370                    i += 64;
371                    continue;
372                }
373                drain_escape_vec!(buf, bytes, start, i, mask);
374                i += 64;
375            }
376            while i + 32 <= len {
377                let mask = unsafe { escape_mask_avx2(bytes.as_ptr().add(i)) } as u64;
378                if mask == 0 {
379                    i += 32;
380                    continue;
381                }
382                drain_escape_vec!(buf, bytes, start, i, mask);
383                i += 32;
384            }
385        }
386    }
387
388    #[cold]
389    fn scalar_tail(buf: &mut Vec<u8>, bytes: &[u8], mut i: usize, mut start: usize) {
390        let len = bytes.len();
391        while i < len {
392            let b = unsafe { *bytes.get_unchecked(i) };
393            let esc = unsafe { *ESCAPE_TABLE.get_unchecked(b as usize) };
394            if esc != 0 {
395                if start < i {
396                    buf.extend_from_slice(unsafe { bytes.get_unchecked(start..i) });
397                }
398                match esc {
399                    b'u' => {
400                        buf.extend_from_slice(b"\\u00");
401                        let hi = (b >> 4) & 0xF;
402                        let lo = b & 0xF;
403                        buf.push(if hi < 10 { b'0' + hi } else { b'a' + hi - 10 });
404                        buf.push(if lo < 10 { b'0' + lo } else { b'a' + lo - 10 });
405                    }
406                    c => {
407                        buf.push(b'\\');
408                        buf.push(c);
409                    }
410                }
411                start = i + 1;
412            }
413            i += 1;
414        }
415        if start < len {
416            buf.extend_from_slice(unsafe { bytes.get_unchecked(start..len) });
417        }
418    }
419
420    if i < len {
421        scalar_tail(buf, bytes, i, start);
422    } else {
423        if start < len {
424            buf.extend_from_slice(unsafe { bytes.get_unchecked(start..len) });
425        }
426    }
427    buf.push(b'"');
428}
429
430/// Write `bytes` as a JSON string into a raw pointer. Returns the new pointer.
431///
432/// # Safety
433/// `curr` must point to a writable buffer with capacity ≥ `bytes.len() * 6 + 2`.
434#[inline]
435pub unsafe fn write_str_escape_raw(mut curr: *mut u8, bytes: &[u8]) -> *mut u8 {
436    *curr = b'"';
437    curr = curr.add(1);
438    let len = bytes.len();
439    let mut i = 0usize;
440    let mut start = 0usize;
441
442    #[cfg(target_arch = "aarch64")]
443    {
444        while i + 64 <= len {
445            let mask = escape_mask_neon_x64(bytes.as_ptr().add(i));
446            if mask == 0 {
447                i += 64;
448                continue;
449            }
450            drain_escape_raw!(curr, bytes, start, i, mask);
451            i += 64;
452        }
453        while i + 32 <= len {
454            let mask = escape_mask_neon_x32(bytes.as_ptr().add(i)) as u64;
455            if mask == 0 {
456                i += 32;
457                continue;
458            }
459            drain_escape_raw!(curr, bytes, start, i, mask);
460            i += 32;
461        }
462        while i + 16 <= len {
463            let mask = escape_mask_neon_x16(bytes.as_ptr().add(i)) as u64;
464            if mask == 0 {
465                i += 16;
466                continue;
467            }
468            drain_escape_raw!(curr, bytes, start, i, mask);
469            i += 16;
470        }
471    }
472
473    #[cfg(target_arch = "x86_64")]
474    {
475        if is_x86_feature_detected!("avx2") {
476            while i + 64 <= len {
477                let mask = escape_mask_avx2_x2(bytes.as_ptr().add(i));
478                if mask == 0 {
479                    i += 64;
480                    continue;
481                }
482                drain_escape_raw!(curr, bytes, start, i, mask);
483                i += 64;
484            }
485            while i + 32 <= len {
486                let mask = escape_mask_avx2(bytes.as_ptr().add(i)) as u64;
487                if mask == 0 {
488                    i += 32;
489                    continue;
490                }
491                drain_escape_raw!(curr, bytes, start, i, mask);
492                i += 32;
493            }
494        }
495    }
496
497    #[cold]
498    unsafe fn scalar_tail_raw(
499        mut curr: *mut u8,
500        bytes: &[u8],
501        mut i: usize,
502        mut start: usize,
503    ) -> *mut u8 {
504        let len = bytes.len();
505        while i < len {
506            let b = *bytes.get_unchecked(i);
507            let esc = *ESCAPE_TABLE.get_unchecked(b as usize);
508            if esc != 0 {
509                if start < i {
510                    let chunk = bytes.get_unchecked(start..i);
511                    std::ptr::copy_nonoverlapping(chunk.as_ptr(), curr, chunk.len());
512                    curr = curr.add(chunk.len());
513                }
514                match esc {
515                    b'u' => {
516                        std::ptr::copy_nonoverlapping(b"\\u00".as_ptr(), curr, 4);
517                        curr = curr.add(4);
518                        let hi = (b >> 4) & 0xF;
519                        let lo = b & 0xF;
520                        *curr = if hi < 10 { b'0' + hi } else { b'a' + hi - 10 };
521                        curr = curr.add(1);
522                        *curr = if lo < 10 { b'0' + lo } else { b'a' + lo - 10 };
523                        curr = curr.add(1);
524                    }
525                    c => {
526                        *curr = b'\\';
527                        curr = curr.add(1);
528                        *curr = c;
529                        curr = curr.add(1);
530                    }
531                }
532                start = i + 1;
533            }
534            i += 1;
535        }
536
537        if start < len {
538            let chunk = bytes.get_unchecked(start..len);
539            std::ptr::copy_nonoverlapping(chunk.as_ptr(), curr, chunk.len());
540            curr = curr.add(chunk.len());
541        }
542        curr
543    }
544
545    if i < len {
546        curr = scalar_tail_raw(curr, bytes, i, start);
547    } else {
548        if start < len {
549            let chunk = bytes.get_unchecked(start..len);
550            std::ptr::copy_nonoverlapping(chunk.as_ptr(), curr, chunk.len());
551            curr = curr.add(chunk.len());
552        }
553    }
554
555    *curr = b'"';
556    curr.add(1)
557}
558
559// ---------------------------------------------------------------------------
560// Serialize trait
561// ---------------------------------------------------------------------------
562pub trait Serialize {
563    fn serialize(&self, buf: &mut Vec<u8>);
564}
565
566pub trait SerializeRaw {
567    /// Serialize the value directly to raw memory.
568    ///
569    /// # Safety
570    /// This function is unsafe because it dereferences raw pointers.  
571    /// The caller must ensure that `curr` points to a valid, properly aligned, and writable buffer with sufficient capacity.
572    unsafe fn serialize_raw(&self, curr: *mut u8) -> *mut u8;
573}
574
575impl SerializeRaw for String {
576    #[inline(always)]
577    unsafe fn serialize_raw(&self, curr: *mut u8) -> *mut u8 {
578        unsafe { write_str_escape_raw(curr, self.as_bytes()) }
579    }
580}
581
582impl SerializeRaw for str {
583    #[inline(always)]
584    unsafe fn serialize_raw(&self, curr: *mut u8) -> *mut u8 {
585        unsafe { write_str_escape_raw(curr, self.as_bytes()) }
586    }
587}
588
589impl SerializeRaw for bool {
590    #[inline(always)]
591    unsafe fn serialize_raw(&self, curr: *mut u8) -> *mut u8 {
592        unsafe {
593            if *self {
594                std::ptr::copy_nonoverlapping(b"true".as_ptr(), curr, 4);
595                curr.add(4)
596            } else {
597                std::ptr::copy_nonoverlapping(b"false".as_ptr(), curr, 5);
598                curr.add(5)
599            }
600        }
601    }
602}
603
604impl Serialize for String {
605    #[inline(always)]
606    fn serialize(&self, buf: &mut Vec<u8>) {
607        write_str_escape(buf, self.as_bytes());
608    }
609}
610
611impl Serialize for str {
612    #[inline(always)]
613    fn serialize(&self, buf: &mut Vec<u8>) {
614        write_str_escape(buf, self.as_bytes());
615    }
616}
617
618impl Serialize for bool {
619    #[inline(always)]
620    fn serialize(&self, buf: &mut Vec<u8>) {
621        if *self {
622            buf.extend_from_slice(b"true");
623        } else {
624            buf.extend_from_slice(b"false");
625        }
626    }
627}
628
629macro_rules! impl_serialize_int {
630    ($($t:ty),*) => {
631        $(
632            impl Serialize for $t {
633                #[inline(always)]
634                fn serialize(&self, buf: &mut Vec<u8>) {
635                    let mut buffer = itoa::Buffer::new();
636                    buf.extend_from_slice(buffer.format(*self).as_bytes());
637                }
638            }
639
640            impl SerializeRaw for $t {
641                #[inline(always)]
642                unsafe fn serialize_raw(&self, curr: *mut u8) -> *mut u8 {
643                    let mut buffer = itoa::Buffer::new();
644                    let s = buffer.format(*self);
645                    let len = s.len();
646                    unsafe {
647                        std::ptr::copy_nonoverlapping(s.as_ptr(), curr, len);
648                        curr.add(len)
649                    }
650                }
651            }
652        )*
653    };
654}
655
656impl_serialize_int!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
657
658macro_rules! impl_serialize_float {
659    ($($t:ty),*) => {
660        $(
661            impl Serialize for $t {
662                #[inline(always)]
663                fn serialize(&self, buf: &mut Vec<u8>) {
664                    let mut buffer = ryu::Buffer::new();
665                    buf.extend_from_slice(buffer.format(*self).as_bytes());
666                }
667            }
668
669            impl SerializeRaw for $t {
670                #[inline(always)]
671                unsafe fn serialize_raw(&self, curr: *mut u8) -> *mut u8 {
672                    let mut buffer = ryu::Buffer::new();
673                    let s = buffer.format(*self);
674                    let len = s.len();
675                    unsafe {
676                        std::ptr::copy_nonoverlapping(s.as_ptr(), curr, len);
677                        curr.add(len)
678                    }
679                }
680            }
681        )*
682    };
683}
684
685impl_serialize_float!(f32, f64);
686
687/// Raw global dispatcher
688///
689/// # Safety
690/// This function is unsafe because it calls unsafe trait methods that dereference raw pointers.
691/// The caller must ensure that `curr` points to a valid, properly aligned, and writable buffer with sufficient capacity.
692#[inline(always)]
693pub unsafe fn write_value_raw<T: SerializeRaw + ?Sized>(val: &T, curr: *mut u8) -> *mut u8 {
694    unsafe { val.serialize_raw(curr) }
695}
696
697/// Global dispatcher used by the derive macro
698#[inline(always)]
699pub fn write_value<T: Serialize + ?Sized>(val: &T, buf: &mut Vec<u8>) {
700    val.serialize(buf);
701}
702
703// ---------------------------------------------------------------------------
704// Advanced Serialize implementations (Collections, Option, etc.)
705// ---------------------------------------------------------------------------
706
707impl<T: Serialize> Serialize for Vec<T> {
708    #[inline]
709    fn serialize(&self, buf: &mut Vec<u8>) {
710        self.as_slice().serialize(buf);
711    }
712}
713
714impl<T: Serialize> Serialize for [T] {
715    #[inline]
716    fn serialize(&self, buf: &mut Vec<u8>) {
717        buf.push(b'[');
718        for (i, item) in self.iter().enumerate() {
719            if i > 0 {
720                buf.push(b',');
721            }
722            item.serialize(buf);
723        }
724        buf.push(b']');
725    }
726}
727
728impl<T: Serialize + ?Sized> Serialize for &T {
729    #[inline(always)]
730    fn serialize(&self, buf: &mut Vec<u8>) {
731        (**self).serialize(buf);
732    }
733}
734
735impl<T: Serialize> Serialize for Option<T> {
736    #[inline]
737    fn serialize(&self, buf: &mut Vec<u8>) {
738        match self {
739            Some(v) => v.serialize(buf),
740            None => buf.extend_from_slice(b"null"),
741        }
742    }
743}
744
745impl<T: Serialize + ?Sized> Serialize for Box<T> {
746    #[inline(always)]
747    fn serialize(&self, buf: &mut Vec<u8>) {
748        self.as_ref().serialize(buf);
749    }
750}
751
752use std::borrow::Cow;
753impl<'a, T: Serialize + ?Sized + ToOwned> Serialize for Cow<'a, T> {
754    #[inline(always)]
755    fn serialize(&self, buf: &mut Vec<u8>) {
756        self.as_ref().serialize(buf);
757    }
758}
759impl<T: SerializeRaw> SerializeRaw for Vec<T> {
760    #[inline]
761    unsafe fn serialize_raw(&self, curr: *mut u8) -> *mut u8 {
762        unsafe { self.as_slice().serialize_raw(curr) }
763    }
764}
765
766impl<T: SerializeRaw> SerializeRaw for [T] {
767    #[inline]
768    unsafe fn serialize_raw(&self, mut curr: *mut u8) -> *mut u8 {
769        unsafe {
770            *curr = b'[';
771            curr = curr.add(1);
772            for (i, item) in self.iter().enumerate() {
773                if i > 0 {
774                    *curr = b',';
775                    curr = curr.add(1);
776                }
777                curr = item.serialize_raw(curr);
778            }
779            *curr = b']';
780            curr.add(1)
781        }
782    }
783}
784
785impl<T: SerializeRaw + ?Sized> SerializeRaw for &T {
786    #[inline(always)]
787    unsafe fn serialize_raw(&self, curr: *mut u8) -> *mut u8 {
788        unsafe { (**self).serialize_raw(curr) }
789    }
790}
791
792impl<T: SerializeRaw> SerializeRaw for Option<T> {
793    #[inline]
794    unsafe fn serialize_raw(&self, curr: *mut u8) -> *mut u8 {
795        unsafe {
796            match self {
797                Some(v) => v.serialize_raw(curr),
798                None => {
799                    std::ptr::copy_nonoverlapping(b"null".as_ptr(), curr, 4);
800                    curr.add(4)
801                }
802            }
803        }
804    }
805}
806
807impl<T: SerializeRaw + ?Sized> SerializeRaw for Box<T> {
808    #[inline(always)]
809    unsafe fn serialize_raw(&self, curr: *mut u8) -> *mut u8 {
810        unsafe { self.as_ref().serialize_raw(curr) }
811    }
812}
813
814impl<'a, T: SerializeRaw + ?Sized + ToOwned> SerializeRaw for Cow<'a, T> {
815    #[inline(always)]
816    unsafe fn serialize_raw(&self, curr: *mut u8) -> *mut u8 {
817        unsafe { self.as_ref().serialize_raw(curr) }
818    }
819}