mirror of
https://github.com/php/php-src.git
synced 2025-08-15 13:38:49 +02:00
Initial integration of Dead Code Elimination (DCE) and unused variable removing passes, originally developed in https://github.com/nikic/php-src/tree/opt, into DFA optimization pass.
This commit is contained in:
parent
d1eb5ede3a
commit
8c0de53e5f
11 changed files with 1179 additions and 2 deletions
112
ext/opcache/Optimizer/compact_vars.c
Normal file
112
ext/opcache/Optimizer/compact_vars.c
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| Zend Engine, Removing unused variables |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 1998-2017 The PHP Group |
|
||||
+----------------------------------------------------------------------+
|
||||
| This source file is subject to version 3.01 of the PHP license, |
|
||||
| that is bundled with this package in the file LICENSE, and is |
|
||||
| available through the world-wide-web at the following url: |
|
||||
| http://www.php.net/license/3_01.txt |
|
||||
| If you did not receive a copy of the PHP license and are unable to |
|
||||
| obtain it through the world-wide-web, please send a note to |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
| Authors: Nikita Popov <nikic@php.net> |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#include "ZendAccelerator.h"
|
||||
#include "Optimizer/zend_optimizer_internal.h"
|
||||
#include "zend_bitset.h"
|
||||
|
||||
/* This pass removes all CVs that are completely unused. It does *not* merge any CVs.
|
||||
* This pass does not operate on SSA form anymore. */
|
||||
void zend_optimizer_compact_vars(zend_op_array *op_array) {
|
||||
int i;
|
||||
|
||||
ALLOCA_FLAG(use_heap1);
|
||||
ALLOCA_FLAG(use_heap2);
|
||||
uint32_t used_cvs_len = zend_bitset_len(op_array->last_var);
|
||||
zend_bitset used_cvs = ZEND_BITSET_ALLOCA(used_cvs_len, use_heap1);
|
||||
uint32_t *cv_map = do_alloca(op_array->last_var * sizeof(uint32_t), use_heap2);
|
||||
uint32_t num_cvs, tmp_offset;
|
||||
|
||||
/* Determine which CVs are used */
|
||||
zend_bitset_clear(used_cvs, used_cvs_len);
|
||||
for (i = 0; i < op_array->last; i++) {
|
||||
zend_op *opline = &op_array->opcodes[i];
|
||||
if (opline->op1_type == IS_CV) {
|
||||
zend_bitset_incl(used_cvs, VAR_NUM(opline->op1.var));
|
||||
}
|
||||
if (opline->op2_type == IS_CV) {
|
||||
zend_bitset_incl(used_cvs, VAR_NUM(opline->op2.var));
|
||||
}
|
||||
if (opline->result_type == IS_CV) {
|
||||
zend_bitset_incl(used_cvs, VAR_NUM(opline->result.var));
|
||||
}
|
||||
}
|
||||
|
||||
num_cvs = 0;
|
||||
for (i = 0; i < op_array->last_var; i++) {
|
||||
if (zend_bitset_in(used_cvs, i)) {
|
||||
cv_map[i] = num_cvs++;
|
||||
} else {
|
||||
cv_map[i] = (uint32_t) -1;
|
||||
}
|
||||
}
|
||||
|
||||
free_alloca(used_cvs, use_heap1);
|
||||
if (num_cvs == op_array->last_var) {
|
||||
free_alloca(cv_map, use_heap2);
|
||||
return;
|
||||
}
|
||||
|
||||
ZEND_ASSERT(num_cvs < op_array->last_var);
|
||||
tmp_offset = op_array->last_var - num_cvs;
|
||||
|
||||
/* Update CV and TMP references in opcodes */
|
||||
for (i = 0; i < op_array->last; i++) {
|
||||
zend_op *opline = &op_array->opcodes[i];
|
||||
if (opline->op1_type == IS_CV) {
|
||||
opline->op1.var = NUM_VAR(cv_map[VAR_NUM(opline->op1.var)]);
|
||||
} else if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) {
|
||||
opline->op1.var -= sizeof(zval) * tmp_offset;
|
||||
}
|
||||
if (opline->op2_type == IS_CV) {
|
||||
opline->op2.var = NUM_VAR(cv_map[VAR_NUM(opline->op2.var)]);
|
||||
} else if (opline->op2_type & (IS_VAR|IS_TMP_VAR)) {
|
||||
opline->op2.var -= sizeof(zval) * tmp_offset;
|
||||
}
|
||||
if (opline->result_type == IS_CV) {
|
||||
opline->result.var = NUM_VAR(cv_map[VAR_NUM(opline->result.var)]);
|
||||
} else if (opline->result_type & (IS_VAR|IS_TMP_VAR)) {
|
||||
opline->result.var -= sizeof(zval) * tmp_offset;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update TMP references in live ranges */
|
||||
if (op_array->live_range) {
|
||||
for (i = 0; i < op_array->last_live_range; i++) {
|
||||
op_array->live_range[i].var -= sizeof(zval) * tmp_offset;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update CV name table */
|
||||
{
|
||||
zend_string **names = safe_emalloc(sizeof(zend_string *), num_cvs, 0);
|
||||
for (i = 0; i < op_array->last_var; i++) {
|
||||
if (cv_map[i] != (uint32_t) -1) {
|
||||
names[cv_map[i]] = op_array->vars[i];
|
||||
} else {
|
||||
zend_string_release(op_array->vars[i]);
|
||||
}
|
||||
}
|
||||
efree(op_array->vars);
|
||||
op_array->vars = names;
|
||||
}
|
||||
|
||||
op_array->last_var = num_cvs;
|
||||
|
||||
free_alloca(cv_map, use_heap2);
|
||||
}
|
548
ext/opcache/Optimizer/dce.c
Normal file
548
ext/opcache/Optimizer/dce.c
Normal file
|
@ -0,0 +1,548 @@
|
|||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| Zend Engine, DCE - Dead Code Elimination |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 1998-2017 The PHP Group |
|
||||
+----------------------------------------------------------------------+
|
||||
| This source file is subject to version 3.01 of the PHP license, |
|
||||
| that is bundled with this package in the file LICENSE, and is |
|
||||
| available through the world-wide-web at the following url: |
|
||||
| http://www.php.net/license/3_01.txt |
|
||||
| If you did not receive a copy of the PHP license and are unable to |
|
||||
| obtain it through the world-wide-web, please send a note to |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
| Authors: Nikita Popov <nikic@php.net> |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#include "ZendAccelerator.h"
|
||||
#include "Optimizer/zend_optimizer_internal.h"
|
||||
#include "Optimizer/zend_inference.h"
|
||||
#include "Optimizer/zend_ssa.h"
|
||||
#include "Optimizer/zend_func_info.h"
|
||||
#include "Optimizer/zend_call_graph.h"
|
||||
#include "zend_bitset.h"
|
||||
|
||||
/* This pass implements a form of dead code elimination (DCE). The algorithm optimistically assumes
|
||||
* that all instructions and phis are dead. Instructions with immediate side-effects are then marked
|
||||
* as live. We then recursively (using a worklist) propagate liveness to the instructions that def
|
||||
* the used operands.
|
||||
*
|
||||
* Notes:
|
||||
* * This pass does not perform unreachable code elimination. This happens as part of the SCCP
|
||||
* pass.
|
||||
* * The DCE is performed without taking control-dependence into account, i.e. all conditional
|
||||
* branches are assumed to be live. It's possible to take control-dependence into account using
|
||||
* the DCE algorithm described by Cytron et al., however it requires the construction of a
|
||||
* postdominator tree and of postdominance frontiers, which does not seem worthwhile at this
|
||||
* point.
|
||||
* * We separate intrinsic side-effects from potential side-effects in the form of notices thrown
|
||||
* by the instruction (in case we want to make this configurable). See may_have_side_effect() and
|
||||
* zend_may_throw().
|
||||
* * We often cannot DCE assignments and unsets while guaranteeing that dtors run in the same
|
||||
* order. There is an optimization option to allow reordering of dtor effects.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
zend_ssa *ssa;
|
||||
zend_op_array *op_array;
|
||||
zend_bitset instr_dead;
|
||||
zend_bitset phi_dead;
|
||||
zend_bitset instr_worklist;
|
||||
zend_bitset phi_worklist;
|
||||
uint32_t instr_worklist_len;
|
||||
uint32_t phi_worklist_len;
|
||||
unsigned reorder_dtor_effects : 1;
|
||||
} context;
|
||||
|
||||
static inline zend_bool is_bad_mod(const zend_ssa *ssa, int use, int def) {
|
||||
if (def < 0) {
|
||||
/* This modification is not tracked by SSA, assume the worst */
|
||||
return 1;
|
||||
}
|
||||
if (ssa->var_info[use].type & MAY_BE_REF) {
|
||||
/* Modification of reference may have side-effect */
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline zend_bool may_have_side_effects(
|
||||
const context *ctx, const zend_op *opline, const zend_ssa_op *ssa_op) {
|
||||
zend_op_array *op_array = ctx->op_array;
|
||||
zend_ssa *ssa = ctx->ssa;
|
||||
if (zend_may_throw(opline, op_array, ssa)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
switch (opline->opcode) {
|
||||
case ZEND_NOP:
|
||||
case ZEND_IS_IDENTICAL:
|
||||
case ZEND_IS_NOT_IDENTICAL:
|
||||
case ZEND_QM_ASSIGN:
|
||||
case ZEND_FREE:
|
||||
case ZEND_TYPE_CHECK:
|
||||
case ZEND_DEFINED:
|
||||
case ZEND_ADD:
|
||||
case ZEND_SUB:
|
||||
case ZEND_MUL:
|
||||
case ZEND_POW:
|
||||
case ZEND_BW_OR:
|
||||
case ZEND_BW_AND:
|
||||
case ZEND_BW_XOR:
|
||||
case ZEND_CONCAT:
|
||||
case ZEND_FAST_CONCAT:
|
||||
case ZEND_DIV:
|
||||
case ZEND_MOD:
|
||||
case ZEND_BOOL_XOR:
|
||||
case ZEND_BOOL:
|
||||
case ZEND_BOOL_NOT:
|
||||
case ZEND_BW_NOT:
|
||||
case ZEND_SL:
|
||||
case ZEND_SR:
|
||||
case ZEND_IS_EQUAL:
|
||||
case ZEND_IS_NOT_EQUAL:
|
||||
case ZEND_IS_SMALLER:
|
||||
case ZEND_IS_SMALLER_OR_EQUAL:
|
||||
case ZEND_CASE:
|
||||
case ZEND_CAST:
|
||||
case ZEND_ROPE_INIT:
|
||||
case ZEND_ROPE_ADD:
|
||||
case ZEND_ROPE_END:
|
||||
case ZEND_INIT_ARRAY:
|
||||
case ZEND_ADD_ARRAY_ELEMENT:
|
||||
/* No side effects */
|
||||
return 0;
|
||||
case ZEND_JMP:
|
||||
case ZEND_JMPZ:
|
||||
case ZEND_JMPNZ:
|
||||
case ZEND_JMPZNZ:
|
||||
case ZEND_JMPZ_EX:
|
||||
case ZEND_JMPNZ_EX:
|
||||
case ZEND_JMP_SET:
|
||||
case ZEND_COALESCE:
|
||||
case ZEND_ASSERT_CHECK:
|
||||
/* For our purposes a jumps and branches are side effects. */
|
||||
return 1;
|
||||
case ZEND_BEGIN_SILENCE:
|
||||
case ZEND_END_SILENCE:
|
||||
case ZEND_ECHO:
|
||||
case ZEND_INCLUDE_OR_EVAL:
|
||||
case ZEND_THROW:
|
||||
case ZEND_EXT_STMT:
|
||||
case ZEND_EXT_FCALL_BEGIN:
|
||||
case ZEND_EXT_FCALL_END:
|
||||
case ZEND_EXT_NOP:
|
||||
case ZEND_TICKS:
|
||||
case ZEND_YIELD:
|
||||
case ZEND_YIELD_FROM:
|
||||
/* Intrinsic side effects */
|
||||
return 1;
|
||||
case ZEND_DO_FCALL:
|
||||
case ZEND_DO_FCALL_BY_NAME:
|
||||
case ZEND_DO_ICALL:
|
||||
case ZEND_DO_UCALL:
|
||||
/* For now assume all calls have side effects */
|
||||
return 1;
|
||||
case ZEND_RECV:
|
||||
case ZEND_RECV_INIT:
|
||||
/* Even though RECV_INIT can be side-effect free, these cannot be simply dropped
|
||||
* due to the prologue skipping code. */
|
||||
return 1;
|
||||
case ZEND_ASSIGN_REF:
|
||||
return 1;
|
||||
case ZEND_ASSIGN:
|
||||
{
|
||||
uint32_t t1 = OP1_INFO();
|
||||
if (is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def)) {
|
||||
return 1;
|
||||
}
|
||||
if (!ctx->reorder_dtor_effects) {
|
||||
if (t1 & MAY_HAVE_DTOR) {
|
||||
/* DCE might extend lifetime */
|
||||
return 1;
|
||||
}
|
||||
if (opline->op2_type != IS_CONST && (OP2_INFO() & MAY_HAVE_DTOR)) {
|
||||
/* DCE might shorten lifetime */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case ZEND_UNSET_VAR:
|
||||
{
|
||||
uint32_t t1 = OP1_INFO();
|
||||
if (!(opline->extended_value & ZEND_QUICK_SET)) {
|
||||
return 1;
|
||||
}
|
||||
if (t1 & MAY_BE_REF) {
|
||||
/* We don't consider uses as the LHS of an assignment as real uses during DCE, so
|
||||
* an unset may be considered dead even if there is a later assignment to the
|
||||
* variable. Removing the unset in this case would not be correct if the variable
|
||||
* is a reference, because unset breaks references. */
|
||||
return 1;
|
||||
}
|
||||
if (!ctx->reorder_dtor_effects && (t1 & MAY_HAVE_DTOR)) {
|
||||
/* DCE might extend lifetime */
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case ZEND_PRE_INC:
|
||||
case ZEND_POST_INC:
|
||||
case ZEND_PRE_DEC:
|
||||
case ZEND_POST_DEC:
|
||||
return is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def);
|
||||
case ZEND_ASSIGN_ADD:
|
||||
case ZEND_ASSIGN_SUB:
|
||||
case ZEND_ASSIGN_MUL:
|
||||
case ZEND_ASSIGN_DIV:
|
||||
case ZEND_ASSIGN_MOD:
|
||||
case ZEND_ASSIGN_SL:
|
||||
case ZEND_ASSIGN_SR:
|
||||
case ZEND_ASSIGN_CONCAT:
|
||||
case ZEND_ASSIGN_BW_OR:
|
||||
case ZEND_ASSIGN_BW_AND:
|
||||
case ZEND_ASSIGN_BW_XOR:
|
||||
case ZEND_ASSIGN_POW:
|
||||
if (opline->extended_value) {
|
||||
/* ASSIGN_DIM has no side-effect, but we can't deal with OP_DATA anyway */
|
||||
return 1;
|
||||
}
|
||||
return is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def);
|
||||
default:
|
||||
/* For everything we didn't handle, assume a side-effect */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void add_to_worklists(context *ctx, int var_num) {
|
||||
zend_ssa_var *var = &ctx->ssa->vars[var_num];
|
||||
if (var->definition >= 0) {
|
||||
if (zend_bitset_in(ctx->instr_dead, var->definition)) {
|
||||
zend_bitset_incl(ctx->instr_worklist, var->definition);
|
||||
}
|
||||
} else if (var->definition_phi) {
|
||||
if (zend_bitset_in(ctx->phi_dead, var_num)) {
|
||||
zend_bitset_incl(ctx->phi_worklist, var_num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void add_to_phi_worklist_only(context *ctx, int var_num) {
|
||||
zend_ssa_var *var = &ctx->ssa->vars[var_num];
|
||||
if (var->definition_phi && zend_bitset_in(ctx->phi_dead, var_num)) {
|
||||
zend_bitset_incl(ctx->phi_worklist, var_num);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void add_operands_to_worklists(context *ctx, zend_op *opline, zend_ssa_op *ssa_op) {
|
||||
if (ssa_op->result_use >= 0) {
|
||||
add_to_worklists(ctx, ssa_op->result_use);
|
||||
}
|
||||
if (ssa_op->op1_use >= 0 && !zend_has_improper_op1_use(opline)) {
|
||||
add_to_worklists(ctx, ssa_op->op1_use);
|
||||
}
|
||||
if (ssa_op->op2_use >= 0) {
|
||||
add_to_worklists(ctx, ssa_op->op2_use);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void add_phi_sources_to_worklists(context *ctx, zend_ssa_phi *phi) {
|
||||
zend_ssa *ssa = ctx->ssa;
|
||||
int source;
|
||||
FOREACH_PHI_SOURCE(phi, source) {
|
||||
add_to_worklists(ctx, source);
|
||||
} FOREACH_PHI_SOURCE_END();
|
||||
}
|
||||
|
||||
static inline zend_bool is_var_dead(context *ctx, int var_num) {
|
||||
zend_ssa_var *var = &ctx->ssa->vars[var_num];
|
||||
if (var->definition_phi) {
|
||||
return zend_bitset_in(ctx->phi_dead, var_num);
|
||||
} else if (var->definition >= 0) {
|
||||
return zend_bitset_in(ctx->instr_dead, var->definition);
|
||||
} else {
|
||||
/* Variable has no definition, so either the definition has already been removed (var is
|
||||
* dead) or this is one of the implicit variables at the start of the function (for our
|
||||
* purposes live) */
|
||||
return var_num >= ctx->op_array->last_var;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
int free_var = -1;
|
||||
zend_uchar free_var_type;
|
||||
|
||||
if (opline->opcode == ZEND_NOP) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We mark FREEs as dead, but they're only really dead if the destroyed var is dead */
|
||||
if (opline->opcode == ZEND_FREE && !is_var_dead(ctx, ssa_op->op1_use)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO Two free vars?
|
||||
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) && !is_var_dead(ctx, ssa_op->op1_use)) {
|
||||
free_var = ssa_op->op1_use;
|
||||
free_var_type = opline->op1_type;
|
||||
} else if ((opline->op2_type & (IS_VAR|IS_TMP_VAR)) && !is_var_dead(ctx, ssa_op->op2_use)) {
|
||||
free_var = ssa_op->op2_use;
|
||||
free_var_type = opline->op2_type;
|
||||
}
|
||||
|
||||
zend_ssa_rename_defs_of_instr(ctx->ssa, ssa_op);
|
||||
zend_ssa_remove_instr(ctx->ssa, opline, ssa_op);
|
||||
|
||||
if (free_var >= 0) {
|
||||
// TODO Sometimes we can mark the var as EXT_UNUSED
|
||||
opline->opcode = ZEND_FREE;
|
||||
opline->op1.var = (uintptr_t) ZEND_CALL_VAR_NUM(NULL, ssa->vars[free_var].var);
|
||||
opline->op1_type = free_var_type;
|
||||
|
||||
ssa_op->op1_use = free_var;
|
||||
ssa_op->op1_use_chain = ssa->vars[free_var].use_chain;
|
||||
ssa->vars[free_var].use_chain = ssa_op - ssa->ops;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// TODO Move this somewhere else (CFG simplification?)
|
||||
static int simplify_jumps(zend_ssa *ssa, zend_op_array *op_array) {
|
||||
int removed_ops = 0;
|
||||
zend_basic_block *block;
|
||||
FOREACH_BLOCK(block) {
|
||||
int block_num = block - ssa->cfg.blocks;
|
||||
zend_op *opline = &op_array->opcodes[block->start + block->len - 1];
|
||||
zend_ssa_op *ssa_op = &ssa->ops[block->start + block->len - 1];
|
||||
zval *op1;
|
||||
|
||||
if (block->len == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Convert jump-and-set into jump if result is not used */
|
||||
switch (opline->opcode) {
|
||||
case ZEND_JMPZ_EX:
|
||||
ZEND_ASSERT(ssa_op->result_def >= 0);
|
||||
if (ssa->vars[ssa_op->result_def].use_chain < 0
|
||||
&& ssa->vars[ssa_op->result_def].phi_use_chain == NULL) {
|
||||
opline->opcode = ZEND_JMPZ;
|
||||
opline->result_type = IS_UNUSED;
|
||||
zend_ssa_remove_result_def(ssa, ssa_op);
|
||||
}
|
||||
break;
|
||||
case ZEND_JMPNZ_EX:
|
||||
case ZEND_JMP_SET:
|
||||
ZEND_ASSERT(ssa_op->result_def >= 0);
|
||||
if (ssa->vars[ssa_op->result_def].use_chain < 0
|
||||
&& ssa->vars[ssa_op->result_def].phi_use_chain == NULL) {
|
||||
opline->opcode = ZEND_JMPNZ;
|
||||
opline->result_type = IS_UNUSED;
|
||||
zend_ssa_remove_result_def(ssa, ssa_op);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Convert jump-and-set to QM_ASSIGN/BOOL if the "else" branch is not taken. */
|
||||
switch (opline->opcode) {
|
||||
case ZEND_JMPZ_EX:
|
||||
case ZEND_JMPNZ_EX:
|
||||
if (block->successors[1] < 0 && block->successors[0] != block_num + 1) {
|
||||
opline->opcode = ZEND_BOOL;
|
||||
}
|
||||
break;
|
||||
case ZEND_JMP_SET:
|
||||
case ZEND_COALESCE:
|
||||
if (block->successors[1] < 0 && block->successors[0] != block_num + 1) {
|
||||
opline->opcode = ZEND_QM_ASSIGN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (opline->op1_type != IS_CONST) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Convert constant conditional jump to unconditional jump */
|
||||
op1 = &ZEND_OP1_LITERAL(opline);
|
||||
switch (opline->opcode) {
|
||||
case ZEND_JMPZ:
|
||||
if (!zend_is_true(op1)) {
|
||||
literal_dtor(op1);
|
||||
opline->op1_type = IS_UNUSED;
|
||||
opline->op1.num = opline->op2.num;
|
||||
opline->opcode = ZEND_JMP;
|
||||
} else {
|
||||
MAKE_NOP(opline);
|
||||
removed_ops++;
|
||||
}
|
||||
break;
|
||||
case ZEND_JMPNZ:
|
||||
if (zend_is_true(op1)) {
|
||||
literal_dtor(op1);
|
||||
opline->op1_type = IS_UNUSED;
|
||||
opline->op1.num = opline->op2.num;
|
||||
opline->opcode = ZEND_JMP;
|
||||
} else {
|
||||
MAKE_NOP(opline);
|
||||
removed_ops++;
|
||||
}
|
||||
break;
|
||||
case ZEND_COALESCE:
|
||||
ZEND_ASSERT(ssa_op->result_def >= 0);
|
||||
if (ssa->vars[ssa_op->result_def].use_chain >= 0
|
||||
|| ssa->vars[ssa_op->result_def].phi_use_chain != NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
zend_ssa_remove_result_def(ssa, ssa_op);
|
||||
if (Z_TYPE_P(op1) != IS_NULL) {
|
||||
literal_dtor(op1);
|
||||
opline->op1_type = IS_UNUSED;
|
||||
opline->op1.num = opline->op2.num;
|
||||
opline->opcode = ZEND_JMP;
|
||||
opline->result_type = IS_UNUSED;
|
||||
} else {
|
||||
MAKE_NOP(opline);
|
||||
removed_ops++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} FOREACH_BLOCK_END();
|
||||
return removed_ops;
|
||||
}
|
||||
|
||||
static inline int get_common_phi_source(zend_ssa *ssa, zend_ssa_phi *phi) {
|
||||
int common_source = -1;
|
||||
int source;
|
||||
FOREACH_PHI_SOURCE(phi, source) {
|
||||
if (common_source == -1) {
|
||||
common_source = source;
|
||||
} else if (common_source != source && source != phi->ssa_var) {
|
||||
return -1;
|
||||
}
|
||||
} FOREACH_PHI_SOURCE_END();
|
||||
ZEND_ASSERT(common_source != -1);
|
||||
return common_source;
|
||||
}
|
||||
|
||||
static void try_remove_trivial_phi(context *ctx, zend_ssa_phi *phi) {
|
||||
zend_ssa *ssa = ctx->ssa;
|
||||
if (phi->pi < 0) {
|
||||
/* Phi assignment with identical source operands */
|
||||
int common_source = get_common_phi_source(ssa, phi);
|
||||
if (common_source >= 0) {
|
||||
zend_ssa_rename_var_uses(ssa, phi->ssa_var, common_source, 1);
|
||||
zend_ssa_remove_phi(ssa, phi);
|
||||
}
|
||||
} else {
|
||||
/* Pi assignment that is only used in Phi/Pi assignments */
|
||||
// TODO What if we want to rerun type inference after DCE? Maybe separate this?
|
||||
/*ZEND_ASSERT(phi->sources[0] != -1);
|
||||
if (ssa->vars[phi->ssa_var].use_chain < 0) {
|
||||
zend_ssa_rename_var_uses_keep_types(ssa, phi->ssa_var, phi->sources[0], 1);
|
||||
zend_ssa_remove_phi(ssa, phi);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
int dce_optimize_op_array(zend_op_array *op_array, zend_ssa *ssa, zend_bool reorder_dtor_effects) {
|
||||
int i;
|
||||
zend_ssa_phi *phi;
|
||||
int removed_ops = 0;
|
||||
|
||||
/* DCE of CV operations may affect vararg functions. For now simply treat all instructions
|
||||
* as live if varargs in use and only collect dead phis. */
|
||||
zend_bool has_varargs = (ZEND_FUNC_INFO(op_array)->flags & ZEND_FUNC_VARARG) != 0;
|
||||
|
||||
context ctx;
|
||||
ctx.ssa = ssa;
|
||||
ctx.op_array = op_array;
|
||||
ctx.reorder_dtor_effects = reorder_dtor_effects;
|
||||
|
||||
/* We have no dedicated phi vector, so we use the whole ssa var vector instead */
|
||||
ctx.instr_worklist_len = zend_bitset_len(op_array->last);
|
||||
ctx.instr_worklist = alloca(sizeof(zend_ulong) * ctx.instr_worklist_len);
|
||||
memset(ctx.instr_worklist, 0, sizeof(zend_ulong) * ctx.instr_worklist_len);
|
||||
ctx.phi_worklist_len = zend_bitset_len(ssa->vars_count);
|
||||
ctx.phi_worklist = alloca(sizeof(zend_ulong) * ctx.phi_worklist_len);
|
||||
memset(ctx.phi_worklist, 0, sizeof(zend_ulong) * ctx.phi_worklist_len);
|
||||
|
||||
/* Optimistically assume all instructions and phis to be dead */
|
||||
ctx.instr_dead = alloca(sizeof(zend_ulong) * ctx.instr_worklist_len);
|
||||
memset(ctx.instr_dead, 0xff, sizeof(zend_ulong) * ctx.instr_worklist_len);
|
||||
ctx.phi_dead = alloca(sizeof(zend_ulong) * ctx.phi_worklist_len);
|
||||
memset(ctx.phi_dead, 0xff, sizeof(zend_ulong) * ctx.phi_worklist_len);
|
||||
|
||||
/* Mark instruction with side effects as live */
|
||||
FOREACH_INSTR_NUM(i) {
|
||||
if (may_have_side_effects(&ctx, &op_array->opcodes[i], &ssa->ops[i]) || has_varargs) {
|
||||
zend_bitset_excl(ctx.instr_dead, i);
|
||||
add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i]);
|
||||
}
|
||||
} FOREACH_INSTR_NUM_END();
|
||||
|
||||
/* Propagate liveness backwards to all definitions of used vars */
|
||||
while (!zend_bitset_empty(ctx.instr_worklist, ctx.instr_worklist_len)
|
||||
|| !zend_bitset_empty(ctx.phi_worklist, ctx.phi_worklist_len)) {
|
||||
while ((i = zend_bitset_pop_first(ctx.instr_worklist, ctx.instr_worklist_len)) >= 0) {
|
||||
zend_bitset_excl(ctx.instr_dead, i);
|
||||
add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i]);
|
||||
}
|
||||
while ((i = zend_bitset_pop_first(ctx.phi_worklist, ctx.phi_worklist_len)) >= 0) {
|
||||
zend_bitset_excl(ctx.phi_dead, i);
|
||||
add_phi_sources_to_worklists(&ctx, ssa->vars[i].definition_phi);
|
||||
}
|
||||
}
|
||||
|
||||
/* Eliminate dead instructions */
|
||||
FOREACH_INSTR_NUM(i) {
|
||||
if (zend_bitset_in(ctx.instr_dead, i)) {
|
||||
if (dce_instr(&ctx, &op_array->opcodes[i], &ssa->ops[i])) {
|
||||
removed_ops++;
|
||||
}
|
||||
}
|
||||
} FOREACH_INSTR_NUM_END();
|
||||
|
||||
/* Improper uses don't count as "uses" for the purpose of instruction elimination,
|
||||
* but we have to retain phis defining them. Push those phis to the worklist. */
|
||||
FOREACH_INSTR_NUM(i) {
|
||||
if (zend_has_improper_op1_use(&op_array->opcodes[i])) {
|
||||
ZEND_ASSERT(ssa->ops[i].op1_use >= 0);
|
||||
add_to_phi_worklist_only(&ctx, ssa->ops[i].op1_use);
|
||||
}
|
||||
} FOREACH_INSTR_NUM_END();
|
||||
|
||||
/* Propagate this information backwards, marking any phi with an improperly used
|
||||
* target as non-dead. */
|
||||
while ((i = zend_bitset_pop_first(ctx.phi_worklist, ctx.phi_worklist_len)) >= 0) {
|
||||
zend_ssa_phi *phi = ssa->vars[i].definition_phi;
|
||||
int source;
|
||||
zend_bitset_excl(ctx.phi_dead, i);
|
||||
FOREACH_PHI_SOURCE(phi, source) {
|
||||
add_to_phi_worklist_only(&ctx, source);
|
||||
} FOREACH_PHI_SOURCE_END();
|
||||
}
|
||||
|
||||
/* Now collect the actually dead phis */
|
||||
FOREACH_PHI(phi) {
|
||||
if (zend_bitset_in(ctx.phi_dead, phi->ssa_var)) {
|
||||
zend_ssa_remove_uses_of_var(ssa, phi->ssa_var);
|
||||
zend_ssa_remove_phi(ssa, phi);
|
||||
}
|
||||
} FOREACH_PHI_END();
|
||||
|
||||
/* Remove trivial phis (phis with identical source operands) */
|
||||
FOREACH_PHI(phi) {
|
||||
try_remove_trivial_phi(&ctx, phi);
|
||||
} FOREACH_PHI_END();
|
||||
|
||||
removed_ops += simplify_jumps(ssa, op_array);
|
||||
|
||||
return removed_ops;
|
||||
}
|
|
@ -719,6 +719,15 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx
|
|||
}
|
||||
}
|
||||
|
||||
if (ZEND_OPTIMIZER_PASS_14 & ctx->optimization_level) {
|
||||
if (dce_optimize_op_array(op_array, ssa, 0)) {
|
||||
remove_nops = 1;
|
||||
}
|
||||
if (ctx->debug_level & ZEND_DUMP_AFTER_DCE_PASS) {
|
||||
zend_dump_op_array(op_array, ZEND_DUMP_SSA, "after dce pass", ssa);
|
||||
}
|
||||
}
|
||||
|
||||
if (remove_nops) {
|
||||
zend_ssa_remove_nops(op_array, ssa);
|
||||
}
|
||||
|
|
|
@ -3927,6 +3927,294 @@ void zend_inference_check_recursive_dependencies(zend_op_array *op_array)
|
|||
free_alloca(worklist, use_heap);
|
||||
}
|
||||
|
||||
int zend_may_throw(const zend_op *opline, zend_op_array *op_array, zend_ssa *ssa)
|
||||
{
|
||||
uint32_t t1 = OP1_INFO();
|
||||
uint32_t t2 = OP2_INFO();
|
||||
|
||||
if (opline->op1_type == IS_CV) {
|
||||
if (t1 & MAY_BE_UNDEF) {
|
||||
switch (opline->opcode) {
|
||||
case ZEND_UNSET_VAR:
|
||||
case ZEND_ISSET_ISEMPTY_VAR:
|
||||
if (opline->extended_value & ZEND_QUICK_SET) {
|
||||
break;
|
||||
}
|
||||
case ZEND_ISSET_ISEMPTY_DIM_OBJ:
|
||||
case ZEND_ISSET_ISEMPTY_PROP_OBJ:
|
||||
case ZEND_ASSIGN:
|
||||
case ZEND_ASSIGN_DIM:
|
||||
case ZEND_ASSIGN_REF:
|
||||
case ZEND_BIND_GLOBAL:
|
||||
case ZEND_FETCH_DIM_IS:
|
||||
case ZEND_FETCH_OBJ_IS:
|
||||
case ZEND_SEND_REF:
|
||||
break;
|
||||
default:
|
||||
/* undefined variable warning */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} else if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
|
||||
if (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY)) {
|
||||
switch (opline->opcode) {
|
||||
case ZEND_CASE:
|
||||
case ZEND_FE_FETCH_R:
|
||||
case ZEND_FE_FETCH_RW:
|
||||
case ZEND_FETCH_LIST:
|
||||
case ZEND_QM_ASSIGN:
|
||||
case ZEND_SEND_VAL:
|
||||
case ZEND_SEND_VAL_EX:
|
||||
case ZEND_SEND_VAR:
|
||||
case ZEND_SEND_VAR_EX:
|
||||
case ZEND_SEND_VAR_NO_REF:
|
||||
case ZEND_SEND_VAR_NO_REF_EX:
|
||||
case ZEND_SEND_REF:
|
||||
case ZEND_SEPARATE:
|
||||
case ZEND_END_SILENCE:
|
||||
break;
|
||||
default:
|
||||
/* destructor may be called */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (opline->op2_type == IS_CV) {
|
||||
if (t2 & MAY_BE_UNDEF) {
|
||||
switch (opline->opcode) {
|
||||
case ZEND_ASSIGN_REF:
|
||||
break;
|
||||
default:
|
||||
/* undefined variable warning */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} else if (opline->op2_type & (IS_TMP_VAR|IS_VAR)) {
|
||||
if (t2 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY)) {
|
||||
switch (opline->opcode) {
|
||||
case ZEND_ASSIGN:
|
||||
break;
|
||||
default:
|
||||
/* destructor may be called */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (opline->opcode) {
|
||||
case ZEND_NOP:
|
||||
case ZEND_IS_IDENTICAL:
|
||||
case ZEND_IS_NOT_IDENTICAL:
|
||||
case ZEND_QM_ASSIGN:
|
||||
case ZEND_JMP:
|
||||
case ZEND_CHECK_VAR:
|
||||
case ZEND_MAKE_REF:
|
||||
case ZEND_SEND_VAR:
|
||||
case ZEND_BEGIN_SILENCE:
|
||||
case ZEND_END_SILENCE:
|
||||
case ZEND_SEND_VAL:
|
||||
case ZEND_SEND_REF:
|
||||
case ZEND_SEND_VAR_EX:
|
||||
case ZEND_FREE:
|
||||
case ZEND_SEPARATE:
|
||||
case ZEND_TYPE_CHECK:
|
||||
case ZEND_DEFINED:
|
||||
case ZEND_ISSET_ISEMPTY_THIS:
|
||||
case ZEND_COALESCE:
|
||||
case ZEND_SWITCH_LONG:
|
||||
case ZEND_SWITCH_STRING:
|
||||
return 0;
|
||||
case ZEND_INIT_FCALL:
|
||||
/* can't throw, because call is resolved at compile time */
|
||||
return 0;
|
||||
case ZEND_BIND_GLOBAL:
|
||||
if ((opline+1)->opcode == ZEND_BIND_GLOBAL) {
|
||||
return zend_may_throw(opline + 1, op_array, ssa);
|
||||
}
|
||||
return 0;
|
||||
case ZEND_ADD:
|
||||
if ((t1 & MAY_BE_ANY) == MAY_BE_ARRAY
|
||||
&& (t2 & MAY_BE_ANY) == MAY_BE_ARRAY) {
|
||||
return 0;
|
||||
}
|
||||
return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_DIV:
|
||||
case ZEND_MOD:
|
||||
if (!OP2_HAS_RANGE() ||
|
||||
(OP2_MIN_RANGE() <= 0 && OP2_MAX_RANGE() >= 0)) {
|
||||
/* Division by zero */
|
||||
return 1;
|
||||
}
|
||||
/* break missing intentionally */
|
||||
case ZEND_SUB:
|
||||
case ZEND_MUL:
|
||||
case ZEND_POW:
|
||||
return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_SL:
|
||||
case ZEND_SR:
|
||||
return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
!OP2_HAS_RANGE() ||
|
||||
OP2_MIN_RANGE() < 0;
|
||||
case ZEND_CONCAT:
|
||||
case ZEND_FAST_CONCAT:
|
||||
return (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_BW_OR:
|
||||
case ZEND_BW_AND:
|
||||
case ZEND_BW_XOR:
|
||||
if ((t1 & MAY_BE_ANY) == MAY_BE_STRING
|
||||
&& (t2 & MAY_BE_ANY) == MAY_BE_STRING) {
|
||||
return 0;
|
||||
}
|
||||
return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_BW_NOT:
|
||||
return (t1 & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_BOOL_NOT:
|
||||
case ZEND_PRE_INC:
|
||||
case ZEND_POST_INC:
|
||||
case ZEND_PRE_DEC:
|
||||
case ZEND_POST_DEC:
|
||||
case ZEND_JMPZ:
|
||||
case ZEND_JMPNZ:
|
||||
case ZEND_JMPZNZ:
|
||||
case ZEND_JMPZ_EX:
|
||||
case ZEND_JMPNZ_EX:
|
||||
case ZEND_BOOL:
|
||||
case ZEND_JMP_SET:
|
||||
return (t1 & MAY_BE_OBJECT);
|
||||
case ZEND_BOOL_XOR:
|
||||
return (t1 & MAY_BE_OBJECT) || (t2 & MAY_BE_OBJECT);
|
||||
case ZEND_IS_EQUAL:
|
||||
case ZEND_IS_NOT_EQUAL:
|
||||
case ZEND_IS_SMALLER:
|
||||
case ZEND_IS_SMALLER_OR_EQUAL:
|
||||
case ZEND_CASE:
|
||||
case ZEND_SPACESHIP:
|
||||
if ((t1 & MAY_BE_ANY) == MAY_BE_NULL
|
||||
|| (t2 & MAY_BE_ANY) == MAY_BE_NULL) {
|
||||
return 0;
|
||||
}
|
||||
return (t1 & MAY_BE_OBJECT) || (t2 & MAY_BE_OBJECT);
|
||||
case ZEND_ASSIGN_ADD:
|
||||
if (opline->extended_value != 0) {
|
||||
return 1;
|
||||
}
|
||||
if ((t1 & MAY_BE_ANY) == MAY_BE_ARRAY
|
||||
&& (t2 & MAY_BE_ANY) == MAY_BE_ARRAY) {
|
||||
return 0;
|
||||
}
|
||||
return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_ASSIGN_DIV:
|
||||
case ZEND_ASSIGN_MOD:
|
||||
if (opline->extended_value != 0) {
|
||||
return 1;
|
||||
}
|
||||
if (!OP2_HAS_RANGE() ||
|
||||
(OP2_MIN_RANGE() <= 0 && OP2_MAX_RANGE() >= 0)) {
|
||||
/* Division by zero */
|
||||
return 1;
|
||||
}
|
||||
/* break missing intentionally */
|
||||
case ZEND_ASSIGN_SUB:
|
||||
case ZEND_ASSIGN_MUL:
|
||||
case ZEND_ASSIGN_POW:
|
||||
if (opline->extended_value != 0) {
|
||||
return 1;
|
||||
}
|
||||
return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_ASSIGN_SL:
|
||||
case ZEND_ASSIGN_SR:
|
||||
if (opline->extended_value != 0) {
|
||||
return 1;
|
||||
}
|
||||
return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
!OP2_HAS_RANGE() ||
|
||||
OP2_MIN_RANGE() < 0;
|
||||
case ZEND_ASSIGN_CONCAT:
|
||||
if (opline->extended_value != 0) {
|
||||
return 1;
|
||||
}
|
||||
return (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_ASSIGN_BW_OR:
|
||||
case ZEND_ASSIGN_BW_AND:
|
||||
case ZEND_ASSIGN_BW_XOR:
|
||||
if (opline->extended_value != 0) {
|
||||
return 1;
|
||||
}
|
||||
if ((t1 & MAY_BE_ANY) == MAY_BE_STRING
|
||||
&& (t2 & MAY_BE_ANY) == MAY_BE_STRING) {
|
||||
return 0;
|
||||
}
|
||||
return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_ASSIGN:
|
||||
return (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY));
|
||||
case ZEND_ASSIGN_DIM:
|
||||
return (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_LONG|MAY_BE_DOUBLE)) || opline->op2_type == IS_UNUSED ||
|
||||
(t2 & (MAY_BE_UNDEF|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
|
||||
case ZEND_ROPE_INIT:
|
||||
case ZEND_ROPE_ADD:
|
||||
case ZEND_ROPE_END:
|
||||
return t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT);
|
||||
case ZEND_INIT_ARRAY:
|
||||
case ZEND_ADD_ARRAY_ELEMENT:
|
||||
return (opline->op2_type != IS_UNUSED) && (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
|
||||
case ZEND_STRLEN:
|
||||
return (t1 & MAY_BE_ANY) != MAY_BE_STRING;
|
||||
case ZEND_RECV_INIT:
|
||||
if (Z_CONSTANT_P(CRT_CONSTANT_EX(op_array, opline->op2, ssa->rt_constants))) {
|
||||
return 1;
|
||||
}
|
||||
if (op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) {
|
||||
uint32_t arg_num = opline->op1.num;
|
||||
zend_arg_info *cur_arg_info;
|
||||
|
||||
if (EXPECTED(arg_num <= op_array->num_args)) {
|
||||
cur_arg_info = &op_array->arg_info[arg_num-1];
|
||||
} else if (UNEXPECTED(op_array->fn_flags & ZEND_ACC_VARIADIC)) {
|
||||
cur_arg_info = &op_array->arg_info[op_array->num_args];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return ZEND_TYPE_IS_SET(cur_arg_info->type);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
case ZEND_ISSET_ISEMPTY_DIM_OBJ:
|
||||
case ZEND_FETCH_DIM_IS:
|
||||
return (t1 & MAY_BE_OBJECT) || (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_CAST:
|
||||
switch (opline->extended_value) {
|
||||
case IS_NULL:
|
||||
return 0;
|
||||
case _IS_BOOL:
|
||||
return (t1 & MAY_BE_OBJECT);
|
||||
case IS_LONG:
|
||||
case IS_DOUBLE:
|
||||
return (t1 & (MAY_BE_STRING|MAY_BE_OBJECT));
|
||||
case IS_STRING:
|
||||
return (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case IS_ARRAY:
|
||||
return (t1 & MAY_BE_OBJECT);
|
||||
case IS_OBJECT:
|
||||
return (t1 & MAY_BE_ARRAY);
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* tab-width: 4
|
||||
|
|
|
@ -32,6 +32,9 @@
|
|||
#define MAY_BE_RC1 (1<<27) /* may be non-reference with refcount == 1 */
|
||||
#define MAY_BE_RCN (1<<28) /* may be non-reference with refcount > 1 */
|
||||
|
||||
#define MAY_HAVE_DTOR \
|
||||
(MAY_BE_OBJECT|MAY_BE_RESOURCE \
|
||||
|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE)
|
||||
|
||||
#define DEFINE_SSA_OP_HAS_RANGE(opN) \
|
||||
static zend_always_inline zend_bool _ssa_##opN##_has_range(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \
|
||||
|
@ -263,6 +266,8 @@ void zend_func_return_info(const zend_op_array *op_array,
|
|||
int widening,
|
||||
zend_ssa_var_info *ret);
|
||||
|
||||
int zend_may_throw(const zend_op *opline, zend_op_array *op_array, zend_ssa *ssa);
|
||||
|
||||
END_EXTERN_C()
|
||||
|
||||
#endif /* ZEND_INFERENCE_H */
|
||||
|
|
|
@ -1068,6 +1068,15 @@ static void zend_optimize(zend_op_array *op_array,
|
|||
}
|
||||
}
|
||||
|
||||
if ((ZEND_OPTIMIZER_PASS_13 & ctx->optimization_level) &&
|
||||
(!(ZEND_OPTIMIZER_PASS_6 & ctx->optimization_level) ||
|
||||
!(ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level))) {
|
||||
zend_optimizer_compact_vars(op_array);
|
||||
if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_13) {
|
||||
zend_dump_op_array(op_array, 0, "after pass 13", NULL);
|
||||
}
|
||||
}
|
||||
|
||||
if (ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level) {
|
||||
return;
|
||||
}
|
||||
|
@ -1283,6 +1292,15 @@ int zend_optimize_script(zend_script *script, zend_long optimization_level, zend
|
|||
}
|
||||
}
|
||||
|
||||
if (ZEND_OPTIMIZER_PASS_13 & optimization_level) {
|
||||
for (i = 0; i < call_graph.op_arrays_count; i++) {
|
||||
zend_optimizer_compact_vars(call_graph.op_arrays[i]);
|
||||
if (debug_level & ZEND_DUMP_AFTER_PASS_13) {
|
||||
zend_dump_op_array(call_graph.op_arrays[i], 0, "after pass 13", NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ZEND_OPTIMIZER_PASS_12 & optimization_level) {
|
||||
for (i = 0; i < call_graph.op_arrays_count; i++) {
|
||||
zend_adjust_fcall_stack_size_graph(call_graph.op_arrays[i]);
|
||||
|
|
|
@ -37,8 +37,8 @@
|
|||
#define ZEND_OPTIMIZER_PASS_10 (1<<9) /* NOP removal */
|
||||
#define ZEND_OPTIMIZER_PASS_11 (1<<10) /* Merge equal constants */
|
||||
#define ZEND_OPTIMIZER_PASS_12 (1<<11) /* Adjust used stack */
|
||||
#define ZEND_OPTIMIZER_PASS_13 (1<<12)
|
||||
#define ZEND_OPTIMIZER_PASS_14 (1<<13)
|
||||
#define ZEND_OPTIMIZER_PASS_13 (1<<12) /* Remove unused variables */
|
||||
#define ZEND_OPTIMIZER_PASS_14 (1<<13) /* DCE (dead code elimination) */
|
||||
#define ZEND_OPTIMIZER_PASS_15 (1<<14) /* Collect constants */
|
||||
#define ZEND_OPTIMIZER_PASS_16 (1<<15) /* Inline functions */
|
||||
|
||||
|
@ -78,6 +78,7 @@
|
|||
#define ZEND_DUMP_DFA_SSA (1<<27)
|
||||
#define ZEND_DUMP_DFA_SSA_VARS (1<<28)
|
||||
#define ZEND_DUMP_AFTER_SCCP_PASS (1<<29)
|
||||
#define ZEND_DUMP_AFTER_DCE_PASS (1<<30)
|
||||
|
||||
typedef struct _zend_script {
|
||||
zend_string *filename;
|
||||
|
|
|
@ -104,6 +104,7 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx
|
|||
void zend_optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *ctx);
|
||||
void zend_optimizer_nop_removal(zend_op_array *op_array);
|
||||
void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx *ctx);
|
||||
void zend_optimizer_compact_vars(zend_op_array *op_array);
|
||||
int zend_optimizer_is_disabled_func(const char *name, size_t len);
|
||||
zend_function *zend_optimizer_get_called_func(
|
||||
zend_script *script, zend_op_array *op_array, zend_op *opline, zend_bool rt_constants);
|
||||
|
@ -112,5 +113,11 @@ void zend_optimizer_migrate_jump(zend_op_array *op_array, zend_op *new_opline, z
|
|||
void zend_optimizer_shift_jump(zend_op_array *op_array, zend_op *opline, uint32_t *shiftlist);
|
||||
zend_uchar zend_compound_assign_to_binary_op(zend_uchar opcode);
|
||||
int sccp_optimize_op_array(zend_optimizer_ctx *ctx, zend_op_array *op_arrya, zend_ssa *ssa, zend_call_info **call_map);
|
||||
int dce_optimize_op_array(zend_op_array *op_array, zend_ssa *ssa, zend_bool reorder_dtor_effects);
|
||||
|
||||
static inline zend_bool zend_has_improper_op1_use(zend_op *opline) {
|
||||
return opline->opcode == ZEND_ASSIGN
|
||||
|| (opline->opcode == ZEND_UNSET_VAR && opline->extended_value & ZEND_QUICK_SET);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1450,6 +1450,135 @@ void zend_ssa_remove_block(zend_op_array *op_array, zend_ssa *ssa, int i) /* {{{
|
|||
}
|
||||
/* }}} */
|
||||
|
||||
static void propagate_phi_type_widening(zend_ssa *ssa, int var) /* {{{ */
|
||||
{
|
||||
zend_ssa_phi *phi;
|
||||
FOREACH_PHI_USE(&ssa->vars[var], phi) {
|
||||
if (ssa->var_info[var].type & ~ssa->var_info[phi->ssa_var].type) {
|
||||
ssa->var_info[phi->ssa_var].type |= ssa->var_info[var].type;
|
||||
propagate_phi_type_widening(ssa, phi->ssa_var);
|
||||
}
|
||||
} FOREACH_PHI_USE_END();
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
void zend_ssa_rename_var_uses(zend_ssa *ssa, int old, int new, zend_bool update_types) /* {{{ */
|
||||
{
|
||||
zend_ssa_var *old_var = &ssa->vars[old];
|
||||
zend_ssa_var *new_var = &ssa->vars[new];
|
||||
int use;
|
||||
zend_ssa_phi *phi;
|
||||
|
||||
ZEND_ASSERT(old >= 0 && new >= 0);
|
||||
ZEND_ASSERT(old != new);
|
||||
|
||||
/* Only a no_val is both variables are */
|
||||
new_var->no_val &= old_var->no_val;
|
||||
|
||||
/* Update ssa_op use chains */
|
||||
FOREACH_USE(old_var, use) {
|
||||
zend_ssa_op *ssa_op = &ssa->ops[use];
|
||||
|
||||
/* If the op already uses the new var, don't add the op to the use
|
||||
* list again. Instead move the use_chain to the correct operand. */
|
||||
zend_bool add_to_use_chain = 1;
|
||||
if (ssa_op->result_use == new) {
|
||||
add_to_use_chain = 0;
|
||||
} else if (ssa_op->op1_use == new) {
|
||||
if (ssa_op->result_use == old) {
|
||||
ssa_op->res_use_chain = ssa_op->op1_use_chain;
|
||||
ssa_op->op1_use_chain = -1;
|
||||
}
|
||||
add_to_use_chain = 0;
|
||||
} else if (ssa_op->op2_use == new) {
|
||||
if (ssa_op->result_use == old) {
|
||||
ssa_op->res_use_chain = ssa_op->op2_use_chain;
|
||||
ssa_op->op2_use_chain = -1;
|
||||
} else if (ssa_op->op1_use == old) {
|
||||
ssa_op->op1_use_chain = ssa_op->op2_use_chain;
|
||||
ssa_op->op2_use_chain = -1;
|
||||
}
|
||||
add_to_use_chain = 0;
|
||||
}
|
||||
|
||||
/* Perform the actual renaming */
|
||||
if (ssa_op->result_use == old) {
|
||||
ssa_op->result_use = new;
|
||||
}
|
||||
if (ssa_op->op1_use == old) {
|
||||
ssa_op->op1_use = new;
|
||||
}
|
||||
if (ssa_op->op2_use == old) {
|
||||
ssa_op->op2_use = new;
|
||||
}
|
||||
|
||||
/* Add op to use chain of new var (if it isn't already). We use the
|
||||
* first use chain of (result, op1, op2) that has the new variable. */
|
||||
if (add_to_use_chain) {
|
||||
if (ssa_op->result_use == new) {
|
||||
ssa_op->res_use_chain = new_var->use_chain;
|
||||
new_var->use_chain = use;
|
||||
} else if (ssa_op->op1_use == new) {
|
||||
ssa_op->op1_use_chain = new_var->use_chain;
|
||||
new_var->use_chain = use;
|
||||
} else {
|
||||
ZEND_ASSERT(ssa_op->op2_use == new);
|
||||
ssa_op->op2_use_chain = new_var->use_chain;
|
||||
new_var->use_chain = use;
|
||||
}
|
||||
}
|
||||
} FOREACH_USE_END();
|
||||
old_var->use_chain = -1;
|
||||
|
||||
/* Update phi use chains */
|
||||
FOREACH_PHI_USE(old_var, phi) {
|
||||
int j;
|
||||
zend_bool after_first_new_source = 0;
|
||||
|
||||
/* If the phi already uses the new var, find its use chain, as we may
|
||||
* need to move it to a different source operand. */
|
||||
zend_ssa_phi **existing_use_chain_ptr = NULL;
|
||||
for (j = 0; j < ssa->cfg.blocks[phi->block].predecessors_count; j++) {
|
||||
if (phi->sources[j] == new) {
|
||||
existing_use_chain_ptr = &phi->use_chains[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (j = 0; j < ssa->cfg.blocks[phi->block].predecessors_count; j++) {
|
||||
if (phi->sources[j] == new) {
|
||||
after_first_new_source = 1;
|
||||
} else if (phi->sources[j] == old) {
|
||||
phi->sources[j] = new;
|
||||
|
||||
/* Either move existing use chain to this source, or add the phi
|
||||
* to the phi use chain of the new variables. Do this only once. */
|
||||
if (!after_first_new_source) {
|
||||
if (existing_use_chain_ptr) {
|
||||
phi->use_chains[j] = *existing_use_chain_ptr;
|
||||
*existing_use_chain_ptr = NULL;
|
||||
} else {
|
||||
phi->use_chains[j] = new_var->phi_use_chain;
|
||||
new_var->phi_use_chain = phi;
|
||||
}
|
||||
after_first_new_source = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sure phi result types are not incorrectly narrow after renaming.
|
||||
* This should not normally happen, but can occur if we DCE an assignment
|
||||
* or unset and there is an improper phi-indirected use lateron. */
|
||||
// TODO Alternatively we could rerun type-inference after DCE
|
||||
if (update_types && (ssa->var_info[new].type & ~ssa->var_info[phi->ssa_var].type)) {
|
||||
ssa->var_info[phi->ssa_var].type |= ssa->var_info[new].type;
|
||||
propagate_phi_type_widening(ssa, phi->ssa_var);
|
||||
}
|
||||
} FOREACH_PHI_USE_END();
|
||||
old_var->phi_use_chain = NULL;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* tab-width: 4
|
||||
|
|
|
@ -143,6 +143,7 @@ void zend_ssa_remove_instr(zend_ssa *ssa, zend_op *opline, zend_ssa_op *ssa_op);
|
|||
void zend_ssa_remove_phi(zend_ssa *ssa, zend_ssa_phi *phi);
|
||||
void zend_ssa_remove_uses_of_var(zend_ssa *ssa, int var_num);
|
||||
void zend_ssa_remove_block(zend_op_array *op_array, zend_ssa *ssa, int b);
|
||||
void zend_ssa_rename_var_uses(zend_ssa *ssa, int old_var, int new_var, zend_bool update_types);
|
||||
|
||||
static zend_always_inline void _zend_ssa_remove_def(zend_ssa_var *var)
|
||||
{
|
||||
|
@ -214,6 +215,31 @@ static zend_always_inline zend_bool zend_ssa_is_no_val_use(const zend_op *opline
|
|||
return 0;
|
||||
}
|
||||
|
||||
static zend_always_inline void zend_ssa_rename_defs_of_instr(zend_ssa *ssa, zend_ssa_op *ssa_op) {
|
||||
/* Rename def to use if possible. Mark variable as not defined otherwise. */
|
||||
if (ssa_op->op1_def >= 0) {
|
||||
if (ssa_op->op1_use >= 0) {
|
||||
zend_ssa_rename_var_uses(ssa, ssa_op->op1_def, ssa_op->op1_use, 1);
|
||||
}
|
||||
ssa->vars[ssa_op->op1_def].definition = -1;
|
||||
ssa_op->op1_def = -1;
|
||||
}
|
||||
if (ssa_op->op2_def >= 0) {
|
||||
if (ssa_op->op2_use >= 0) {
|
||||
zend_ssa_rename_var_uses(ssa, ssa_op->op2_def, ssa_op->op2_use, 1);
|
||||
}
|
||||
ssa->vars[ssa_op->op2_def].definition = -1;
|
||||
ssa_op->op2_def = -1;
|
||||
}
|
||||
if (ssa_op->result_def >= 0) {
|
||||
if (ssa_op->result_use >= 0) {
|
||||
zend_ssa_rename_var_uses(ssa, ssa_op->result_def, ssa_op->result_use, 1);
|
||||
}
|
||||
ssa->vars[ssa_op->result_def].definition = -1;
|
||||
ssa_op->result_def = -1;
|
||||
}
|
||||
}
|
||||
|
||||
#define NUM_PHI_SOURCES(phi) \
|
||||
((phi)->pi >= 0 ? 1 : (ssa->cfg.blocks[(phi)->block].predecessors_count))
|
||||
|
||||
|
@ -246,6 +272,38 @@ static zend_always_inline zend_bool zend_ssa_is_no_val_use(const zend_op *opline
|
|||
} \
|
||||
} while (0)
|
||||
|
||||
#define FOREACH_PHI(phi) do { \
|
||||
int _i; \
|
||||
for (_i = 0; _i < ssa->cfg.blocks_count; _i++) { \
|
||||
phi = ssa->blocks[_i].phis; \
|
||||
for (; phi; phi = phi->next) {
|
||||
#define FOREACH_PHI_END() \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define FOREACH_BLOCK(block) do { \
|
||||
int _i; \
|
||||
for (_i = 0; _i < ssa->cfg.blocks_count; _i++) { \
|
||||
(block) = &ssa->cfg.blocks[_i]; \
|
||||
if (!((block)->flags & ZEND_BB_REACHABLE)) { \
|
||||
continue; \
|
||||
}
|
||||
#define FOREACH_BLOCK_END() \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* Does not support "break" */
|
||||
#define FOREACH_INSTR_NUM(i) do { \
|
||||
zend_basic_block *_block; \
|
||||
FOREACH_BLOCK(_block) { \
|
||||
uint32_t _end = _block->start + _block->len; \
|
||||
for ((i) = _block->start; (i) < _end; (i)++) {
|
||||
#define FOREACH_INSTR_NUM_END() \
|
||||
} \
|
||||
} FOREACH_BLOCK_END(); \
|
||||
} while (0)
|
||||
|
||||
#endif /* ZEND_SSA_H */
|
||||
|
||||
/*
|
||||
|
|
|
@ -412,6 +412,8 @@ fi
|
|||
Optimizer/zend_call_graph.c \
|
||||
Optimizer/sccp.c \
|
||||
Optimizer/scdf.c \
|
||||
Optimizer/dce.c \
|
||||
Optimizer/compact_vars.c \
|
||||
Optimizer/zend_dump.c,
|
||||
shared,,-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1,,yes)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue