Skip to content

Commit df0fa5b

Browse files
committed
Clean up remaining opcodes for foreach([] as $x)
Previously, two useless FE_RESET_R and FE_FREE would be left over whether the empty array was from a literal, a variable, or a class constant. This doesn't pick up the RESET_RW case due to a weakness in our "may throw" modeling. (for foreach by reference). Co-Authored-By: Nikita Popov <[email protected]> using https://gist.github.com/nikic/58d367ad605e10299f5433d2d83a0b5b Closes phpGH-4949
1 parent 3460af5 commit df0fa5b

File tree

3 files changed

+94
-3
lines changed

3 files changed

+94
-3
lines changed

ext/opcache/Optimizer/dce.c

+22-3
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ static inline zend_bool may_have_side_effects(
8282
case ZEND_IS_NOT_IDENTICAL:
8383
case ZEND_QM_ASSIGN:
8484
case ZEND_FREE:
85+
case ZEND_FE_FREE:
8586
case ZEND_TYPE_CHECK:
8687
case ZEND_DEFINED:
8788
case ZEND_ADD:
@@ -246,6 +247,11 @@ static inline zend_bool may_have_side_effects(
246247
return 0;
247248
case ZEND_CHECK_VAR:
248249
return (OP1_INFO() & MAY_BE_UNDEF) != 0;
250+
case ZEND_FE_RESET_R:
251+
case ZEND_FE_RESET_RW:
252+
/* Model as not having side-effects -- let the side-effect be introduced by
253+
* FE_FETCH if the array is not known to be non-empty. */
254+
return (OP1_INFO() & MAY_BE_ANY) != MAY_BE_ARRAY;
249255
default:
250256
/* For everything we didn't handle, assume a side-effect */
251257
return 1;
@@ -373,6 +379,21 @@ static zend_bool try_remove_var_def(context *ctx, int free_var, int use_chain, z
373379
return 0;
374380
}
375381

382+
static inline zend_bool is_free_of_live_var(context *ctx, zend_op *opline, zend_ssa_op *ssa_op) {
383+
switch (opline->opcode) {
384+
case ZEND_FREE:
385+
/* It is always safe to remove FREEs of non-refcounted values, even if they are live. */
386+
if (!(ctx->ssa->var_info[ssa_op->op1_use].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
387+
return 0;
388+
}
389+
/* break missing intentionally */
390+
case ZEND_FE_FREE:
391+
return !is_var_dead(ctx, ssa_op->op1_use);
392+
default:
393+
return 0;
394+
}
395+
}
396+
376397
/* Returns whether the instruction has been DCEd */
377398
static zend_bool dce_instr(context *ctx, zend_op *opline, zend_ssa_op *ssa_op) {
378399
zend_ssa *ssa = ctx->ssa;
@@ -384,9 +405,7 @@ static zend_bool dce_instr(context *ctx, zend_op *opline, zend_ssa_op *ssa_op) {
384405
}
385406

386407
/* We mark FREEs as dead, but they're only really dead if the destroyed var is dead */
387-
if (opline->opcode == ZEND_FREE
388-
&& (ssa->var_info[ssa_op->op1_use].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))
389-
&& !is_var_dead(ctx, ssa_op->op1_use)) {
408+
if (is_free_of_live_var(ctx, opline, ssa_op)) {
390409
return 0;
391410
}
392411

ext/opcache/Optimizer/zend_inference.c

+1
Original file line numberDiff line numberDiff line change
@@ -4391,6 +4391,7 @@ int zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op, const ze
43914391
case ZEND_BEGIN_SILENCE:
43924392
case ZEND_END_SILENCE:
43934393
case ZEND_FREE:
4394+
case ZEND_FE_FREE:
43944395
case ZEND_SEPARATE:
43954396
case ZEND_TYPE_CHECK:
43964397
case ZEND_DEFINED:

ext/opcache/tests/opt/dce_009.phpt

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
--TEST--
2+
DCE 009: Foreach over empty array is a no-op
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.optimization_level=-1
7+
opcache.opt_debug_level=0x20000
8+
opcache.preload=
9+
--SKIPIF--
10+
<?php require_once('skipif.inc'); ?>
11+
--FILE--
12+
<?php
13+
class Loop {
14+
const VALUES = [];
15+
public static function test() {
16+
echo "Start\n";
17+
$y = [];
18+
foreach ($y as $x) {
19+
}
20+
echo "Done\n";
21+
}
22+
public static function test2() {
23+
foreach (self::VALUES as $x) {
24+
}
25+
}
26+
public static function test3() {
27+
foreach ([] as $k => &$v) {
28+
}
29+
}
30+
}
31+
Loop::test();
32+
Loop::test2();
33+
Loop::test3();
34+
--EXPECTF--
35+
$_main:
36+
; (lines=7, args=0, vars=0, tmps=0)
37+
; (after optimizer)
38+
; %sdce_009.php:1-23
39+
0000 INIT_STATIC_METHOD_CALL 0 string("Loop") string("test")
40+
0001 DO_UCALL
41+
0002 INIT_STATIC_METHOD_CALL 0 string("Loop") string("test2")
42+
0003 DO_UCALL
43+
0004 INIT_STATIC_METHOD_CALL 0 string("Loop") string("test3")
44+
0005 DO_UCALL
45+
0006 RETURN int(1)
46+
47+
Loop::test:
48+
; (lines=3, args=0, vars=0, tmps=0)
49+
; (after optimizer)
50+
; %sdce_009.php:4-10
51+
0000 ECHO string("Start
52+
")
53+
0001 ECHO string("Done
54+
")
55+
0002 RETURN null
56+
57+
Loop::test2:
58+
; (lines=1, args=0, vars=0, tmps=0)
59+
; (after optimizer)
60+
; %sdce_009.php:11-14
61+
0000 RETURN null
62+
63+
Loop::test3:
64+
; (lines=3, args=0, vars=0, tmps=1)
65+
; (after optimizer)
66+
; %sdce_009.php:15-18
67+
0000 V0 = FE_RESET_RW array(...) 0001
68+
0001 FE_FREE V0
69+
0002 RETURN null
70+
Start
71+
Done

0 commit comments

Comments
 (0)