@@ -526,14 +526,90 @@ typedef int (*php_json_compute_escape_intersection_t)(const __m128i mask, const
526526
527527ZEND_NO_SANITIZE_ADDRESS
528528ZEND_ATTRIBUTE_UNUSED /* clang mistakenly warns about this */
529- static php_json_compute_escape_intersection_t resolve_json_escape_intersection (void ) {
529+ static php_json_compute_escape_intersection_t resolve_json_escape_intersection (void ) { // TODO: rename
530530 if (zend_cpu_supports_sse42 ()) {
531531 return php_json_sse42_compute_escape_intersection_real ;
532532 }
533533 return php_json_sse2_compute_escape_intersection ;
534534}
535535#endif
536536
537+ #ifdef JSON_USE_SIMD
538+ typedef enum php_json_simd_result {
539+ PHP_JSON_STOP ,
540+ PHP_JSON_SLOW ,
541+ PHP_JSON_NON_ASCII ,
542+ } php_json_simd_result ;
543+
544+ static zend_always_inline php_json_simd_result php_json_process_simd_block (smart_str * buf , const __m128i sse_escape_mask , const char * * s , size_t * pos , size_t * len , int options )
545+ {
546+ while (* len >= sizeof (__m128i )) {
547+ const __m128i input = _mm_loadu_si128 ((const __m128i * ) (* s + * pos ));
548+ /* signed compare, so checks for unsigned bytes >= 0x80 as well */
549+ const __m128i input_range = _mm_cmplt_epi8 (input , _mm_set1_epi8 (32 ));
550+
551+ int max_shift = sizeof (__m128i );
552+
553+ int input_range_mask = _mm_movemask_epi8 (input_range );
554+ if (input_range_mask != 0 ) {
555+ if (UNEXPECTED (input_range_mask & 1 )) {
556+ /* not worth it */
557+ return PHP_JSON_NON_ASCII ;
558+ }
559+ max_shift = zend_ulong_ntz (input_range_mask );
560+ }
561+
562+ #ifdef ZEND_INTRIN_SSE4_2_NATIVE
563+ int mask = php_json_sse42_compute_escape_intersection_real (sse_escape_mask , input );
564+ #elif defined(ZEND_INTRIN_SSE4_2_FUNC_PROTO )
565+ int mask = php_json_sse42_compute_escape_intersection (sse_escape_mask , input );
566+ #else
567+ int mask = php_json_sse2_compute_escape_intersection (_mm_setzero_si128 (), input );
568+ #endif
569+ if (mask != 0 ) {
570+ if (UNEXPECTED (max_shift < sizeof (__m128i ))) {
571+ int shift = zend_ulong_ntz (mask ); /* first offending character */
572+ * pos += MIN (max_shift , shift );
573+ * len -= MIN (max_shift , shift );
574+ return PHP_JSON_SLOW ;
575+ }
576+
577+ php_json_append (buf , * s , * pos );
578+ * s += * pos ;
579+ const char * s_backup = * s ;
580+
581+ do {
582+ /* Note that we shift the input forward, so we have to shift the mask as well,
583+ * beyond the to-be-escaped character */
584+ int len = zend_ulong_ntz (mask );
585+ mask >>= len + 1 ;
586+
587+ php_json_append (buf , * s , len );
588+
589+ unsigned char us = (unsigned char )(* s )[len ];
590+ * s += len + 1 ; /* skip 'us' too */
591+
592+ bool handled = php_json_printable_ascii_escape (buf , us , options );
593+ ZEND_ASSERT (handled == true);
594+ } while (mask != 0 );
595+
596+ * pos = sizeof (__m128i ) - (* s - s_backup );
597+ } else {
598+ if (UNEXPECTED (max_shift < sizeof (__m128i ))) {
599+ * pos += max_shift ;
600+ * len -= max_shift ;
601+ return PHP_JSON_SLOW ;
602+ }
603+ * pos += sizeof (__m128i );
604+ }
605+
606+ * len -= sizeof (__m128i );
607+ }
608+
609+ return !* len ? PHP_JSON_STOP : PHP_JSON_SLOW ;
610+ }
611+ #endif
612+
537613zend_result php_json_escape_string (
538614 smart_str * buf , const char * s , size_t len ,
539615 int options , php_json_encoder * encoder ) /* {{{ */
@@ -581,81 +657,17 @@ zend_result php_json_escape_string(
581657 0xffffffff , 0xffffffff , 0xffffffff , 0xffffffff };
582658
583659#ifdef JSON_USE_SIMD
584- while (len >= sizeof (__m128i )) {
585- const __m128i input = _mm_loadu_si128 ((__m128i * ) (s + pos ));
586- /* signed compare, so checks for unsigned bytes >= 0x80 as well */
587- const __m128i input_range = _mm_cmplt_epi8 (input , _mm_set1_epi8 (32 ));
588-
589- int max_shift = sizeof (__m128i );
590-
591- int input_range_mask = _mm_movemask_epi8 (input_range );
592- if (input_range_mask != 0 ) {
593- if (UNEXPECTED (input_range_mask & 1 )) {
594- /* not worth it */
595- us = (unsigned char )s [pos ];
596- goto fallback ;
597- }
598- max_shift = zend_ulong_ntz (input_range_mask );
599- }
600-
601- #ifdef ZEND_INTRIN_SSE4_2_NATIVE
602- int mask = php_json_sse42_compute_escape_intersection_real (sse_escape_mask , input );
603- #elif defined(ZEND_INTRIN_SSE4_2_FUNC_PROTO )
604- int mask = php_json_sse42_compute_escape_intersection (sse_escape_mask , input );
605- #else
606- int mask = php_json_sse2_compute_escape_intersection (_mm_setzero_si128 (), input );
607- #endif
608- if (mask != 0 ) {
609- if (UNEXPECTED (max_shift < sizeof (__m128i ))) {
610- int shift = zend_ulong_ntz (mask ); /* first offending character */
611- pos += MIN (max_shift , shift );
612- len -= MIN (max_shift , shift );
613- break ;
614- }
615-
616- php_json_append (buf , s , pos );
617- s += pos ;
618- const char * s_backup = s ;
619-
620- do {
621- /* Note that we shift the input forward, so we have to shift the mask as well,
622- * beyond the to-be-escaped character */
623- int len = zend_ulong_ntz (mask );
624- mask >>= len + 1 ;
625-
626- php_json_append (buf , s , len );
627-
628- us = (unsigned char )s [len ];
629- s += len + 1 ; /* skip 'us' too */
630-
631- bool handled = php_json_printable_ascii_escape (buf , us , options );
632- ZEND_ASSERT (handled == true);
633- } while (mask != 0 );
634-
635- pos = sizeof (__m128i ) - (s - s_backup );
636- } else {
637- if (UNEXPECTED (max_shift < sizeof (__m128i ))) {
638- pos += max_shift ;
639- len -= max_shift ;
640- break ;
641- }
642- pos += sizeof (__m128i );
643- }
644-
645- len -= sizeof (__m128i );
646- }
647-
648- if (!len ) {
660+ php_json_simd_result result = php_json_process_simd_block (buf , sse_escape_mask , & s , & pos , & len , options );
661+ if (UNEXPECTED (result == PHP_JSON_STOP )) {
649662 break ;
650663 }
651664#endif
652665
653666 us = (unsigned char )s [pos ];
654- if (EXPECTED (!ZEND_BIT_TEST (charmap , us ))) {
667+ if (EXPECTED (result != PHP_JSON_NON_ASCII && !ZEND_BIT_TEST (charmap , us ))) {
655668 pos ++ ;
656669 len -- ;
657670 } else {
658- fallback :;
659671 if (UNEXPECTED (us >= 0x80 )) {
660672 zend_result status ;
661673 size_t pos_old = pos ;
0 commit comments