@@ -109,6 +109,16 @@ static zend_never_inline ZEND_COLD int stable_sort_fallback(Bucket *a, Bucket *b
109109static int php_array_compare_transitive (zval * op1 , zval * op2 );
110110
111111static int php_array_compare_transitive (zval * op1 , zval * op2 );
112+ static zend_always_inline int php_array_compare_non_numeric_strings (zend_string * s1 , zend_string * s2 )
113+ {
114+ size_t min_len = s1 -> len < s2 -> len ? s1 -> len : s2 -> len ;
115+ int cmp = memcmp (s1 -> val , s2 -> val , min_len );
116+ if (cmp != 0 ) {
117+ return cmp < 0 ? -1 : 1 ;
118+ }
119+ return ZEND_THREEWAY_COMPARE (s1 -> len , s2 -> len );
120+ }
121+
112122/* Generate inlined unstable and stable variants, and non-inlined reversed variants. */
113123#define DEFINE_SORT_VARIANTS (name ) \
114124 static zend_never_inline int php_array_##name##_unstable(Bucket *a, Bucket *b) { \
@@ -132,6 +142,10 @@ static zend_always_inline int php_array_key_compare_impl(Bucket *f, Bucket *s) /
132142 if (f -> key == NULL && s -> key == NULL ) {
133143 return (zend_long )f -> h > (zend_long )s -> h ? 1 : -1 ;
134144 } else if (f -> key && s -> key ) {
145+ if ((unsigned char )f -> key -> val [0 ] > '9'
146+ && (unsigned char )s -> key -> val [0 ] > '9' ) {
147+ return php_array_compare_non_numeric_strings (f -> key , s -> key );
148+ }
135149 return zendi_smart_strcmp (f -> key , s -> key );
136150 }
137151 if (f -> key ) {
@@ -296,26 +310,91 @@ static zend_always_inline int php_array_key_compare_string_locale_unstable_i(Buc
296310
297311static zend_always_inline int php_array_data_compare_impl (Bucket * f , Bucket * s ) /* {{{ */
298312{
299- int result = zend_compare (& f -> val , & s -> val );
300- /* Special enums handling for array_unique. We don't want to add this logic to zend_compare as
301- * that would be observable via comparison operators. */
302- zval * rhs = & s -> val ;
303- ZVAL_DEREF (rhs );
304- if (UNEXPECTED (Z_TYPE_P (rhs ) == IS_OBJECT )
305- && result == ZEND_UNCOMPARABLE
306- && (Z_OBJCE_P (rhs )-> ce_flags & ZEND_ACC_ENUM )) {
307- zval * lhs = & f -> val ;
308- ZVAL_DEREF (lhs );
309- if (Z_TYPE_P (lhs ) == IS_OBJECT && (Z_OBJCE_P (lhs )-> ce_flags & ZEND_ACC_ENUM )) {
310- // Order doesn't matter, we just need to group the same enum values
311- uintptr_t lhs_uintptr = (uintptr_t )Z_OBJ_P (lhs );
312- uintptr_t rhs_uintptr = (uintptr_t )Z_OBJ_P (rhs );
313+ zval * op1 = & f -> val ;
314+ zval * op2 = & s -> val ;
315+
316+ if (EXPECTED (Z_TYPE_P (op1 ) == IS_LONG && Z_TYPE_P (op2 ) == IS_LONG )) {
317+ return ZEND_THREEWAY_COMPARE (Z_LVAL_P (op1 ), Z_LVAL_P (op2 ));
318+ }
319+
320+ if (EXPECTED (Z_TYPE_P (op1 ) == IS_DOUBLE && Z_TYPE_P (op2 ) == IS_DOUBLE )) {
321+ return ZEND_THREEWAY_COMPARE (Z_DVAL_P (op1 ), Z_DVAL_P (op2 ));
322+ }
323+
324+ if (EXPECTED (Z_TYPE_P (op1 ) == IS_STRING && Z_TYPE_P (op2 ) == IS_STRING )) {
325+ zend_string * str1 = Z_STR_P (op1 );
326+ zend_string * str2 = Z_STR_P (op2 );
327+
328+ if ((unsigned char )str1 -> val [0 ] > '9'
329+ && (unsigned char )str2 -> val [0 ] > '9' ) {
330+ return php_array_compare_non_numeric_strings (str1 , str2 );
331+ }
332+
333+ return zendi_smart_strcmp (str1 , str2 );
334+ }
335+
336+ ZVAL_DEREF (op1 );
337+ ZVAL_DEREF (op2 );
338+
339+ if (Z_TYPE_P (op1 ) == IS_OBJECT && Z_TYPE_P (op2 ) == IS_OBJECT ) {
340+ if (Z_OBJ_P (op1 ) == Z_OBJ_P (op2 )) {
341+ return 0 ;
342+ }
343+
344+ if (Z_OBJCE_P (op1 ) != Z_OBJCE_P (op2 )) {
345+ bool lhs_enum = (Z_OBJCE_P (op1 )-> ce_flags & ZEND_ACC_ENUM ) != 0 ;
346+ bool rhs_enum = (Z_OBJCE_P (op2 )-> ce_flags & ZEND_ACC_ENUM ) != 0 ;
347+
348+ if (UNEXPECTED (lhs_enum || rhs_enum )) {
349+ if (lhs_enum && !rhs_enum ) {
350+ return 1 ;
351+ } else if (!lhs_enum && rhs_enum ) {
352+ return -1 ;
353+ }
354+ uintptr_t lhs_uintptr = (uintptr_t )Z_OBJ_P (op1 );
355+ uintptr_t rhs_uintptr = (uintptr_t )Z_OBJ_P (op2 );
356+ return lhs_uintptr == rhs_uintptr ? 0 : (lhs_uintptr < rhs_uintptr ? -1 : 1 );
357+ }
358+
359+ return ZEND_UNCOMPARABLE ;
360+ }
361+
362+ if (UNEXPECTED (Z_OBJCE_P (op1 )-> ce_flags & ZEND_ACC_ENUM )) {
363+ uintptr_t lhs_uintptr = (uintptr_t )Z_OBJ_P (op1 );
364+ uintptr_t rhs_uintptr = (uintptr_t )Z_OBJ_P (op2 );
313365 return lhs_uintptr == rhs_uintptr ? 0 : (lhs_uintptr < rhs_uintptr ? -1 : 1 );
314- } else {
315- // Shift enums to the end of the array
316- return -1 ;
317366 }
367+
368+ return zend_compare (op1 , op2 );
369+ }
370+
371+ if (Z_TYPE_P (op1 ) == IS_ARRAY && Z_TYPE_P (op2 ) == IS_ARRAY ) {
372+ if (Z_ARR_P (op1 ) == Z_ARR_P (op2 )) {
373+ return 0 ;
374+ }
375+
376+ uint32_t n1 = zend_hash_num_elements (Z_ARRVAL_P (op1 ));
377+ uint32_t n2 = zend_hash_num_elements (Z_ARRVAL_P (op2 ));
378+ if (n1 != n2 ) {
379+ return ZEND_THREEWAY_COMPARE (n1 , n2 );
380+ }
381+
382+ return zend_compare (op1 , op2 );
318383 }
384+
385+ int result = zend_compare (op1 , op2 );
386+ if (UNEXPECTED (result == ZEND_UNCOMPARABLE
387+ && Z_TYPE_P (op2 ) == IS_OBJECT
388+ && (Z_OBJCE_P (op2 )-> ce_flags & ZEND_ACC_ENUM ))) {
389+ if (Z_TYPE_P (op1 ) == IS_OBJECT && (Z_OBJCE_P (op1 )-> ce_flags & ZEND_ACC_ENUM )) {
390+ uintptr_t lhs_uintptr = (uintptr_t )Z_OBJ_P (op1 );
391+ uintptr_t rhs_uintptr = (uintptr_t )Z_OBJ_P (op2 );
392+ return lhs_uintptr == rhs_uintptr ? 0 : (lhs_uintptr < rhs_uintptr ? -1 : 1 );
393+ }
394+ /* Shift enums to the end of the array. */
395+ return -1 ;
396+ }
397+
319398 return result ;
320399}
321400/* }}} */
@@ -729,6 +808,10 @@ static zend_always_inline int php_array_key_compare_regular_unstable_i(Bucket *f
729808 if (f -> key == NULL && s -> key == NULL ) {
730809 return (zend_long )f -> h > (zend_long )s -> h ? 1 : -1 ;
731810 } else if (f -> key && s -> key ) {
811+ if ((unsigned char )f -> key -> val [0 ] > '9'
812+ && (unsigned char )s -> key -> val [0 ] > '9' ) {
813+ return php_array_compare_non_numeric_strings (f -> key , s -> key );
814+ }
732815 return php_array_smart_strcmp_transitive (f -> key , s -> key );
733816 }
734817 if (f -> key ) {
@@ -746,13 +829,36 @@ static zend_always_inline int php_array_key_compare_regular_unstable_i(Bucket *f
746829
747830static zend_always_inline int php_array_data_compare_regular_unstable_i (Bucket * f , Bucket * s )
748831{
749- int result = php_array_compare_regular (& f -> val , & s -> val );
750- zval * rhs = & s -> val ;
832+ zval * op1 = & f -> val ;
833+ zval * op2 = & s -> val ;
834+
835+ if (EXPECTED (Z_TYPE_P (op1 ) == IS_LONG && Z_TYPE_P (op2 ) == IS_LONG )) {
836+ return ZEND_THREEWAY_COMPARE (Z_LVAL_P (op1 ), Z_LVAL_P (op2 ));
837+ }
838+
839+ if (EXPECTED (Z_TYPE_P (op1 ) == IS_DOUBLE && Z_TYPE_P (op2 ) == IS_DOUBLE )) {
840+ return ZEND_THREEWAY_COMPARE (Z_DVAL_P (op1 ), Z_DVAL_P (op2 ));
841+ }
842+
843+ if (EXPECTED (Z_TYPE_P (op1 ) == IS_STRING && Z_TYPE_P (op2 ) == IS_STRING )) {
844+ zend_string * str1 = Z_STR_P (op1 );
845+ zend_string * str2 = Z_STR_P (op2 );
846+
847+ if ((unsigned char )str1 -> val [0 ] > '9'
848+ && (unsigned char )str2 -> val [0 ] > '9' ) {
849+ return php_array_compare_non_numeric_strings (str1 , str2 );
850+ }
851+
852+ return php_array_smart_strcmp_transitive (str1 , str2 );
853+ }
854+
855+ int result = php_array_compare_regular (op1 , op2 );
856+ zval * rhs = op2 ;
751857 ZVAL_DEREF (rhs );
752858 if (UNEXPECTED (Z_TYPE_P (rhs ) == IS_OBJECT )
753859 && result == ZEND_UNCOMPARABLE
754860 && (Z_OBJCE_P (rhs )-> ce_flags & ZEND_ACC_ENUM )) {
755- zval * lhs = & f -> val ;
861+ zval * lhs = op1 ;
756862 ZVAL_DEREF (lhs );
757863 if (Z_TYPE_P (lhs ) == IS_OBJECT && (Z_OBJCE_P (lhs )-> ce_flags & ZEND_ACC_ENUM )) {
758864 uintptr_t lhs_uintptr = (uintptr_t )Z_OBJ_P (lhs );
0 commit comments