diff --git a/ext/standard/tests/array/array_modify_arg_in_place_optimization.phpt b/ext/standard/tests/array/array_modify_arg_in_place_optimization.phpt new file mode 100644 index 0000000000000..bb8e8f4cede1d --- /dev/null +++ b/ext/standard/tests/array/array_modify_arg_in_place_optimization.phpt @@ -0,0 +1,344 @@ +--TEST-- +Test array argument in-place optimization +--EXTENSIONS-- +zend_test +--FILE-- + 10) { // always true, but not const expr as time() is not CTE function + $listInt[] = 1; + $listInt[] = 2; + $listStr[] = 'a'; + $mapInt[1] = 0; + $mapObj['e'] = EN::Y; + $mapObj['f'] = EN::N; +} + +$anotherList = [8, 9]; + +// the arrays now have GC_IMMUTABLE and GC_PERSISTENT flags cleared + +echo "*** array_merge ***\n"; + +// const empty 2nd array +$ptrBefore = zend_get_array_ptr($listInt); +$listInt = array_merge($listInt, []); +$ptrAfter = zend_get_array_ptr($listInt); +var_dump($ptrAfter === $ptrBefore); + +$ptrBefore = zend_get_array_ptr($listStr); +$listStr = array_merge($listStr, []); +$ptrAfter = zend_get_array_ptr($listStr); +var_dump($ptrAfter === $ptrBefore); + +$ptrBefore = zend_get_array_ptr($mapInt); +$mapInt = array_merge($mapInt, []); +$ptrAfter = zend_get_array_ptr($mapInt); +var_dump($ptrAfter === $ptrBefore); + +$ptrBefore = zend_get_array_ptr($mapObj); +$mapObj = array_merge($mapObj, []); +$ptrAfter = zend_get_array_ptr($mapObj); +var_dump($ptrAfter === $ptrBefore); + +// const non-empty 2nd array +$ptrBefore = zend_get_array_ptr($listInt); +$listInt = array_merge($listInt, [4]); +$ptrAfter = zend_get_array_ptr($listInt); +var_dump($ptrAfter === $ptrBefore); + +$ptrBefore = zend_get_array_ptr($listStr); +$listStr = array_merge($listStr, [4]); +$ptrAfter = zend_get_array_ptr($listStr); +var_dump($ptrAfter === $ptrBefore); + +$ptrBefore = zend_get_array_ptr($mapInt); +$mapInt = array_merge($mapInt, [4]); +$ptrAfter = zend_get_array_ptr($mapInt); +var_dump($ptrAfter === $ptrBefore); + +$ptrBefore = zend_get_array_ptr($mapObj); +$mapObj = array_merge($mapObj, [4]); +$ptrAfter = zend_get_array_ptr($mapObj); +var_dump($ptrAfter === $ptrBefore); + +// non-const non-empty 2nd array +$ptrBefore = zend_get_array_ptr($listInt); +$listInt = array_merge($listInt, $anotherList); +$ptrAfter = zend_get_array_ptr($listInt); +var_dump($ptrAfter === $ptrBefore); + +$ptrBefore = zend_get_array_ptr($listStr); +$listStr = array_merge($listStr, $anotherList); +$ptrAfter = zend_get_array_ptr($listStr); +var_dump($ptrAfter === $ptrBefore); + +$ptrBefore = zend_get_array_ptr($mapInt); +$mapInt = array_merge($mapInt, $anotherList); +$ptrAfter = zend_get_array_ptr($mapInt); +var_dump($ptrAfter === $ptrBefore); + +$ptrBefore = zend_get_array_ptr($mapObj); +$mapObj = array_merge($mapObj, $anotherList); +$ptrAfter = zend_get_array_ptr($mapObj); +var_dump($ptrAfter === $ptrBefore); + +// non-1st argument as a result +$ptrBefore = zend_get_array_ptr($listInt); +$listInt = array_merge($anotherList, $listInt); +$ptrAfter = zend_get_array_ptr($listInt); +var_dump($ptrAfter === $ptrBefore); + +$ptrBefore = zend_get_array_ptr($listStr); +$listStr = array_merge($anotherList, $listStr); +$ptrAfter = zend_get_array_ptr($listStr); +var_dump($ptrAfter === $ptrBefore); + +$ptrBefore = zend_get_array_ptr($mapInt); +$mapInt = array_merge($anotherList, $mapInt); +$ptrAfter = zend_get_array_ptr($mapInt); +var_dump($ptrAfter === $ptrBefore); + +$ptrBefore = zend_get_array_ptr($mapObj); +$mapObj = array_merge($anotherList, $mapObj); +$ptrAfter = zend_get_array_ptr($mapObj); +var_dump($ptrAfter === $ptrBefore); + +echo "---\n"; +foreach ($listInt as $v) { + $ptrBefore = zend_get_array_ptr($listInt); + $listInt = array_merge($listInt, [$v]); // 2nd and 3rd iteration must not copy the array + $ptrAfter = zend_get_array_ptr($listInt); + var_dump($ptrAfter === $ptrBefore); + + if ($v === 1) { // 3rd iteration + break; + } +} +foreach (array_keys($listInt) as $k) { + $ptrBefore = zend_get_array_ptr($listInt); + $listInt = array_merge($listInt, [$listInt[$k]]); // array must never be copied + $ptrAfter = zend_get_array_ptr($listInt); + var_dump($ptrAfter === $ptrBefore); + + if ($listInt[$k] === 1) { // 3rd iteration + break; + } +} + +// TODO array_merge should be optimized to always return the 1st array zval if there is no element to be added + +print_r($anotherList); // must not be modified +print_r($listInt); +print_r($listStr); +print_r($mapInt); +print_r($mapObj); + +$listInt = array_slice($listInt, 0, 2, true); +$listStr = array_slice($listStr, 0, 1, true); +$mapInt = array_slice($mapInt, 0, 1, true); +$mapObj = array_slice($mapObj, 0, 2, true); + + +echo "*** array merge with unpacking ***\n"; +// TODO + +echo "*** array_diff family ***\n"; +// TODO array_diff family should be optimized - https://github.com/php/php-src/pull/11060#discussion_r1175022196 + +echo "*** array_intersect family ***\n"; +$oneListInt = array_slice($listInt, 0, 1, true); +$oneMapObj = array_slice($mapObj, 0, 1, true); + +$listInt[] = -1; +$ptrBefore = zend_get_array_ptr($listInt); +$listInt = array_intersect($listInt, $oneListInt); +$ptrAfter = zend_get_array_ptr($listInt); +var_dump($ptrAfter === $ptrBefore); + +$mapObj[] = -1; +$ptrBefore = zend_get_array_ptr($mapObj); +$mapObj = array_intersect($mapObj, $oneMapObj); +$ptrAfter = zend_get_array_ptr($mapObj); +var_dump($ptrAfter === $ptrBefore); + +$listInt[] = -1; +$ptrBefore = zend_get_array_ptr($listInt); +$listInt = array_intersect_assoc($listInt, $oneListInt); +$ptrAfter = zend_get_array_ptr($listInt); +var_dump($ptrAfter === $ptrBefore); + +$mapObj[] = -1; +$ptrBefore = zend_get_array_ptr($mapObj); +$mapObj = array_intersect_assoc($mapObj, $oneMapObj); +$ptrAfter = zend_get_array_ptr($mapObj); +var_dump($ptrAfter === $ptrBefore); + +$listInt[] = -1; +$ptrBefore = zend_get_array_ptr($listInt); +$listInt = array_intersect_key($listInt, $oneListInt); +$ptrAfter = zend_get_array_ptr($listInt); +var_dump($ptrAfter === $ptrBefore); + +$listInt[] = -1; +$ptrBefore = zend_get_array_ptr($listInt); +$listInt = array_intersect_ukey($listInt, $oneListInt, fn ($k1, $k2) => $k1 <=> $k2); +$ptrAfter = zend_get_array_ptr($listInt); +var_dump($ptrAfter === $ptrBefore); + +$mapObj[] = -1; +$ptrBefore = zend_get_array_ptr($mapObj); +$mapObj = array_intersect_ukey($mapObj, $oneListInt, fn ($k1, $k2) => $k1 <=> $k2); +$ptrAfter = zend_get_array_ptr($mapObj); +var_dump($ptrAfter === $ptrBefore); + +echo "*** array_unique ***\n"; + +$listInt[] = end($listInt); +$ptrBefore = zend_get_array_ptr($listInt); +$listInt = array_unique($listInt); +$ptrAfter = zend_get_array_ptr($listInt); +var_dump($ptrAfter === $ptrBefore); + +$mapObj[] = end($mapObj); +$ptrBefore = zend_get_array_ptr($mapObj); +$mapObj = array_unique($mapObj, SORT_REGULAR); +$ptrAfter = zend_get_array_ptr($mapObj); +var_dump($ptrAfter === $ptrBefore); + +echo "*** array_replace ***\n"; + +$listInt[] = end($listInt); +$ptrBefore = zend_get_array_ptr($listInt); +$listInt = array_replace($listInt, ['*']); +$ptrAfter = zend_get_array_ptr($listInt); +var_dump($ptrAfter === $ptrBefore); + +$mapObj[] = end($mapObj); +$ptrBefore = zend_get_array_ptr($mapObj); +$mapObj = array_replace($mapObj, ['*']); +$ptrAfter = zend_get_array_ptr($mapObj); +var_dump($ptrAfter === $ptrBefore); + +print_r($listInt); +print_r($mapObj); + +?> +--EXPECT-- +*** array_merge *** +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +--- +bool(false) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +Array +( + [0] => 8 + [1] => 9 +) +Array +( + [0] => 8 + [1] => 9 + [2] => 1 + [3] => 2 + [4] => 4 + [5] => 8 + [6] => 9 + [7] => 8 + [8] => 9 + [9] => 1 + [10] => 8 + [11] => 9 + [12] => 1 +) +Array +( + [0] => 8 + [1] => 9 + [2] => a + [3] => 4 + [4] => 8 + [5] => 9 +) +Array +( + [0] => 8 + [1] => 9 + [2] => 0 + [3] => 4 + [4] => 8 + [5] => 9 +) +Array +( + [0] => 8 + [1] => 9 + [e] => EN Enum + ( + [name] => Y + ) + + [f] => EN Enum + ( + [name] => N + ) + + [2] => 4 + [3] => 8 + [4] => 9 +) +*** array merge with unpacking *** +*** array_diff family *** +*** array_intersect family *** +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +*** array_unique *** +bool(true) +bool(true) +*** array_replace *** +bool(true) +bool(true) +Array +( + [0] => * + [1] => 8 +) +Array +( + [0] => * + [3] => 8 +) diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 55ff614a51ace..3ff195da8ae30 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -435,6 +435,17 @@ static ZEND_FUNCTION(zend_test_is_string_marked_as_valid_utf8) RETURN_BOOL(ZSTR_IS_VALID_UTF8(str)); } +static ZEND_FUNCTION(zend_get_array_ptr) +{ + zval *v; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY(v) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_LONG((zend_long) Z_ARR_P(v)); +} + static ZEND_FUNCTION(ZendTestNS2_namespaced_func) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 30980cf7045f1..1831f6f12cd47 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -196,6 +196,8 @@ function zend_get_map_ptr_last(): int {} function zend_test_crash(?string $message = null): void {} function zend_test_fill_packed_array(array &$array): void {} + + function zend_get_array_ptr(array $array): int {} } namespace ZendTestNS { diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 6a01f6cd0169e..7fc696704efb8 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: eb6dd9bae381ca8163307e8a0f54bf3983b79cb5 */ + * Stub hash: e4453d06b517308d4081ee05fb12dc8b889b46bc */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -122,6 +122,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_fill_packed_array, 0, ZEND_ARG_TYPE_INFO(1, array, IS_ARRAY, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_get_array_ptr, 0, 1, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, array, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ZendTestNS2_namespaced_func, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() @@ -227,6 +231,7 @@ static ZEND_FUNCTION(zend_test_is_string_marked_as_valid_utf8); static ZEND_FUNCTION(zend_get_map_ptr_last); static ZEND_FUNCTION(zend_test_crash); static ZEND_FUNCTION(zend_test_fill_packed_array); +static ZEND_FUNCTION(zend_get_array_ptr); static ZEND_FUNCTION(ZendTestNS2_namespaced_func); static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func); static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func); @@ -289,6 +294,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_get_map_ptr_last, arginfo_zend_get_map_ptr_last) ZEND_FE(zend_test_crash, arginfo_zend_test_crash) ZEND_FE(zend_test_fill_packed_array, arginfo_zend_test_fill_packed_array) + ZEND_FE(zend_get_array_ptr, arginfo_zend_get_array_ptr) ZEND_NS_FALIAS("ZendTestNS2", namespaced_func, ZendTestNS2_namespaced_func, arginfo_ZendTestNS2_namespaced_func) ZEND_NS_DEP_FALIAS("ZendTestNS2", namespaced_deprecated_func, ZendTestNS2_namespaced_deprecated_func, arginfo_ZendTestNS2_namespaced_deprecated_func) ZEND_NS_FALIAS("ZendTestNS2", namespaced_aliased_func, zend_test_void_return, arginfo_ZendTestNS2_namespaced_aliased_func)