Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,13 @@ PHP 8.6 UPGRADE NOTES
and DirectoryIterator::current() returns string|SplFileInfo|static.

- Standard:
. During sort() and rsort() on packed arrays, implicit comparison callbacks
(such as __toString() and error handlers) can now read numeric indices of
the array and observe its partially sorted contents. Previously, these
reads reported an undefined array key warning and returned null because
the hash index was cleared during sorting. This applies to all array sizes
and comparison flags that invoke such callbacks. Arrays stored in mixed
form continue to use the previous sorting path.
. array_intersect() with at least two arrays now converts values to strings
while scanning its inputs instead of during sort comparisons. This can
change the number and order of conversion warnings and __toString() calls,
Expand Down Expand Up @@ -983,6 +990,8 @@ PHP 8.6 UPGRADE NOTES
. Improved performance of array_walk().
. Improved performance of intval('+0b...', 2) and intval('0b...', 2).
. Improved performance of str_split().
. sort(), rsort(), and usort() now avoid converting packed arrays to mixed
storage while sorting. usort() continues to sort a copy of the input array.

- URI:
. Improved performance of Uri\WhatWg\Url::parse() when collecting
Expand Down
13 changes: 13 additions & 0 deletions UPGRADING.INTERNALS
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,19 @@ PHP 8.6 INTERNALS UPGRADE NOTES
. New zend_class_entry.ce_flags2 and zend_function.fn_flags2 fields were
added, given the primary flags were running out of bits.
. Added zend_hash_str_lookup().
. Added zend_hash_sort_packed() to sort and renumber packed arrays in place
without converting them to mixed storage. The array must have a reference
count of 1. Holes are compacted and iterator positions are relocated,
including cursors at holes and one past the end. The comparator receives
zval pointers and must use the original positions initialized in
Z_EXTRA_P() to break ties stably. It must not execute user code or modify
the array. Empty arrays retain their internal pointer and next free element.
. Added zend_array_sort_packed() to sort and renumber packed user arrays
without converting them to mixed storage. The array must have a reference
count of 1. Holes are compacted, and the array is kept alive while the
comparator runs, including calls to user code. The comparator receives
zval pointers and must use the original positions initialized in
Z_EXTRA_P() to break ties stably.
. Added zend_ast_call_get_args() to fetch the argument node from any call
node.
. Added Z_PARAM_ENUM().
Expand Down
90 changes: 85 additions & 5 deletions Zend/zend_hash.c
Original file line number Diff line number Diff line change
Expand Up @@ -2990,6 +2990,90 @@ ZEND_API void zend_hash_bucket_packed_swap(Bucket *p, Bucket *q)
q->h = h;
}

static void zend_hash_packed_zval_swap(void *a, void *b)
{
zval tmp = *(zval *) a;
*(zval *) a = *(zval *) b;
*(zval *) b = tmp;
}

static void zend_hash_sort_packed_prepare(HashTable *ht)
{
uint32_t old_num_used = ht->nNumUsed;
uint32_t iter_pos = HT_INVALID_IDX;
uint32_t count = 0;
for (uint32_t i = 0; i < ht->nNumUsed; i++) {
zval *value = &ht->arPacked[i];
if (UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) {
if (count == i && UNEXPECTED(HT_HAS_ITERATORS(ht))) {
/* As in rehash, cursors at the first hole already have the right
* destination. Relocate subsequent cursors as values move. */
iter_pos = zend_hash_iterators_lower_pos(ht, i + 1);
}
continue;
}
if (count != i) {
ht->arPacked[count] = *value;
/* Comparators may observe current() before sorting resets the pointer. */
if (UNEXPECTED(ht->nInternalPointer > count && ht->nInternalPointer <= i)) {
ht->nInternalPointer = count;
}
if (UNEXPECTED(i >= iter_pos)) {
do {
zend_hash_iterators_update(ht, iter_pos, count);
iter_pos = zend_hash_iterators_lower_pos(ht, iter_pos + 1);
} while (iter_pos <= i);
}
}
Z_EXTRA(ht->arPacked[count]) = count;
count++;
}
ht->nNumUsed = count;
if (count != old_num_used && UNEXPECTED(HT_HAS_ITERATORS(ht))) {
/* A cursor at the old end must still see elements appended after sorting. */
_zend_hash_iterators_update(ht, old_num_used, count);
}
}

static void zend_hash_sort_packed_internal(HashTable *ht, compare_func_t compar)
{
IS_CONSISTENT(ht);
ZEND_ASSERT(HT_IS_PACKED(ht));
if (ht->nNumOfElements == 0) {
return;
}

/* Compact holes and record the original order for stable comparisons. */
zend_hash_sort_packed_prepare(ht);
zend_sort(ht->arPacked, ht->nNumUsed, sizeof(zval), compar, zend_hash_packed_zval_swap);
ht->nInternalPointer = 0;
ht->nNextFreeElement = ht->nNumUsed;
}

ZEND_API void ZEND_FASTCALL zend_hash_sort_packed(HashTable *ht, compare_func_t compar)
{
HT_ASSERT_RC1(ht);
zend_hash_sort_packed_internal(ht, compar);
}

static zend_always_inline void zend_array_sort_release(HashTable *ht)
{
if (UNEXPECTED(GC_DELREF(ht) == 0)) {
zend_array_destroy(ht);
} else {
gc_check_possible_root((zend_refcounted *) ht);
}
}

ZEND_API void ZEND_FASTCALL zend_array_sort_packed(HashTable *ht, compare_func_t compar)
{
HT_ASSERT_RC1(ht);
/* Keep the buffer alive and force PHP writes during comparison to separate. */
GC_ADDREF(ht);
zend_hash_sort_packed_internal(ht, compar);
zend_array_sort_release(ht);
}

static void zend_hash_sort_internal(HashTable *ht, sort_func_t sort, bucket_compare_func_t compar, bool renumber)
{
Bucket *p;
Expand Down Expand Up @@ -3102,11 +3186,7 @@ ZEND_API void ZEND_FASTCALL zend_array_sort_ex(HashTable *ht, sort_func_t sort,

zend_hash_sort_internal(ht, sort, compar, renumber);

if (UNEXPECTED(GC_DELREF(ht) == 0)) {
zend_array_destroy(ht);
} else {
gc_check_possible_root((zend_refcounted *)ht);
}
zend_array_sort_release(ht);
}

static zend_always_inline int zend_hash_compare_impl(const HashTable *ht1, const HashTable *ht2, compare_func_t compar, bool ordered) {
Expand Down
13 changes: 13 additions & 0 deletions Zend/zend_hash.h
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,19 @@ ZEND_API int zend_hash_compare(HashTable *ht1, const HashTable *ht2, compare_f
ZEND_API void ZEND_FASTCALL zend_hash_sort_ex(HashTable *ht, sort_func_t sort_func, bucket_compare_func_t compare_func, bool renumber);
ZEND_API void ZEND_FASTCALL zend_array_sort_ex(HashTable *ht, sort_func_t sort_func, bucket_compare_func_t compare_func, bool renumber);

/* Sort and renumber an exclusively owned packed array, compacting holes. The
* comparator receives zvals and must use the original positions initialized
* in Z_EXTRA_P() to break ties stably. It must not execute user code or modify
* the array. Compaction relocates iterators to their new positions, including
* cursors at holes and one past the end.
* Empty arrays retain their internal pointer and next free element. */
ZEND_API void ZEND_FASTCALL zend_hash_sort_packed(HashTable *ht, compare_func_t compare_func);

/* Sort and renumber a packed user array, compacting holes and keeping it alive
* across calls to user code. The comparator receives zvals and must use the
* original positions initialized in Z_EXTRA_P() to break ties stably. */
ZEND_API void ZEND_FASTCALL zend_array_sort_packed(HashTable *ht, compare_func_t compare_func);

static zend_always_inline void ZEND_FASTCALL zend_hash_sort(HashTable *ht, bucket_compare_func_t compare_func, bool renumber) {
zend_hash_sort_ex(ht, zend_sort, compare_func, renumber);
}
Expand Down
Loading
Loading