@@ -846,7 +846,7 @@ static inline VALUE json_push_value(JSON_ParserState *state, JSON_ParserConfig *
846846 return value ;
847847}
848848
849- static const bool string_scan [256 ] = {
849+ static const bool string_scan_table [256 ] = {
850850 // ASCII Control Characters
851851 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
852852 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
@@ -865,97 +865,76 @@ static const bool string_scan[256] = {
865865#define FORCE_INLINE
866866#endif
867867
868- static inline bool FORCE_INLINE string_scan_basic (JSON_ParserState * state )
869- {
870- while (state -> cursor < state -> end ) {
871- if (RB_UNLIKELY (string_scan [(unsigned char )* state -> cursor ])) {
872- return 1 ;
873- }
874- * state -> cursor ++ ;
875- }
876- return 0 ;
877- }
878-
879- static bool (* string_scan_impl )(JSON_ParserState * );
868+ // Not used at the moment.
869+ static SIMD_Implementation simd_impl = SIMD_NONE ;
880870
881871#ifdef HAVE_SIMD
882- #ifdef HAVE_SIMD_NEON
883872
884- static inline FORCE_INLINE bool string_scan_neon ( JSON_ParserState * state )
873+ static inline FORCE_INLINE int has_next_vector ( void * state , size_t width )
885874{
886- while (state -> cursor + sizeof (uint8x16_t ) <= state -> end ) {
887- uint8x16_t chunk = vld1q_u8 ((const unsigned char * )state -> cursor );
888-
889- // Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33
890- // https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/
891- const uint8x16_t too_low_or_dbl_quote = vcltq_u8 (veorq_u8 (chunk , vdupq_n_u8 (2 )), vdupq_n_u8 (33 ));
892-
893- uint8x16_t has_backslash = vceqq_u8 (chunk , vdupq_n_u8 ('\\' ));
894- uint8x16_t needs_escape = vorrq_u8 (too_low_or_dbl_quote , has_backslash );
895- uint64_t mask = neon_match_mask (needs_escape );
896- if (mask ) {
897- state -> cursor += trailing_zeros64 (mask ) >> 2 ;
898- return true;
899- }
900-
901- state -> cursor += sizeof (uint8x16_t );
902- }
903-
904- if (state -> cursor < state -> end ) {
905- return string_scan_basic (state );
906- }
907- return 0 ;
875+ JSON_ParserState * s = state ;
876+ return s -> cursor + width <= s -> end ;
908877}
909- #endif /* HAVE_SIMD_NEON */
910-
911- #ifdef HAVE_SIMD_SSE2
912878
913- #define _mm_cmpge_epu8 (a , b ) _mm_cmpeq_epi8(_mm_max_epu8(a, b), a)
914- #define _mm_cmple_epu8 (a , b ) _mm_cmpge_epu8(b, a)
915- #define _mm_cmpgt_epu8 (a , b ) _mm_xor_si128(_mm_cmple_epu8(a, b), _mm_set1_epi8(-1))
916- #define _mm_cmplt_epu8 (a , b ) _mm_cmpgt_epu8(b, a)
879+ static inline FORCE_INLINE const char * ptr (void * state ) { return ((JSON_ParserState * ) state )-> cursor ; }
880+ static inline FORCE_INLINE void advance_by (void * state , size_t count ) { ((JSON_ParserState * ) state )-> cursor += count ; }
917881
918- #if defined(__clang__ ) || defined(__GNUC__ )
919- #define TARGET_SSE2 __attribute__((target("sse2")))
920- #else
921- #define TARGET_SSE2
922- #endif
882+ #ifdef HAVE_SIMD_NEON
883+ static inline FORCE_INLINE void set_match_mask (void * state , uint64_t mask ) { advance_by (state , trailing_zeros64 (mask ) >> 2 ); }
884+ #elif defined(HAVE_SIMD_SSE2 )
885+ static inline FORCE_INLINE void set_match_mask (void * state , int mask ) { advance_by (state , trailing_zeros (mask )); }
886+ #endif /* HAVE_SIMD_NEON */
887+ #endif /* HAVE_SIMD */
923888
924- static inline TARGET_SSE2 FORCE_INLINE bool string_scan_sse2 (JSON_ParserState * state )
889+ static inline bool FORCE_INLINE string_scan (JSON_ParserState * state )
925890{
926- while (state -> cursor + sizeof (__m128i ) <= state -> end ) {
927- __m128i chunk = _mm_loadu_si128 ((__m128i const * )state -> cursor );
928-
929- // Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33
930- // https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/
931- __m128i too_low_or_dbl_quote = _mm_cmplt_epu8 (_mm_xor_si128 (chunk , _mm_set1_epi8 (2 )), _mm_set1_epi8 (33 ));
932- __m128i has_backslash = _mm_cmpeq_epi8 (chunk , _mm_set1_epi8 ('\\' ));
933- __m128i needs_escape = _mm_or_si128 (too_low_or_dbl_quote , has_backslash );
934- int mask = _mm_movemask_epi8 (needs_escape );
935- if (mask ) {
936- state -> cursor += trailing_zeros (mask );
937- return true;
938- }
891+ #ifdef HAVE_SIMD
892+ #if defined(HAVE_SIMD_NEON )
893+ // Do not extract this structor to a global variable or make it static without profiling and
894+ // verifying that it does not cause performance regressions. clang 17 and gcc 14 currently
895+ // inline these methods if this structure is defined like this.
896+ NeonStringScanIterator iterator = {
897+ .has_next_vector = has_next_vector ,
898+ .ptr = ptr ,
899+ .advance_by = advance_by ,
900+ .set_match_mask = set_match_mask
901+ };
939902
940- state -> cursor += sizeof (__m128i );
903+ if (string_scan_simd_neon (& iterator , state )) {
904+ return 1 ;
905+ }
906+ #elif defined(HAVE_SIMD_SSE2 )
907+ if (simd_impl == SIMD_SSE2 ) {
908+ SSE2StringScanIterator iterator = {
909+ .has_next_vector = has_next_vector ,
910+ .ptr = ptr ,
911+ .advance_by = advance_by ,
912+ .set_match_mask = set_match_mask
913+ };
914+
915+ if (string_scan_simd_sse2 (& iterator , state )) {
916+ return 1 ;
917+ }
941918 }
919+ #endif /* HAVE_SIMD_NEON or HAVE_SIMD_SSE2 */
920+ #endif /* HAVE_SIMD */
942921
943- if (state -> cursor < state -> end ) {
944- return string_scan_basic (state );
922+ while (state -> cursor < state -> end ) {
923+ if (RB_UNLIKELY (string_scan_table [(unsigned char )* state -> cursor ])) {
924+ return 1 ;
925+ }
926+ * state -> cursor ++ ;
945927 }
946928 return 0 ;
947929}
948- #endif
949-
950- #endif /* HAVE_SIMD */
951930
952931static inline VALUE json_parse_string (JSON_ParserState * state , JSON_ParserConfig * config , bool is_name )
953932{
954933 state -> cursor ++ ;
955934 const char * start = state -> cursor ;
956935 bool escaped = false;
957936
958- while (RB_UNLIKELY (string_scan_impl (state ))) {
937+ while (RB_UNLIKELY (string_scan (state ))) {
959938 switch (* state -> cursor ) {
960939 case '"' : {
961940 VALUE string = json_decode_string (state , config , start , state -> cursor , escaped , is_name );
@@ -1504,21 +1483,7 @@ void Init_parser(void)
15041483 utf8_encindex = rb_utf8_encindex ();
15051484 enc_utf8 = rb_utf8_encoding ();
15061485
1507- switch (find_simd_implementation ()) {
15081486#ifdef HAVE_SIMD
1509- #ifdef HAVE_SIMD_NEON
1510- case SIMD_NEON :
1511- string_scan_impl = string_scan_neon ;
1512- break ;
1513- #endif /* HAVE_SIMD_NEON */
1514- #ifdef HAVE_SIMD_SSE2
1515- case SIMD_SSE2 :
1516- string_scan_impl = string_scan_sse2 ;
1517- break ;
1518- #endif /* HAVE_SIMD_SSE2 */
1519- #endif /* HAVE_SIMD */
1520- default :
1521- string_scan_impl = string_scan_basic ;
1522- break ;
1523- }
1487+ simd_impl = find_simd_implementation ();
1488+ #endif
15241489}
0 commit comments