From 725bbc7b621dca7f43eb69082e64012416e8c023 Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Tue, 26 Nov 2019 10:11:47 -0500 Subject: [PATCH] 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 using https://gist.github.com/nikic/58d367ad605e10299f5433d2d83a0b5b --- ext/opcache/Optimizer/dce.c | 25 +++++++-- ext/opcache/Optimizer/zend_inference.c | 1 + ext/opcache/tests/opt/dce_009.phpt | 71 ++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 ext/opcache/tests/opt/dce_009.phpt diff --git a/ext/opcache/Optimizer/dce.c b/ext/opcache/Optimizer/dce.c index 91c9665d1daae..d21c40d189cd5 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 0dd905e8f3de4..7bd40120dbb33 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 0000000000000..d914330c46511 --- /dev/null +++ b/ext/opcache/tests/opt/dce_009.phpt @@ -0,0 +1,71 @@ +--TEST-- +DFA 001: 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-- + +--FILE-- + &$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 \ No newline at end of file