summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTyson Andre <tysonandre775@hotmail.com>2019-11-26 10:11:47 -0500
committerTyson Andre <tysonandre775@hotmail.com>2021-01-07 09:43:31 -0500
commitdf0fa5b1787c6b26b6abfb721d7b5a00b77a1fb6 (patch)
tree0d572098a3245fd8315aaaf86b660dcb62cc8eaa
parent3460af5262e268fb6f5150c33b38ae6ad42d9b95 (diff)
downloadphp-git-df0fa5b1787c6b26b6abfb721d7b5a00b77a1fb6.tar.gz
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 <nikita.ppv@gmail.com> using https://gist.github.com/nikic/58d367ad605e10299f5433d2d83a0b5b Closes GH-4949
-rw-r--r--ext/opcache/Optimizer/dce.c25
-rw-r--r--ext/opcache/Optimizer/zend_inference.c1
-rw-r--r--ext/opcache/tests/opt/dce_009.phpt71
3 files changed, 94 insertions, 3 deletions
diff --git a/ext/opcache/Optimizer/dce.c b/ext/opcache/Optimizer/dce.c
index 91c9665d1d..d21c40d189 100644
--- a/ext/opcache/Optimizer/dce.c
+++ b/ext/opcache/Optimizer/dce.c
@@ -82,6 +82,7 @@ static inline zend_bool may_have_side_effects(
case ZEND_IS_NOT_IDENTICAL:
case ZEND_QM_ASSIGN:
case ZEND_FREE:
+ case ZEND_FE_FREE:
case ZEND_TYPE_CHECK:
case ZEND_DEFINED:
case ZEND_ADD:
@@ -246,6 +247,11 @@ static inline zend_bool may_have_side_effects(
return 0;
case ZEND_CHECK_VAR:
return (OP1_INFO() & MAY_BE_UNDEF) != 0;
+ case ZEND_FE_RESET_R:
+ case ZEND_FE_RESET_RW:
+ /* Model as not having side-effects -- let the side-effect be introduced by
+ * FE_FETCH if the array is not known to be non-empty. */
+ return (OP1_INFO() & MAY_BE_ANY) != MAY_BE_ARRAY;
default:
/* For everything we didn't handle, assume a side-effect */
return 1;
@@ -373,6 +379,21 @@ static zend_bool try_remove_var_def(context *ctx, int free_var, int use_chain, z
return 0;
}
+static inline zend_bool is_free_of_live_var(context *ctx, zend_op *opline, zend_ssa_op *ssa_op) {
+ switch (opline->opcode) {
+ case ZEND_FREE:
+ /* It is always safe to remove FREEs of non-refcounted values, even if they are live. */
+ 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))) {
+ return 0;
+ }
+ /* break missing intentionally */
+ case ZEND_FE_FREE:
+ return !is_var_dead(ctx, ssa_op->op1_use);
+ default:
+ return 0;
+ }
+}
+
/* Returns whether the instruction has been DCEd */
static zend_bool dce_instr(context *ctx, zend_op *opline, zend_ssa_op *ssa_op) {
zend_ssa *ssa = ctx->ssa;
@@ -384,9 +405,7 @@ static zend_bool dce_instr(context *ctx, zend_op *opline, zend_ssa_op *ssa_op) {
}
/* We mark FREEs as dead, but they're only really dead if the destroyed var is dead */
- if (opline->opcode == ZEND_FREE
- && (ssa->var_info[ssa_op->op1_use].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))
- && !is_var_dead(ctx, ssa_op->op1_use)) {
+ if (is_free_of_live_var(ctx, opline, ssa_op)) {
return 0;
}
diff --git a/ext/opcache/Optimizer/zend_inference.c b/ext/opcache/Optimizer/zend_inference.c
index 0dd905e8f3..7bd40120db 100644
--- a/ext/opcache/Optimizer/zend_inference.c
+++ b/ext/opcache/Optimizer/zend_inference.c
@@ -4391,6 +4391,7 @@ int zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op, const ze
case ZEND_BEGIN_SILENCE:
case ZEND_END_SILENCE:
case ZEND_FREE:
+ case ZEND_FE_FREE:
case ZEND_SEPARATE:
case ZEND_TYPE_CHECK:
case ZEND_DEFINED:
diff --git a/ext/opcache/tests/opt/dce_009.phpt b/ext/opcache/tests/opt/dce_009.phpt
new file mode 100644
index 0000000000..0503614f34
--- /dev/null
+++ b/ext/opcache/tests/opt/dce_009.phpt
@@ -0,0 +1,71 @@
+--TEST--
+DCE 009: Foreach over empty array is a no-op
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+opcache.opt_debug_level=0x20000
+opcache.preload=
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+class Loop {
+ const VALUES = [];
+ public static function test() {
+ echo "Start\n";
+ $y = [];
+ foreach ($y as $x) {
+ }
+ echo "Done\n";
+ }
+ public static function test2() {
+ foreach (self::VALUES as $x) {
+ }
+ }
+ public static function test3() {
+ foreach ([] as $k => &$v) {
+ }
+ }
+}
+Loop::test();
+Loop::test2();
+Loop::test3();
+--EXPECTF--
+$_main:
+ ; (lines=7, args=0, vars=0, tmps=0)
+ ; (after optimizer)
+ ; %sdce_009.php:1-23
+0000 INIT_STATIC_METHOD_CALL 0 string("Loop") string("test")
+0001 DO_UCALL
+0002 INIT_STATIC_METHOD_CALL 0 string("Loop") string("test2")
+0003 DO_UCALL
+0004 INIT_STATIC_METHOD_CALL 0 string("Loop") string("test3")
+0005 DO_UCALL
+0006 RETURN int(1)
+
+Loop::test:
+ ; (lines=3, args=0, vars=0, tmps=0)
+ ; (after optimizer)
+ ; %sdce_009.php:4-10
+0000 ECHO string("Start
+")
+0001 ECHO string("Done
+")
+0002 RETURN null
+
+Loop::test2:
+ ; (lines=1, args=0, vars=0, tmps=0)
+ ; (after optimizer)
+ ; %sdce_009.php:11-14
+0000 RETURN null
+
+Loop::test3:
+ ; (lines=3, args=0, vars=0, tmps=1)
+ ; (after optimizer)
+ ; %sdce_009.php:15-18
+0000 V0 = FE_RESET_RW array(...) 0001
+0001 FE_FREE V0
+0002 RETURN null
+Start
+Done