Skip to content

Commit 9de56dd

Browse files
committed
Test array argument in-place optimization (when the refcount is 1)
1 parent b915a1d commit 9de56dd

File tree

4 files changed

+364
-1
lines changed

4 files changed

+364
-1
lines changed
Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
--TEST--
2+
Test array argument in-place optimization (when the refcount is 1)
3+
--EXTENSIONS--
4+
zend_test
5+
--FILE--
6+
<?php
7+
8+
enum EN
9+
{
10+
case Y;
11+
case N;
12+
}
13+
14+
$listInt = [];
15+
$listStr = [];
16+
$mapInt = [];
17+
$mapObj = [];
18+
if (time() > 10) { // always true, but not const expr as time() is not CTE function
19+
$listInt[] = 1;
20+
$listInt[] = 2;
21+
$listStr[] = 'a';
22+
$mapInt[1] = 0;
23+
$mapObj['e'] = EN::Y;
24+
$mapObj['f'] = EN::N;
25+
}
26+
27+
$anotherList = [8, 9];
28+
29+
// the arrays now have GC_IMMUTABLE and GC_PERSISTENT flags cleared
30+
31+
echo "*** array_merge ***\n";
32+
33+
// const empty 2nd array
34+
$ptrBefore = zend_get_zval_ptr($listInt);
35+
$listInt = array_merge($listInt, []);
36+
$ptrAfter = zend_get_zval_ptr($listInt);
37+
var_dump($ptrAfter === $ptrBefore);
38+
39+
$ptrBefore = zend_get_zval_ptr($listStr);
40+
$listStr = array_merge($listStr, []);
41+
$ptrAfter = zend_get_zval_ptr($listStr);
42+
var_dump($ptrAfter === $ptrBefore);
43+
44+
$ptrBefore = zend_get_zval_ptr($mapInt);
45+
$mapInt = array_merge($mapInt, []);
46+
$ptrAfter = zend_get_zval_ptr($mapInt);
47+
var_dump($ptrAfter === $ptrBefore);
48+
49+
$ptrBefore = zend_get_zval_ptr($mapObj);
50+
$mapObj = array_merge($mapObj, []);
51+
$ptrAfter = zend_get_zval_ptr($mapObj);
52+
var_dump($ptrAfter === $ptrBefore);
53+
54+
// const non-empty 2nd array
55+
$ptrBefore = zend_get_zval_ptr($listInt);
56+
$listInt = array_merge($listInt, [4]);
57+
$ptrAfter = zend_get_zval_ptr($listInt);
58+
var_dump($ptrAfter === $ptrBefore);
59+
60+
$ptrBefore = zend_get_zval_ptr($listStr);
61+
$listStr = array_merge($listStr, [4]);
62+
$ptrAfter = zend_get_zval_ptr($listStr);
63+
var_dump($ptrAfter === $ptrBefore);
64+
65+
$ptrBefore = zend_get_zval_ptr($mapInt);
66+
$mapInt = array_merge($mapInt, [4]);
67+
$ptrAfter = zend_get_zval_ptr($mapInt);
68+
var_dump($ptrAfter === $ptrBefore);
69+
70+
$ptrBefore = zend_get_zval_ptr($mapObj);
71+
$mapObj = array_merge($mapObj, [4]);
72+
$ptrAfter = zend_get_zval_ptr($mapObj);
73+
var_dump($ptrAfter === $ptrBefore);
74+
75+
// non-const non-empty 2nd array
76+
$ptrBefore = zend_get_zval_ptr($listInt);
77+
$listInt = array_merge($listInt, $anotherList);
78+
$ptrAfter = zend_get_zval_ptr($listInt);
79+
var_dump($ptrAfter === $ptrBefore);
80+
81+
$ptrBefore = zend_get_zval_ptr($listStr);
82+
$listStr = array_merge($listStr, $anotherList);
83+
$ptrAfter = zend_get_zval_ptr($listStr);
84+
var_dump($ptrAfter === $ptrBefore);
85+
86+
$ptrBefore = zend_get_zval_ptr($mapInt);
87+
$mapInt = array_merge($mapInt, $anotherList);
88+
$ptrAfter = zend_get_zval_ptr($mapInt);
89+
var_dump($ptrAfter === $ptrBefore);
90+
91+
$ptrBefore = zend_get_zval_ptr($mapObj);
92+
$mapObj = array_merge($mapObj, $anotherList);
93+
$ptrAfter = zend_get_zval_ptr($mapObj);
94+
var_dump($ptrAfter === $ptrBefore);
95+
96+
// non-1st argument as a result
97+
$ptrBefore = zend_get_zval_ptr($listInt);
98+
$listInt = array_merge($anotherList, $listInt);
99+
$ptrAfter = zend_get_zval_ptr($listInt);
100+
var_dump($ptrAfter === $ptrBefore);
101+
102+
$ptrBefore = zend_get_zval_ptr($listStr);
103+
$listStr = array_merge($anotherList, $listStr);
104+
$ptrAfter = zend_get_zval_ptr($listStr);
105+
var_dump($ptrAfter === $ptrBefore);
106+
107+
$ptrBefore = zend_get_zval_ptr($mapInt);
108+
$mapInt = array_merge($anotherList, $mapInt);
109+
$ptrAfter = zend_get_zval_ptr($mapInt);
110+
var_dump($ptrAfter === $ptrBefore);
111+
112+
$ptrBefore = zend_get_zval_ptr($mapObj);
113+
$mapObj = array_merge($anotherList, $mapObj);
114+
$ptrAfter = zend_get_zval_ptr($mapObj);
115+
var_dump($ptrAfter === $ptrBefore);
116+
117+
echo "---\n";
118+
foreach ($listInt as $v) {
119+
$ptrBefore = zend_get_zval_ptr($listInt);
120+
$listInt = array_merge($listInt, [$v]); // 2nd and 3rd iteration must not copy the array
121+
$ptrAfter = zend_get_zval_ptr($listInt);
122+
var_dump($ptrAfter === $ptrBefore);
123+
124+
if ($v === 1) { // 3rd iteration
125+
break;
126+
}
127+
}
128+
foreach (array_keys($listInt) as $k) {
129+
$ptrBefore = zend_get_zval_ptr($listInt);
130+
$listInt = array_merge($listInt, [$listInt[$k]]); // array must never be copied
131+
$ptrAfter = zend_get_zval_ptr($listInt);
132+
var_dump($ptrAfter === $ptrBefore);
133+
134+
if ($listInt[$k] === 1) { // 3rd iteration
135+
break;
136+
}
137+
}
138+
139+
// TODO array_merge should be optimized to always return the 1st array zval if there is no element to be added
140+
141+
print_r($anotherList); // must not be modified
142+
print_r($listInt);
143+
print_r($listStr);
144+
print_r($mapInt);
145+
print_r($mapObj);
146+
147+
$listInt = array_slice($listInt, 0, 2, true);
148+
$listStr = array_slice($listStr, 0, 1, true);
149+
$mapInt = array_slice($mapInt, 0, 1, true);
150+
$mapObj = array_slice($mapObj, 0, 2, true);
151+
152+
153+
echo "*** array merge with unpacking ***\n";
154+
// TODO
155+
156+
echo "*** array_diff family ***\n";
157+
// TODO array_diff family should be optimized - https://github.com/php/php-src/pull/11060#discussion_r1175022196
158+
159+
echo "*** array_intersect family ***\n";
160+
$oneListInt = array_slice($listInt, 0, 1, true);
161+
$oneMapObj = array_slice($mapObj, 0, 1, true);
162+
163+
$listInt[] = -1;
164+
$ptrBefore = zend_get_zval_ptr($listInt);
165+
$listInt = array_intersect($listInt, $oneListInt);
166+
$ptrAfter = zend_get_zval_ptr($listInt);
167+
var_dump($ptrAfter === $ptrBefore);
168+
169+
$mapObj[] = -1;
170+
$ptrBefore = zend_get_zval_ptr($mapObj);
171+
$mapObj = array_intersect($mapObj, $oneMapObj);
172+
$ptrAfter = zend_get_zval_ptr($mapObj);
173+
var_dump($ptrAfter === $ptrBefore);
174+
175+
$listInt[] = -1;
176+
$ptrBefore = zend_get_zval_ptr($listInt);
177+
$listInt = array_intersect_assoc($listInt, $oneListInt);
178+
$ptrAfter = zend_get_zval_ptr($listInt);
179+
var_dump($ptrAfter === $ptrBefore);
180+
181+
$mapObj[] = -1;
182+
$ptrBefore = zend_get_zval_ptr($mapObj);
183+
$mapObj = array_intersect_assoc($mapObj, $oneMapObj);
184+
$ptrAfter = zend_get_zval_ptr($mapObj);
185+
var_dump($ptrAfter === $ptrBefore);
186+
187+
$listInt[] = -1;
188+
$ptrBefore = zend_get_zval_ptr($listInt);
189+
$listInt = array_intersect_key($listInt, $oneListInt);
190+
$ptrAfter = zend_get_zval_ptr($listInt);
191+
var_dump($ptrAfter === $ptrBefore);
192+
193+
$listInt[] = -1;
194+
$ptrBefore = zend_get_zval_ptr($listInt);
195+
$listInt = array_intersect_ukey($listInt, $oneListInt, fn ($k1, $k2) => $k1 <=> $k2);
196+
$ptrAfter = zend_get_zval_ptr($listInt);
197+
var_dump($ptrAfter === $ptrBefore);
198+
199+
$mapObj[] = -1;
200+
$ptrBefore = zend_get_zval_ptr($mapObj);
201+
$mapObj = array_intersect_ukey($mapObj, $oneListInt, fn ($k1, $k2) => $k1 <=> $k2);
202+
$ptrAfter = zend_get_zval_ptr($mapObj);
203+
var_dump($ptrAfter === $ptrBefore);
204+
205+
echo "*** array_unique ***\n";
206+
207+
$listInt[] = end($listInt);
208+
$ptrBefore = zend_get_zval_ptr($listInt);
209+
$listInt = array_unique($listInt);
210+
$ptrAfter = zend_get_zval_ptr($listInt);
211+
var_dump($ptrAfter === $ptrBefore);
212+
213+
$mapObj[] = end($mapObj);
214+
$ptrBefore = zend_get_zval_ptr($mapObj);
215+
$mapObj = array_unique($mapObj, SORT_REGULAR);
216+
$ptrAfter = zend_get_zval_ptr($mapObj);
217+
var_dump($ptrAfter === $ptrBefore);
218+
219+
echo "*** array_replace ***\n";
220+
221+
$listInt[] = end($listInt);
222+
$ptrBefore = zend_get_zval_ptr($listInt);
223+
$listInt = array_replace($listInt, ['*']);
224+
$ptrAfter = zend_get_zval_ptr($listInt);
225+
var_dump($ptrAfter === $ptrBefore);
226+
227+
$mapObj[] = end($mapObj);
228+
$ptrBefore = zend_get_zval_ptr($mapObj);
229+
$mapObj = array_replace($mapObj, ['*']);
230+
$ptrAfter = zend_get_zval_ptr($mapObj);
231+
var_dump($ptrAfter === $ptrBefore);
232+
233+
print_r($listInt);
234+
print_r($mapObj);
235+
236+
?>
237+
--EXPECT--
238+
*** array_merge ***
239+
bool(true)
240+
bool(true)
241+
bool(true)
242+
bool(true)
243+
bool(true)
244+
bool(true)
245+
bool(true)
246+
bool(true)
247+
bool(true)
248+
bool(true)
249+
bool(true)
250+
bool(true)
251+
bool(false)
252+
bool(false)
253+
bool(false)
254+
bool(false)
255+
---
256+
bool(false)
257+
bool(true)
258+
bool(true)
259+
bool(true)
260+
bool(true)
261+
bool(true)
262+
Array
263+
(
264+
[0] => 8
265+
[1] => 9
266+
)
267+
Array
268+
(
269+
[0] => 8
270+
[1] => 9
271+
[2] => 1
272+
[3] => 2
273+
[4] => 4
274+
[5] => 8
275+
[6] => 9
276+
[7] => 8
277+
[8] => 9
278+
[9] => 1
279+
[10] => 8
280+
[11] => 9
281+
[12] => 1
282+
)
283+
Array
284+
(
285+
[0] => 8
286+
[1] => 9
287+
[2] => a
288+
[3] => 4
289+
[4] => 8
290+
[5] => 9
291+
)
292+
Array
293+
(
294+
[0] => 8
295+
[1] => 9
296+
[2] => 0
297+
[3] => 4
298+
[4] => 8
299+
[5] => 9
300+
)
301+
Array
302+
(
303+
[0] => 8
304+
[1] => 9
305+
[e] => EN Enum
306+
(
307+
[name] => Y
308+
)
309+
310+
[f] => EN Enum
311+
(
312+
[name] => N
313+
)
314+
315+
[2] => 4
316+
[3] => 8
317+
[4] => 9
318+
)
319+
*** array merge with unpacking ***
320+
*** array_diff family ***
321+
*** array_intersect family ***
322+
bool(true)
323+
bool(true)
324+
bool(true)
325+
bool(true)
326+
bool(true)
327+
bool(true)
328+
bool(true)
329+
*** array_unique ***
330+
bool(true)
331+
bool(true)
332+
*** array_replace ***
333+
bool(true)
334+
bool(true)
335+
Array
336+
(
337+
[0] => *
338+
[1] => 8
339+
)
340+
Array
341+
(
342+
[0] => *
343+
[3] => 8
344+
)

ext/zend_test/test.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,17 @@ static ZEND_FUNCTION(zend_test_is_string_marked_as_valid_utf8)
435435
RETURN_BOOL(ZSTR_IS_VALID_UTF8(str));
436436
}
437437

438+
static ZEND_FUNCTION(zend_get_zval_ptr)
439+
{
440+
zval *v;
441+
442+
ZEND_PARSE_PARAMETERS_START(1, 1)
443+
Z_PARAM_ZVAL(value)
444+
ZEND_PARSE_PARAMETERS_END();
445+
446+
RETURN_LONG((zend_long) v);
447+
}
448+
438449
static ZEND_FUNCTION(ZendTestNS2_namespaced_func)
439450
{
440451
ZEND_PARSE_PARAMETERS_NONE();

ext/zend_test/test.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ function zend_get_map_ptr_last(): int {}
196196
function zend_test_crash(?string $message = null): void {}
197197

198198
function zend_test_fill_packed_array(array &$array): void {}
199+
200+
function zend_get_zval_ptr(mixed $variable): int {}
199201
}
200202

201203
namespace ZendTestNS {

0 commit comments

Comments
 (0)