column_long and index_long might not be set, but are still used as arguments.
They are not actually used if column_str is set, but it's better to initialize
them anyway, if only to make MemorySanitizer happy.
This allows array_merge, array_intersect, array_replace, array_unique
and usort to avoid taking a copy and do the transformation in-place.
** Safety **
There are some array functions which take a copy of the input
array into a temporary C array for sorting purposes.
(e.g. array_unique, array_diff, and array_intersect do this).
Since we no longer take a copy in all cases, we must check if
it's possible that a value is accessed that was already destroyed.
For array_unique: cmpdata will never be removed so that will never reach
refcount 0. And when something is removed, it is the previous value of
cmpdata, not the one user later. So this seems okay.
For array_intersect: a previous pointer (ptr[0] - 1) is accessed.
But this can't be a destroyed value because the pointer is first moved forward.
For array_diff: it's possible a previous pointer is accessed after
destruction. So we can't optimise this case easily.
When using ZEND_NORMALIZE_BOOL(a - b) where a and b are doubles, this
generates the following instruction sequence on x64:
subsd xmm0, xmm1
pxor xmm1, xmm1
comisd xmm0, xmm1
...
whereas if we use ZEND_THREEWAY_COMPARE we get two instructions less:
ucomisd xmm0, xmm1
The only difference is that the threeway compare uses *u*comisd instead
of comisd. The difference is that it will cause a FP signal if a
signaling NAN is used, but as far as I'm aware this doesn't matter for
our use case.
Similarly, the amount of instructions on AArch64 is also quite a bit
lower for this code compared to the old code.
** Results **
Using the benchmark https://gist.github.com/nielsdos/b36517d81a1af74d96baa3576c2b70df
I used hyperfine: hyperfine --runs 25 --warmup 3 './sapi/cli/php sort_double.php'
No extensions such as opcache used during benchmarking.
BEFORE THIS PATCH
-----------------
Time (mean ± σ): 255.5 ms ± 2.2 ms [User: 251.0 ms, System: 2.5 ms]
Range (min … max): 251.5 ms … 260.7 ms 25 runs
AFTER THIS PATCH
----------------
Time (mean ± σ): 236.2 ms ± 2.8 ms [User: 228.9 ms, System: 5.0 ms]
Range (min … max): 231.5 ms … 242.7 ms 25 runs
(And any PECLs returning `zend_empty_array` in the handler->get_properties
overrides)
Closes GH-9697
This is similar to the fix used in d9651a9419
for array_walk.
This should make it safer for php-src (and PECLs, long-term) to return
the empty immutable array in `handler->get_properties` to avoid wasting memory.
See https://github.com/php/php-src/issues/9697#issuecomment-1273613175
The only possible internal iterator position for the empty array is at the end
of the empty array (nInternalPointer=0).
The `zend_hash*del*` helpers will always set nInternalPointer to 0 when an
array becomes empty,
regardless of previous insertions/deletions/updates to the array.
Remove array_pad's arbitrary length restriction
The error message was wrong; it *is* possible to use a larger length.
Furthermore, there is an arbitrary restriction on the new array's
length.
Fix both by checking the length against HT_MAX_SIZE.
The comparator function used at ksort in SORT_REGULAR mode
need to be consistent with basic comparison rules. These rules
were changed in PHP-8.0 for numeric strings, but comparator
used at ksort kept the old behaviour. It leads to inconsistent
situations, when after ksort the first key is GREATER than some
of the next ones by according to the basic comparison operators.
Closes GH-9293.
Whenever ->last_unsafe is set to `true` an exception has been thrown. Thus we
can replace the check for `->last_unsafe` with a check for `EG(exception)`
which is a much more natural way to ommunicate an error up the chain.
We fix the `UNEXPECTED(EXPECTED(…))`, which does not make sense, and
replace the magic number with the respective macro. We also add a
test case to verify the expected behavior for an `array_fill()` edge
case.
Closes GH-8804.
Make sure to destroy the old value only after assigning the new
one. Otherwise we may try to double free, e.g. if GC runs during
this dtor.
This caused an assertion failure in phpro/grumphp and is likely
the cause for bug #81603 as well.
- for packed arrays we store just an array of zvals without keys.
- the elements of packed array are accessible throuf as ht->arPacked[i]
instead of ht->arData[i]
- in addition to general ZEND_HASH_FOREACH_* macros, we introduced similar
familied for packed (ZEND_HASH_PACKED_FORECH_*) and real hashes
(ZEND_HASH_MAP_FOREACH_*)
- introduced an additional family of macros to access elements of array
(packed or real hashes) ZEND_ARRAY_ELEMET_SIZE, ZEND_ARRAY_ELEMET_EX,
ZEND_ARRAY_ELEMET, ZEND_ARRAY_NEXT_ELEMENT, ZEND_ARRAY_PREV_ELEMENT
- zend_hash_minmax() prototype was changed to compare only values
Because of smaller data set, this patch may show performance improvement
on some apps and benchmarks that use packed arrays. (~1% on PHP-Parser)
TODO:
- sapi/phpdbg needs special support for packed arrays (WATCH_ON_BUCKET).
- zend_hash_sort_ex() may require converting packed arrays to hash.