Skip to main content

value_trait/
generator.rs

1// This is mostly taken from json-rust's codegen
2// as it seems to perform well and it makes sense to see
3// if we can adopt the approach
4//
5// https://github.com/maciejhirsz/json-rust/blob/master/src/codegen.rs
6
7macro_rules! stry {
8    ($e:expr) => {
9        match $e {
10            ::std::result::Result::Ok(val) => val,
11            ::std::result::Result::Err(err) => return ::std::result::Result::Err(err),
12        }
13    };
14}
15
16use std::io;
17use std::io::Write;
18use std::ptr;
19
20const QU: u8 = b'"';
21const BS: u8 = b'\\';
22const BB: u8 = b'b';
23const TT: u8 = b't';
24const NN: u8 = b'n';
25const FF: u8 = b'f';
26const RR: u8 = b'r';
27const UU: u8 = b'u';
28const __: u8 = 0;
29
30// Look up table for characters that need escaping in a product string
31pub(crate) static ESCAPED: [u8; 256] = [
32    // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
33    UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0
34    UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1
35    __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2
36    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3
37    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4
38    __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5
39    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6
40    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7
41    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8
42    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9
43    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A
44    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B
45    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C
46    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D
47    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E
48    __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F
49];
50
51// taken from https://github.com/serde-rs/json/blob/4354fc3eb2232ee0ba9a9a23acce107a980a6dc0/src/ser.rs#L1790
52// This is called rarely
53#[inline(never)]
54fn u_encode<W>(w: &mut W, byte: u8) -> io::Result<()>
55where
56    W: Write,
57{
58    static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";
59    let bytes = [
60        b'\\',
61        b'u',
62        b'0',
63        b'0',
64        HEX_DIGITS[(byte >> 4) as usize],
65        HEX_DIGITS[(byte & 0xF) as usize],
66    ];
67    w.write_all(&bytes)
68}
69
70/// Base generator trait
71pub trait BaseGenerator {
72    /// The writer
73    type T: Write;
74
75    /// returns the writer
76    fn get_writer(&mut self) -> &mut Self::T;
77
78    /// Write a slice
79    /// # Errors
80    /// if the write fails
81    #[inline]
82    fn write(&mut self, slice: &[u8]) -> io::Result<()> {
83        self.get_writer().write_all(slice)
84    }
85
86    /// Write a char
87    /// # Errors
88    /// if the write fails
89    #[inline]
90    fn write_char(&mut self, ch: u8) -> io::Result<()> {
91        self.get_writer().write_all(&[ch])
92    }
93
94    /// write with minimum
95    /// # Errors
96    /// if the write fails
97    fn write_min(&mut self, slice: &[u8], min: u8) -> io::Result<()>;
98
99    /// writes new line
100    /// # Errors
101    /// if the write fails
102    #[inline]
103    fn new_line(&mut self) -> io::Result<()> {
104        Ok(())
105    }
106
107    /// indents one step
108    #[inline]
109    fn indent(&mut self) {}
110
111    /// dedents one step
112    #[inline]
113    fn dedent(&mut self) {}
114
115    /// writes a string
116    /// # Errors
117    /// if the write fails
118    #[inline]
119    fn write_string(&mut self, string: &str) -> io::Result<()> {
120        stry!(self.write_char(b'"'));
121        stry!(self.write_string_content(string));
122        self.write_char(b'"')
123    }
124
125    /// writes a string
126    /// # Errors
127    /// if the write fails
128    #[inline]
129    fn write_string_content(&mut self, string: &str) -> io::Result<()> {
130        let mut string = string.as_bytes();
131        unsafe {
132            // Looking at the table above the lower 5 bits are entirely
133            // quote characters that gives us a bitmask of 0x1f for that
134            // region, only quote (`"`) and backslash (`\`) are not in
135            // this range.
136            stry!(self.write_str_simd(&mut string));
137        }
138        write_string_rust(self.get_writer(), &mut string)
139    }
140
141    /// writes a simple string (usually short and non escaped)
142    /// This means we can skip the simd accelerated writing which is
143    /// expensive on short strings.
144    /// # Errors
145    /// if the write fails
146    #[inline]
147    fn write_simple_string(&mut self, string: &str) -> io::Result<()> {
148        self.write(br#"""#)?;
149        write_string_rust(self.get_writer(), &mut string.as_bytes())?;
150        self.write(br#"""#)
151    }
152    /// writes a simple string content  (usually short and non escaped)
153    /// This means we can skip the simd accelerated writing which is
154    /// expensive on short strings.
155    /// # Errors
156    /// if the write fails
157    #[inline]
158    fn write_simple_str_content(&mut self, string: &str) -> io::Result<()> {
159        let mut string = string.as_bytes();
160        // Legacy code to handle the remainder of the code
161        write_string_rust(self.get_writer(), &mut string)
162    }
163
164    /// writes a float value
165    /// # Errors
166    /// if the write fails
167    #[inline]
168    fn write_float(&mut self, num: f64) -> io::Result<()> {
169        let mut buffer = ryu::Buffer::new();
170        let s = buffer.format_finite(num);
171        self.get_writer().write_all(s.as_bytes())
172    }
173
174    /// writes an integer value
175    /// # Errors
176    /// if the write fails
177    #[inline]
178    fn write_int<I: itoa::Integer>(&mut self, num: I) -> io::Result<()> {
179        let mut buffer = itoa::Buffer::new();
180        let s = buffer.format(num);
181        self.get_writer().write_all(s.as_bytes())
182    }
183
184    /// # Safety
185    /// This function is unsafe because it may use simd instructions
186    /// # Errors
187    ///  if the write fails
188    #[cfg(all(
189        feature = "runtime-detection",
190        any(target_arch = "x86_64", target_arch = "x86"),
191    ))]
192    unsafe fn write_str_simd(&mut self, string: &mut &[u8]) -> io::Result<()> {
193        unsafe { write_str_simd_fastest(self.get_writer(), string) }
194    }
195    #[cfg(all(target_feature = "avx2", not(feature = "runtime-detection")))]
196    #[inline]
197    /// Writes a string with simd-acceleration
198    /// # Safety
199    /// This function is unsafe because it uses simd instructions
200    /// # Errors
201    ///  if the write fails
202    unsafe fn write_str_simd(&mut self, string: &mut &[u8]) -> io::Result<()> {
203        write_str_simd_avx2(self.get_writer(), string)
204    }
205
206    #[cfg(all(
207        target_feature = "sse2",
208        not(target_feature = "avx2"),
209        not(feature = "runtime-detection")
210    ))]
211    #[inline]
212    /// Writes a string with simd-acceleration
213    /// # Safety
214    /// This function is unsafe because it uses simd instructions
215    /// # Errors
216    ///  if the write fails
217    unsafe fn write_str_simd(&mut self, string: &mut &[u8]) -> io::Result<()> {
218        write_str_simd_sse2(self.get_writer(), string)
219    }
220
221    #[cfg(not(any(
222        all(
223            feature = "runtime-detection",
224            any(target_arch = "x86_64", target_arch = "x86")
225        ),
226        feature = "portable",
227        target_feature = "avx2",
228        target_feature = "sse2",
229        target_feature = "simd128",
230        target_arch = "aarch64",
231    )))]
232    #[inline]
233    /// Writes a string with simd-acceleration (not really, as the architecture doesn't support it)
234    /// # Safety
235    /// This function is unsafe because it uses simd instructions
236    /// # Errors
237    ///  if the write fails
238    unsafe fn write_str_simd(&mut self, string: &mut &[u8]) -> io::Result<()> {
239        write_string_rust(self.get_writer(), string)
240    }
241
242    #[cfg(target_arch = "aarch64")]
243    #[inline]
244    /// Writes a string with simd-acceleration
245    /// # Safety
246    /// This function is unsafe because it uses simd instructions
247    /// # Errors
248    ///  if the write fails
249    unsafe fn write_str_simd(&mut self, string: &mut &[u8]) -> io::Result<()> {
250        use std::arch::aarch64::{
251            uint8x16_t, vandq_u8, vceqq_u8, vdupq_n_u8, veorq_u8, vgetq_lane_u16, vld1q_u8,
252            vorrq_u8, vpaddq_u8, vreinterpretq_u16_u8,
253        };
254        use std::mem;
255
256        #[inline]
257        unsafe fn bit_mask() -> uint8x16_t {
258            unsafe {
259                mem::transmute([
260                    0x01_u8, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x01, 0x02, 0x4, 0x8, 0x10,
261                    0x20, 0x40, 0x80,
262                ])
263            }
264        }
265
266        #[inline]
267        unsafe fn neon_movemask(input: uint8x16_t) -> u16 {
268            unsafe {
269                let simd_input: uint8x16_t = vandq_u8(input, bit_mask());
270                let tmp: uint8x16_t = vpaddq_u8(simd_input, simd_input);
271                let tmp = vpaddq_u8(tmp, tmp);
272                let tmp = vpaddq_u8(tmp, tmp);
273
274                vgetq_lane_u16(vreinterpretq_u16_u8(tmp), 0)
275            }
276        }
277
278        let writer = self.get_writer();
279        // The case where we have a 16+ byte block
280        // we repeate the same logic as above but with
281        // only 16 bytes
282        let mut idx = 0;
283        unsafe {
284            let zero = vdupq_n_u8(0);
285            let lower_quote_range = vdupq_n_u8(0x1F);
286            let quote = vdupq_n_u8(b'"');
287            let backslash = vdupq_n_u8(b'\\');
288            while string.len() - idx > 16 {
289                // Load 16 bytes of data;
290                let data: uint8x16_t = vld1q_u8(string.as_ptr().add(idx));
291                // Test the data against being backslash and quote.
292                let bs_or_quote = vorrq_u8(vceqq_u8(data, backslash), vceqq_u8(data, quote));
293                // Now mask the data with the quote range (0x1F).
294                let in_quote_range = vandq_u8(data, lower_quote_range);
295                // then test of the data is unchanged. aka: xor it with the
296                // Any field that was inside the quote range it will be zero
297                // now.
298                let is_unchanged = veorq_u8(data, in_quote_range);
299                let in_range = vceqq_u8(is_unchanged, zero);
300                let quote_bits = neon_movemask(vorrq_u8(bs_or_quote, in_range));
301                if quote_bits == 0 {
302                    idx += 16;
303                } else {
304                    let quote_dist = quote_bits.trailing_zeros() as usize;
305                    stry!(writer.write_all(&string[0..idx + quote_dist]));
306                    let ch = string[idx + quote_dist];
307                    match ESCAPED[ch as usize] {
308                        b'u' => stry!(u_encode(writer, ch)),
309                        escape => stry!(writer.write_all(&[b'\\', escape])),
310                    }
311
312                    *string = &string[idx + quote_dist + 1..];
313                    idx = 0;
314                }
315            }
316        }
317        stry!(writer.write_all(&string[0..idx]));
318        *string = &string[idx..];
319        Ok(())
320    }
321
322    #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
323    #[inline]
324    /// Writes a string with simd-acceleration
325    /// # Safety
326    /// This function is unsafe because it uses simd instructions
327    /// # Errors
328    ///  if the write fails
329    unsafe fn write_str_simd(&mut self, string: &mut &[u8]) -> io::Result<()> {
330        let writer = self.get_writer();
331        use std::arch::wasm32::{
332            u8x16_bitmask, u8x16_eq, u8x16_splat, v128, v128_and, v128_load, v128_or, v128_xor,
333        };
334
335        // The case where we have a 16+ byte block
336        // we repeat the same logic as above but with
337        // only 16 bytes
338        let mut idx = 0;
339        let zero = u8x16_splat(0);
340        let lower_quote_range = u8x16_splat(0x1F);
341        let quote = u8x16_splat(b'"');
342        let backslash = u8x16_splat(b'\\');
343        while string.len() - idx > 16 {
344            // Load 16 bytes of data;
345            let data = v128_load(string.as_ptr().add(idx).cast::<v128>());
346            // Test the data against being backslash and quote.
347            let bs_or_quote = v128_or(u8x16_eq(data, backslash), u8x16_eq(data, quote));
348            // Now mask the data with the quote range (0x1F).
349            let in_quote_range = v128_and(data, lower_quote_range);
350            // then test of the data is unchanged. aka: xor it with the
351            // Any field that was inside the quote range it will be zero
352            // now.
353            let is_unchanged = v128_xor(data, in_quote_range);
354            let in_range = u8x16_eq(is_unchanged, zero);
355            let quote_bits = u8x16_bitmask(v128_or(bs_or_quote, in_range));
356            if quote_bits == 0 {
357                idx += 16;
358            } else {
359                let quote_dist = quote_bits.trailing_zeros() as usize;
360                stry!(writer.write_all(&string[0..idx + quote_dist]));
361                let ch = string[idx + quote_dist];
362                match ESCAPED[ch as usize] {
363                    b'u' => stry!(u_encode(writer, ch)),
364                    escape => stry!(writer.write_all(&[b'\\', escape])),
365                }
366
367                *string = &string[idx + quote_dist + 1..];
368                idx = 0;
369            }
370        }
371        stry!(writer.write_all(&string[0..idx]));
372        *string = &string[idx..];
373        Ok(())
374    }
375}
376
377#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
378unsafe fn write_str_simd_fastest<W>(writer: &mut W, string: &mut &[u8]) -> io::Result<()>
379where
380    W: Write,
381{
382    // This is not possible right now since we can't get `W` to be part of the static
383    // use std::sync::atomic::{AtomicPtr, Ordering};
384    // type FnRaw = *mut ();
385
386    // type WriteStrFn<T> = for<'a, 'b, 'c> unsafe fn(&'a mut T, &'b mut &'c [u8]) -> io::Result<()>;
387    // static FN: AtomicPtr<()> = AtomicPtr::new(get_fastest::<W> as FnRaw);
388    // #[inline]
389    // fn get_fastest_available_implementation<W>() -> WriteStrFn<W>
390    // where
391    //     W: Write,
392    // {
393    //     if std::is_x86_feature_detected!("avx2") {
394    //         write_str_simd_avx2
395    //     } else if std::is_x86_feature_detected!("sse4.2") {
396    //         write_str_simd_sse2
397    //     } else {
398    //         write_str_simd_rust
399    //     }
400    // }
401
402    // #[inline]
403    // unsafe fn get_fastest<'invoke, 'de, W>(writer: &mut W, string: &mut &[u8]) -> io::Result<()>
404    // where
405    //     W: Write,
406    //     'de: 'invoke,
407    // {
408    //     let fun = get_fastest_available_implementation();
409    //     FN.store(fun as FnRaw, Ordering::Relaxed);
410    //     (fun)(writer, string)
411    // }
412    // let fun = FN.load(Ordering::Relaxed);
413    // mem::transmute::<FnRaw, WriteStrFn>(fun)(writer, string)
414
415    if std::is_x86_feature_detected!("avx2") {
416        unsafe { write_str_simd_avx2(writer, string) }
417    } else if std::is_x86_feature_detected!("sse2") {
418        unsafe { write_str_simd_sse2(writer, string) }
419    } else {
420        #[cfg(not(feature = "portable"))]
421        return write_string_rust(writer, string);
422        #[cfg(feature = "portable")]
423        return write_str_simd_portable(writer, string);
424    }
425}
426#[inline]
427fn write_string_container<W>(writer: &mut W, string: &[u8], mut start: usize) -> io::Result<()>
428where
429    W: Write,
430{
431    stry!(writer.write_all(&string[..start]));
432
433    for (index, ch) in string.iter().enumerate().skip(start) {
434        let escape = ESCAPED[*ch as usize];
435        if escape > 0 {
436            stry!(writer.write_all(&string[start..index]));
437            if escape == b'u' {
438                stry!(u_encode(writer, *ch));
439            } else {
440                stry!(writer.write_all(&[b'\\', escape]));
441            }
442            start = index + 1;
443        }
444    }
445    writer.write_all(&string[start..])
446}
447
448#[inline]
449fn write_string_rust<W>(writer: &mut W, string: &mut &[u8]) -> io::Result<()>
450where
451    W: Write,
452{
453    // Legacy code to handle the remainder of the code
454    for (index, ch) in string.iter().enumerate() {
455        if ESCAPED[*ch as usize] > 0 {
456            write_string_container(writer, string, index)?;
457            *string = &string[string.len()..];
458            return Ok(());
459        }
460    }
461    writer.write_all(string)?;
462    *string = &string[string.len()..];
463    Ok(())
464}
465
466#[cfg(feature = "portable")]
467#[inline]
468/// Writes a string with simd-acceleration
469/// # Safety
470/// This function is unsafe because it uses simd instructions
471/// # Errors
472///  if the write fails
473unsafe fn write_str_simd_portable<W>(writer: &mut W, string: &mut &[u8]) -> io::Result<()>
474where
475    W: Write,
476{
477    use std::simd::{SimdPartialEq, ToBitMask, u8x32};
478
479    let mut idx = 0;
480    let zero = u8x32::splat(0);
481    let lower_quote_range = u8x32::splat(0x1F_u8);
482    let quote = u8x32::splat(b'"');
483    let backslash = u8x32::splat(b'\\');
484    while string.len() - idx >= 32 {
485        // Load 32 bytes of data;
486        let data = u8x32::from_slice(&string[idx..]);
487        // Test the data against being backslash and quote.
488        let bs_or_quote = data.simd_eq(backslash) | data.simd_eq(quote);
489        // Now mask the data with the quote range (0x1F).
490        let in_quote_range = data & lower_quote_range;
491        // then test of the data is unchanged. aka: xor it with the
492        // Any field that was inside the quote range it will be zero
493        // now.
494        let is_unchanged = data ^ in_quote_range;
495        let in_range = is_unchanged.simd_eq(zero);
496        let quote_bits = (bs_or_quote | in_range).to_bitmask();
497        if quote_bits == 0 {
498            idx += 32;
499        } else {
500            let quote_dist = quote_bits.trailing_zeros() as usize;
501            stry!(writer.write_all(string.get_unchecked(0..idx + quote_dist)));
502
503            let ch = string[idx + quote_dist];
504            match ESCAPED[ch as usize] {
505                b'u' => stry!(u_encode(writer, ch)),
506                escape => stry!(writer.write_all(&[b'\\', escape])),
507            };
508
509            *string = string.get_unchecked(idx + quote_dist + 1..);
510            idx = 0;
511        }
512    }
513    stry!(writer.write_all(&string[0..idx]));
514    *string = string.get_unchecked(idx..);
515    Ok(())
516}
517
518#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
519#[target_feature(enable = "avx2")]
520#[inline]
521/// Writes a string with simd-acceleration
522/// # Safety
523/// This function is unsafe because it uses simd instructions
524/// # Errors
525///  if the write fails
526unsafe fn write_str_simd_avx2<W>(writer: &mut W, string: &mut &[u8]) -> io::Result<()>
527where
528    W: Write,
529{
530    #[cfg(target_arch = "x86")]
531    use std::arch::x86::{
532        __m256i, _mm256_and_si256, _mm256_cmpeq_epi8, _mm256_loadu_si256, _mm256_movemask_epi8,
533        _mm256_or_si256, _mm256_set1_epi8, _mm256_xor_si256,
534    };
535    #[cfg(target_arch = "x86_64")]
536    use std::arch::x86_64::{
537        __m256i, _mm256_and_si256, _mm256_cmpeq_epi8, _mm256_loadu_si256, _mm256_movemask_epi8,
538        _mm256_or_si256, _mm256_set1_epi8, _mm256_xor_si256,
539    };
540
541    unsafe {
542        let mut idx = 0;
543        let zero = _mm256_set1_epi8(0);
544        let lower_quote_range = _mm256_set1_epi8(0x1F_i8);
545        #[allow(clippy::cast_possible_wrap)] // it's a const, it's fine
546        let quote = _mm256_set1_epi8(b'"' as i8);
547        #[allow(clippy::cast_possible_wrap)] // it's a const, it's fine
548        let backslash = _mm256_set1_epi8(b'\\' as i8);
549        while string.len() - idx >= 32 {
550            // Load 32 bytes of data; _mm256_loadu_si256 does not require alignment
551            #[allow(clippy::cast_ptr_alignment)]
552            let data: __m256i = _mm256_loadu_si256(string.as_ptr().add(idx).cast::<__m256i>());
553            // Test the data against being backslash and quote.
554            let bs_or_quote = _mm256_or_si256(
555                _mm256_cmpeq_epi8(data, backslash),
556                _mm256_cmpeq_epi8(data, quote),
557            );
558            // Now mask the data with the quote range (0x1F).
559            let in_quote_range = _mm256_and_si256(data, lower_quote_range);
560            // then test of the data is unchanged. aka: xor it with the
561            // Any field that was inside the quote range it will be zero
562            // now.
563            let is_unchanged = _mm256_xor_si256(data, in_quote_range);
564            let in_range = _mm256_cmpeq_epi8(is_unchanged, zero);
565            let quote_bits = _mm256_movemask_epi8(_mm256_or_si256(bs_or_quote, in_range));
566            if quote_bits == 0 {
567                idx += 32;
568            } else {
569                let quote_dist = quote_bits.trailing_zeros() as usize;
570                stry!(writer.write_all(string.get_unchecked(0..idx + quote_dist)));
571
572                let ch = string[idx + quote_dist];
573                match ESCAPED[ch as usize] {
574                    b'u' => stry!(u_encode(writer, ch)),
575                    escape => stry!(writer.write_all(&[b'\\', escape])),
576                }
577
578                *string = string.get_unchecked(idx + quote_dist + 1..);
579                idx = 0;
580            }
581        }
582        stry!(writer.write_all(&string[0..idx]));
583        *string = string.get_unchecked(idx..);
584    }
585
586    Ok(())
587}
588
589#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
590#[target_feature(enable = "sse2")]
591#[inline]
592/// Writes a string with simd-acceleration
593/// # Safety
594/// This function is unsafe because it uses simd instructions
595/// # Errors
596///  if the write fails
597unsafe fn write_str_simd_sse2<W>(writer: &mut W, string: &mut &[u8]) -> io::Result<()>
598where
599    W: Write,
600{
601    #[cfg(target_arch = "x86")]
602    use std::arch::x86::{
603        __m128i, _mm_and_si128, _mm_cmpeq_epi8, _mm_loadu_si128, _mm_movemask_epi8, _mm_or_si128,
604        _mm_set1_epi8, _mm_xor_si128,
605    };
606    #[cfg(target_arch = "x86_64")]
607    use std::arch::x86_64::{
608        __m128i, _mm_and_si128, _mm_cmpeq_epi8, _mm_loadu_si128, _mm_movemask_epi8, _mm_or_si128,
609        _mm_set1_epi8, _mm_xor_si128,
610    };
611
612    unsafe {
613        let mut idx = 0;
614        let zero = _mm_set1_epi8(0);
615        let lower_quote_range = _mm_set1_epi8(0x1F_i8);
616        #[allow(clippy::cast_possible_wrap)] // it's a const, it's fine
617        let quote = _mm_set1_epi8(b'"' as i8);
618        #[allow(clippy::cast_possible_wrap)] // it's a const, it's fine
619        let backslash = _mm_set1_epi8(b'\\' as i8);
620        while string.len() - idx > 16 {
621            // Load 16 bytes of data; _mm_loadu_si128 does not require alignment
622            #[allow(clippy::cast_ptr_alignment)]
623            let data: __m128i = _mm_loadu_si128(string.as_ptr().add(idx).cast::<__m128i>());
624            // Test the data against being backslash and quote.
625            let bs_or_quote =
626                _mm_or_si128(_mm_cmpeq_epi8(data, backslash), _mm_cmpeq_epi8(data, quote));
627            // Now mask the data with the quote range (0x1F).
628            let in_quote_range = _mm_and_si128(data, lower_quote_range);
629            // then test of the data is unchanged. aka: xor it with the
630            // Any field that was inside the quote range it will be zero
631            // now.
632            let is_unchanged = _mm_xor_si128(data, in_quote_range);
633            let in_range = _mm_cmpeq_epi8(is_unchanged, zero);
634            let quote_bits = _mm_movemask_epi8(_mm_or_si128(bs_or_quote, in_range));
635            if quote_bits == 0 {
636                idx += 16;
637            } else {
638                let quote_dist = quote_bits.trailing_zeros() as usize;
639                stry!(writer.write_all(&string[0..idx + quote_dist]));
640
641                let ch = string[idx + quote_dist];
642                match ESCAPED[ch as usize] {
643                    b'u' => stry!(u_encode(writer, ch)),
644                    escape => stry!(writer.write_all(&[b'\\', escape])),
645                }
646
647                *string = &string[idx + quote_dist + 1..];
648                idx = 0;
649            }
650        }
651        stry!(writer.write_all(&string[0..idx]));
652        *string = &string[idx..];
653    }
654
655    Ok(())
656}
657
658///  Simple dump Generator
659pub struct DumpGenerator {
660    code: Vec<u8>,
661}
662
663impl Default for DumpGenerator {
664    fn default() -> Self {
665        Self {
666            code: Vec::with_capacity(1024),
667        }
668    }
669}
670
671impl DumpGenerator {
672    /// Creates a new generator
673    #[must_use]
674    pub fn new() -> Self {
675        Self::default()
676    }
677
678    /// Returns the data as a String
679    #[must_use]
680    pub fn consume(self) -> String {
681        // Original strings were unicode, numbers are all ASCII,
682        // therefore this is safe.
683        unsafe { String::from_utf8_unchecked(self.code) }
684    }
685}
686
687impl BaseGenerator for DumpGenerator {
688    type T = Vec<u8>;
689
690    #[inline]
691    fn write(&mut self, slice: &[u8]) -> io::Result<()> {
692        extend_from_slice(&mut self.code, slice);
693        Ok(())
694    }
695    #[inline]
696    fn write_char(&mut self, ch: u8) -> io::Result<()> {
697        self.code.push(ch);
698        Ok(())
699    }
700
701    #[inline]
702    fn get_writer(&mut self) -> &mut Vec<u8> {
703        &mut self.code
704    }
705
706    #[inline]
707    fn write_min(&mut self, _: &[u8], min: u8) -> io::Result<()> {
708        self.code.push(min);
709        Ok(())
710    }
711}
712
713/// Pretty Generator
714pub struct PrettyGenerator {
715    code: Vec<u8>,
716    dent: u16,
717    spaces_per_indent: u16,
718}
719
720impl PrettyGenerator {
721    /// Creates a new pretty printing generator
722    #[must_use]
723    pub fn new(spaces: u16) -> Self {
724        Self {
725            code: Vec::with_capacity(1024),
726            dent: 0,
727            spaces_per_indent: spaces,
728        }
729    }
730
731    /// Returns the data as a String
732    #[must_use]
733    pub fn consume(self) -> String {
734        unsafe { String::from_utf8_unchecked(self.code) }
735    }
736}
737
738impl BaseGenerator for PrettyGenerator {
739    type T = Vec<u8>;
740    #[inline]
741    fn write(&mut self, slice: &[u8]) -> io::Result<()> {
742        extend_from_slice(&mut self.code, slice);
743        Ok(())
744    }
745
746    #[inline]
747    fn write_char(&mut self, ch: u8) -> io::Result<()> {
748        self.code.push(ch);
749        Ok(())
750    }
751
752    #[inline]
753    fn get_writer(&mut self) -> &mut Vec<u8> {
754        &mut self.code
755    }
756
757    #[inline]
758    fn write_min(&mut self, slice: &[u8], _: u8) -> io::Result<()> {
759        extend_from_slice(&mut self.code, slice);
760        Ok(())
761    }
762
763    fn new_line(&mut self) -> io::Result<()> {
764        self.code.push(b'\n');
765        self.code.resize(
766            self.code.len() + (self.dent * self.spaces_per_indent) as usize,
767            b' ',
768        );
769        Ok(())
770    }
771
772    fn indent(&mut self) {
773        self.dent += 1;
774    }
775
776    fn dedent(&mut self) {
777        self.dent -= 1;
778    }
779}
780
781/// Writer Generator
782pub struct WriterGenerator<'w, W: 'w + Write> {
783    writer: &'w mut W,
784}
785
786impl<'w, W> WriterGenerator<'w, W>
787where
788    W: 'w + Write,
789{
790    /// Creates a new generator
791    pub fn new(writer: &'w mut W) -> Self {
792        WriterGenerator { writer }
793    }
794}
795
796impl<W> BaseGenerator for WriterGenerator<'_, W>
797where
798    W: Write,
799{
800    type T = W;
801
802    #[inline]
803    fn get_writer(&mut self) -> &mut W {
804        self.writer
805    }
806
807    #[inline]
808    fn write_min(&mut self, _: &[u8], min: u8) -> io::Result<()> {
809        self.writer.write_all(&[min])
810    }
811}
812
813/// Pretty Writer Generator
814pub struct PrettyWriterGenerator<'w, W>
815where
816    W: 'w + Write,
817{
818    writer: &'w mut W,
819    dent: u16,
820    spaces_per_indent: u16,
821}
822
823impl<'w, W> PrettyWriterGenerator<'w, W>
824where
825    W: 'w + Write,
826{
827    /// Creates a new generator
828    pub fn new(writer: &'w mut W, spaces_per_indent: u16) -> Self {
829        PrettyWriterGenerator {
830            writer,
831            dent: 0,
832            spaces_per_indent,
833        }
834    }
835}
836
837impl<W> BaseGenerator for PrettyWriterGenerator<'_, W>
838where
839    W: Write,
840{
841    type T = W;
842
843    #[inline]
844    fn get_writer(&mut self) -> &mut W {
845        self.writer
846    }
847
848    #[inline]
849    fn write_min(&mut self, slice: &[u8], _: u8) -> io::Result<()> {
850        self.writer.write_all(slice)
851    }
852
853    fn new_line(&mut self) -> io::Result<()> {
854        stry!(self.write_char(b'\n'));
855        for _ in 0..(self.dent * self.spaces_per_indent) {
856            stry!(self.write_char(b' '));
857        }
858        Ok(())
859    }
860
861    fn indent(&mut self) {
862        self.dent += 1;
863    }
864
865    fn dedent(&mut self) {
866        self.dent -= 1;
867    }
868}
869
870// From: https://github.com/dtolnay/fastwrite/blob/master/src/lib.rs#L68
871//
872// LLVM is not able to lower `Vec::extend_from_slice` into a memcpy, so this
873// helps eke out that last bit of performance.
874#[inline]
875pub(crate) fn extend_from_slice(dst: &mut Vec<u8>, src: &[u8]) {
876    let dst_len = dst.len();
877    let src_len = src.len();
878
879    dst.reserve(src_len);
880
881    unsafe {
882        // We would have failed if `reserve` overflowed\
883        ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr().add(dst_len), src_len);
884        dst.set_len(dst_len + src_len);
885    }
886}
887
888#[cfg(test)]
889mod tests {
890
891    #[test]
892    fn test_write_string_rust() {
893        let mut writer = Vec::new();
894        let mut string = "Hello, World!".as_bytes();
895        super::write_string_rust(&mut writer, &mut string).expect("failed to write string");
896        assert_eq!(writer, "Hello, World!".as_bytes());
897        assert!(string.is_empty(), "write_string_rust should advance string");
898    }
899    #[test]
900    fn test_write_string_rust2() {
901        let mut writer = Vec::new();
902        let mut string = "Hello, \"World!\"".as_bytes();
903        super::write_string_rust(&mut writer, &mut string).expect("failed to write string");
904        assert_eq!(writer, "Hello, \\\"World!\\\"".as_bytes());
905        assert!(string.is_empty(), "write_string_rust should advance string");
906    }
907
908    #[test]
909    fn test_write_string_long_with_escapes() {
910        use super::{BaseGenerator, DumpGenerator};
911        // Long string with escapes past the 32-byte SIMD boundary exercises
912        // write_string_content -> write_str_simd -> write_string_rust and
913        // catches the double-write bug if the fallback doesn't advance the string.
914        let input = "abcdefghijklmnopqrstuvwxyz012345\"escape here\"";
915        let mut g = DumpGenerator::new();
916        g.write_string(input).expect("write failed");
917        assert_eq!(
918            g.consume(),
919            "\"abcdefghijklmnopqrstuvwxyz012345\\\"escape here\\\"\""
920        );
921    }
922}