From 941afb29f8d0d3bc8b67e937eeeeb66838c30521 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 28 May 2023 19:56:18 +0200 Subject: [PATCH] Add SSA optimization to remove useless FREE and result var stores The block optimization pass already removes FREEs, but this is limited to local tranformations only. This adds an SSA variant of that optimization. --- Zend/Optimizer/block_pass.c | 4 +- Zend/Optimizer/dfa_pass.c | 86 +++++++++++++++++++++++++++- ext/opcache/tests/opt/gh11245_2.phpt | 16 ++---- 3 files changed, 92 insertions(+), 14 deletions(-) diff --git a/Zend/Optimizer/block_pass.c b/Zend/Optimizer/block_pass.c index ccb32e2e453d4..16de94a13b836 100644 --- a/Zend/Optimizer/block_pass.c +++ b/Zend/Optimizer/block_pass.c @@ -262,7 +262,7 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array VAR_SOURCE(opline->op1) = NULL; } break; - +#if 0 case ZEND_FREE: /* Note: Only remove the source if the source is local to this block. * If it's not local, then the other blocks successors must also eventually either FREE or consume the temporary, @@ -330,7 +330,7 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array } } break; - +#endif #if 0 /* pre-evaluate functions: constant(x) diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index b1f568da5d920..660609704048a 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -30,9 +30,9 @@ #include "zend_inference.h" #include "zend_dump.h" -#ifndef ZEND_DEBUG_DFA +// #ifndef ZEND_DEBUG_DFA # define ZEND_DEBUG_DFA ZEND_DEBUG -#endif +// #endif #if ZEND_DEBUG_DFA # include "ssa_integrity.c" @@ -1076,6 +1076,84 @@ static bool zend_dfa_try_to_replace_result(zend_op_array *op_array, zend_ssa *ss return 0; } +static int zend_dfa_remove_only_free_uses(zend_op_array *op_array, zend_ssa *ssa) +{ + int times_applied = 0; + for (uint32_t i = 0; i < op_array->last; i++) { + zend_op *opline = op_array->opcodes + i; + if (opline->opcode != ZEND_FREE) { + continue; + } + int op1_use = ssa->ops[i].op1_use; + /* Possible if it's unreachable. */ + if (op1_use < 0) { + continue; + } + int definition = ssa->vars[op1_use].definition; + if (definition < 0) { + continue; + } + zend_op *defining_opline = op_array->opcodes + definition; + if (opline->op1_type == IS_TMP_VAR) { + switch (defining_opline->opcode) { + case ZEND_ASSIGN: + case ZEND_ASSIGN_DIM: + case ZEND_ASSIGN_OBJ: + case ZEND_ASSIGN_STATIC_PROP: + case ZEND_ASSIGN_OP: + case ZEND_ASSIGN_DIM_OP: + case ZEND_ASSIGN_OBJ_OP: + case ZEND_ASSIGN_STATIC_PROP_OP: + case ZEND_PRE_INC: + case ZEND_PRE_DEC: + case ZEND_PRE_INC_OBJ: + case ZEND_PRE_DEC_OBJ: + case ZEND_PRE_INC_STATIC_PROP: + case ZEND_PRE_DEC_STATIC_PROP: + break; + default: + continue; + } + } else if (opline->op1_type == IS_VAR) { + switch (defining_opline->opcode) { + case ZEND_FETCH_R: + case ZEND_FETCH_STATIC_PROP_R: + case ZEND_FETCH_DIM_R: + case ZEND_FETCH_OBJ_R: + case ZEND_NEW: + case ZEND_FETCH_THIS: + continue; + default: + break; + } + } + int use; + bool all_free = true; + int result_def = ssa->ops[definition].result_def; + if (result_def < 0) { + continue; + } + FOREACH_USE(ssa->vars + result_def, use) { + if (op_array->opcodes[use].opcode != ZEND_FREE) { + all_free = false; + break; + } + } FOREACH_USE_END(); + if (all_free) { + FOREACH_USE(ssa->vars + result_def, use) { + MAKE_NOP(op_array->opcodes + use); + ssa->ops[use].op1_use_chain = -1; + ssa->ops[use].op1_use = -1; + } FOREACH_USE_END(); + defining_opline->result_type = IS_UNUSED; + zend_ssa_remove_uses_of_var(ssa, result_def); + zend_ssa_remove_result_def(ssa, ssa->ops + definition); + times_applied++; + } + } + return times_applied; +} + void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, zend_call_info **call_map) { if (ctx->debug_level & ZEND_DUMP_BEFORE_DFA_PASS) { @@ -1094,6 +1172,10 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx ssa_verify_integrity(op_array, ssa, "before dfa"); #endif + if (zend_dfa_remove_only_free_uses(op_array, ssa)) { + remove_nops = 1; + } + if (ZEND_OPTIMIZER_PASS_8 & ctx->optimization_level) { if (sccp_optimize_op_array(ctx, op_array, ssa, call_map)) { remove_nops = 1; diff --git a/ext/opcache/tests/opt/gh11245_2.phpt b/ext/opcache/tests/opt/gh11245_2.phpt index 8e967bf9f41be..d00e497c7cc5b 100644 --- a/ext/opcache/tests/opt/gh11245_2.phpt +++ b/ext/opcache/tests/opt/gh11245_2.phpt @@ -21,15 +21,11 @@ switch (++X::$prop) { ?> --EXPECTF-- $_main: - ; (lines=7, args=0, vars=1, tmps=2) + ; (lines=5, args=0, vars=1, tmps=1) ; (after optimizer) ; %s -0000 T1 = PRE_INC_STATIC_PROP string("prop") string("X") -0001 T2 = ISSET_ISEMPTY_CV (empty) CV0($xx) -0002 JMPZ T2 0005 -0003 FREE T1 -0004 RETURN null -0005 FREE T1 -0006 RETURN int(1) -LIVE RANGES: - 1: 0001 - 0005 (tmp/var) +0000 PRE_INC_STATIC_PROP string("prop") string("X") +0001 T1 = ISSET_ISEMPTY_CV (empty) CV0($xx) +0002 JMPZ T1 0004 +0003 RETURN null +0004 RETURN int(1)