@@ -470,4 +470,122 @@ int64_t searchReverseU64(
470470 return searchU64Impl<true >(arr, start, end, target);
471471}
472472
473+ // ===----------------------------------------------------------------------===//
474+ // Special character scanning for JSON strings
475+ // ===----------------------------------------------------------------------===//
476+
477+ // / Scalar helper: scan for first byte that is '"', '\\', or <= 0x1F.
478+ static const char *scalarScanU8 (const char *p, const char *end) {
479+ while (p < end) {
480+ unsigned char ch = static_cast <unsigned char >(*p);
481+ if (ch <= 0x1F || ch == ' "' || ch == ' \\ ' )
482+ return p;
483+ ++p;
484+ }
485+ return end;
486+ }
487+
488+ // / Scalar helper: scan for first char16_t that is '"', '\\', or <= 0x1F.
489+ static const char16_t *scalarScanU16 (const char16_t *p, const char16_t *end) {
490+ while (p < end) {
491+ char16_t ch = *p;
492+ if (ch <= 0x1F || ch == u' "' || ch == u' \\ ' )
493+ return p;
494+ ++p;
495+ }
496+ return end;
497+ }
498+
499+ const char *scanJsonEscapeU8 (const char *start, const char *end) {
500+ assert (start <= end && " start must be <= end" );
501+ const char *p = start;
502+
503+ #ifdef HERMES_SIMD_NEON
504+ // Broadcast each special character/threshold into 128-bit vectors.
505+ uint8x16_t vQuote = vdupq_n_u8 (' "' );
506+ uint8x16_t vBackslash = vdupq_n_u8 (' \\ ' );
507+ uint8x16_t vCtrlLimit = vdupq_n_u8 (0x20 );
508+ while (p + 16 <= end) {
509+ uint8x16_t data = vld1q_u8 (reinterpret_cast <const uint8_t *>(p));
510+ // Check three conditions in parallel:
511+ // cmp1: byte == '"', cmp2: byte == '\\', cmp3: byte < 0x20 (control).
512+ uint8x16_t cmp1 = vceqq_u8 (data, vQuote);
513+ uint8x16_t cmp2 = vceqq_u8 (data, vBackslash);
514+ uint8x16_t cmp3 = vcltq_u8 (data, vCtrlLimit);
515+ // OR all three results; any non-zero lane means a special char exists.
516+ uint8x16_t match = vorrq_u8 (vorrq_u8 (cmp1, cmp2), cmp3);
517+ if (vmaxvq_u8 (match))
518+ // Fall back to scalar to find the exact byte within this chunk.
519+ return scalarScanU8 (p, p + 16 );
520+ p += 16 ;
521+ }
522+ #elif defined(HERMES_SIMD_SSE2)
523+ __m128i vQuote = _mm_set1_epi8 (' "' );
524+ __m128i vBackslash = _mm_set1_epi8 (' \\ ' );
525+ __m128i vCtrlLimit = _mm_set1_epi8 (0x20 );
526+ while (p + 16 <= end) {
527+ __m128i data = _mm_loadu_si128 (reinterpret_cast <const __m128i *>(p));
528+ __m128i cmp1 = _mm_cmpeq_epi8 (data, vQuote);
529+ __m128i cmp2 = _mm_cmpeq_epi8 (data, vBackslash);
530+ // _mm_cmplt_epi8 is signed comparison; this works for JSON because
531+ // all valid JSON string bytes are < 0x80 (high bit clear), so
532+ // signed < 0x20 is equivalent to unsigned < 0x20.
533+ __m128i cmp3 = _mm_cmplt_epi8 (data, vCtrlLimit);
534+ __m128i match = _mm_or_si128 (_mm_or_si128 (cmp1, cmp2), cmp3);
535+ // 1 mask bit per byte; countTrailingZeros gives exact byte offset.
536+ unsigned mask = static_cast <unsigned >(_mm_movemask_epi8 (match));
537+ if (mask)
538+ return p + llvh::countTrailingZeros (mask);
539+ p += 16 ;
540+ }
541+ #endif
542+
543+ return scalarScanU8 (p, end);
544+ }
545+
546+ const char16_t *scanJsonEscapeU16 (const char16_t *start, const char16_t *end) {
547+ assert (start <= end && " start must be <= end" );
548+ const char16_t *p = start;
549+
550+ #ifdef HERMES_SIMD_NEON
551+ // Same approach as scanJsonEscapeU8 but with 16-bit lanes (8 per chunk).
552+ uint16x8_t vQuote = vdupq_n_u16 (u' "' );
553+ uint16x8_t vBackslash = vdupq_n_u16 (u' \\ ' );
554+ uint16x8_t vCtrlLimit = vdupq_n_u16 (0x20 );
555+ while (p + 8 <= end) {
556+ uint16x8_t data = vld1q_u16 (reinterpret_cast <const uint16_t *>(p));
557+ uint16x8_t cmp1 = vceqq_u16 (data, vQuote);
558+ uint16x8_t cmp2 = vceqq_u16 (data, vBackslash);
559+ uint16x8_t cmp3 = vcltq_u16 (data, vCtrlLimit);
560+ uint16x8_t match = vorrq_u16 (vorrq_u16 (cmp1, cmp2), cmp3);
561+ if (vmaxvq_u16 (match))
562+ return scalarScanU16 (p, p + 8 );
563+ p += 8 ;
564+ }
565+ #elif defined(HERMES_SIMD_SSE2)
566+ __m128i vQuote = _mm_set1_epi16 (u' "' );
567+ __m128i vBackslash = _mm_set1_epi16 (u' \\ ' );
568+ // Bias for unsigned comparison: XOR with 0x8000 converts signed
569+ // _mm_cmplt_epi16 into an unsigned less-than, avoiding false positives
570+ // for char16_t values >= 0x8000 (e.g. CJK, surrogates).
571+ __m128i bias = _mm_set1_epi16 (static_cast <short >(0x8000 ));
572+ __m128i vCtrlLimitBiased = _mm_xor_si128 (_mm_set1_epi16 (0x20 ), bias);
573+ while (p + 8 <= end) {
574+ __m128i data = _mm_loadu_si128 (reinterpret_cast <const __m128i *>(p));
575+ __m128i cmp1 = _mm_cmpeq_epi16 (data, vQuote);
576+ __m128i cmp2 = _mm_cmpeq_epi16 (data, vBackslash);
577+ __m128i cmp3 = _mm_cmplt_epi16 (_mm_xor_si128 (data, bias), vCtrlLimitBiased);
578+ __m128i match = _mm_or_si128 (_mm_or_si128 (cmp1, cmp2), cmp3);
579+ // Each 16-bit lane produces 2 mask bits; divide by 2 for the
580+ // element index.
581+ unsigned mask = static_cast <unsigned >(_mm_movemask_epi8 (match));
582+ if (mask)
583+ return p + llvh::countTrailingZeros (mask) / 2 ;
584+ p += 8 ;
585+ }
586+ #endif
587+
588+ return scalarScanU16 (p, end);
589+ }
590+
473591} // namespace hermes
0 commit comments