diff options
author | Tyson Andre <tysonandre775@hotmail.com> | 2019-11-26 10:11:47 -0500 |
---|---|---|
committer | Tyson Andre <tysonandre775@hotmail.com> | 2021-01-07 09:43:31 -0500 |
commit | df0fa5b1787c6b26b6abfb721d7b5a00b77a1fb6 (patch) | |
tree | 0d572098a3245fd8315aaaf86b660dcb62cc8eaa | |
parent | 3460af5262e268fb6f5150c33b38ae6ad42d9b95 (diff) | |
download | php-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.c | 25 | ||||
-rw-r--r-- | ext/opcache/Optimizer/zend_inference.c | 1 | ||||
-rw-r--r-- | ext/opcache/tests/opt/dce_009.phpt | 71 |
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 |