summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS5
-rw-r--r--Zend/zend_bitset.h8
-rw-r--r--ext/opcache/Optimizer/compact_vars.c112
-rw-r--r--ext/opcache/Optimizer/dce.c647
-rw-r--r--ext/opcache/Optimizer/dfa_pass.c214
-rw-r--r--ext/opcache/Optimizer/sccp.c1445
-rw-r--r--ext/opcache/Optimizer/scdf.c228
-rw-r--r--ext/opcache/Optimizer/scdf.h99
-rw-r--r--ext/opcache/Optimizer/ssa_integrity.c369
-rw-r--r--ext/opcache/Optimizer/zend_cfg.c16
-rw-r--r--ext/opcache/Optimizer/zend_cfg.h1
-rw-r--r--ext/opcache/Optimizer/zend_inference.c325
-rw-r--r--ext/opcache/Optimizer/zend_inference.h5
-rw-r--r--ext/opcache/Optimizer/zend_optimizer.c121
-rw-r--r--ext/opcache/Optimizer/zend_optimizer.h6
-rw-r--r--ext/opcache/Optimizer/zend_optimizer_internal.h8
-rw-r--r--ext/opcache/Optimizer/zend_ssa.c454
-rw-r--r--ext/opcache/Optimizer/zend_ssa.h124
-rw-r--r--ext/opcache/config.m44
-rw-r--r--ext/opcache/config.w322
-rw-r--r--ext/opcache/tests/ssa_bug_007.phpt23
21 files changed, 4153 insertions, 63 deletions
diff --git a/NEWS b/NEWS
index 598edb21f6..220a73f77c 100644
--- a/NEWS
+++ b/NEWS
@@ -44,6 +44,11 @@ PHP NEWS
- LDAP:
. Fixed passing an empty array to ldap_set_option for client or server controls.
+- Opcache:
+ . Added goblal optimisation passes based on data flow analyses using SSA form:
+ SCCP - Sparse Conditional Constant Propagation, DCE - Dead Code Elimination
+ and removing of unused local variables (Nikita, Dmitry)
+
- OpenSSL:
. Fixed bug #74651 (negative-size-param (-1) in memcpy in zif_openssl_seal()).
(Stas)
diff --git a/Zend/zend_bitset.h b/Zend/zend_bitset.h
index da2dcacd92..98a46f9640 100644
--- a/Zend/zend_bitset.h
+++ b/Zend/zend_bitset.h
@@ -245,6 +245,14 @@ static inline int zend_bitset_last(zend_bitset set, uint32_t len)
} \
} while (0)
+static inline int zend_bitset_pop_first(zend_bitset set, uint32_t len) {
+ int i = zend_bitset_first(set, len);
+ if (i >= 0) {
+ zend_bitset_excl(set, i);
+ }
+ return i;
+}
+
#endif /* _ZEND_BITSET_H_ */
/*
diff --git a/ext/opcache/Optimizer/compact_vars.c b/ext/opcache/Optimizer/compact_vars.c
new file mode 100644
index 0000000000..c5a9b79553
--- /dev/null
+++ b/ext/opcache/Optimizer/compact_vars.c
@@ -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);
+}
diff --git a/ext/opcache/Optimizer/dce.c b/ext/opcache/Optimizer/dce.c
new file mode 100644
index 0000000000..2c21154ef7
--- /dev/null
+++ b/ext/opcache/Optimizer/dce.c
@@ -0,0 +1,647 @@
+/*
+ +----------------------------------------------------------------------+
+ | 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;
+ zend_bitset phi_worklist_no_val;
+ 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(
+ zend_op_array *op_array, zend_ssa *ssa,
+ const zend_op *opline, const zend_ssa_op *ssa_op,
+ zend_bool reorder_dtor_effects) {
+ 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:
+ case ZEND_SPACESHIP:
+ case ZEND_STRLEN:
+ case ZEND_COUNT:
+ case ZEND_GET_TYPE:
+ case ZEND_ISSET_ISEMPTY_THIS:
+ case ZEND_ISSET_ISEMPTY_DIM_OBJ:
+ case ZEND_FETCH_DIM_IS:
+ case ZEND_ISSET_ISEMPTY_VAR:
+ case ZEND_FETCH_IS:
+ /* 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:
+ {
+ if (is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def)) {
+ return 1;
+ }
+ if (!reorder_dtor_effects) {
+ 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;
+ }
+ 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 zend_always_inline void add_to_worklists(context *ctx, int var_num, int check) {
+ zend_ssa_var *var = &ctx->ssa->vars[var_num];
+ if (var->definition >= 0) {
+ if (!check || zend_bitset_in(ctx->instr_dead, var->definition)) {
+ zend_bitset_incl(ctx->instr_worklist, var->definition);
+ }
+ } else if (var->definition_phi) {
+ if (!check || zend_bitset_in(ctx->phi_dead, var_num)) {
+ zend_bitset_incl(ctx->phi_worklist, var_num);
+ }
+ }
+}
+
+static inline void add_to_phi_worklist_no_val(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_no_val, var_num);
+ }
+}
+
+static zend_always_inline void add_operands_to_worklists(context *ctx, zend_op *opline, zend_ssa_op *ssa_op, int check) {
+ if (ssa_op->result_use >= 0) {
+ add_to_worklists(ctx, ssa_op->result_use, check);
+ }
+ if (ssa_op->op1_use >= 0) {
+ if (!zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op1_use)) {
+ add_to_worklists(ctx, ssa_op->op1_use, check);
+ } else {
+ add_to_phi_worklist_no_val(ctx, ssa_op->op1_use);
+ }
+ }
+ if (ssa_op->op2_use >= 0) {
+ if (!zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op2_use)) {
+ add_to_worklists(ctx, ssa_op->op2_use, check);
+ } else {
+ add_to_phi_worklist_no_val(ctx, ssa_op->op2_use);
+ }
+ }
+}
+
+static zend_always_inline void add_phi_sources_to_worklists(context *ctx, zend_ssa_phi *phi, int check) {
+ zend_ssa *ssa = ctx->ssa;
+ int source;
+ FOREACH_PHI_SOURCE(phi, source) {
+ add_to_worklists(ctx, source, check);
+ } 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;
+ }
+}
+
+// Sometimes we can mark the var as EXT_UNUSED
+static zend_bool try_remove_var_def(context *ctx, int free_var, int use_chain, zend_op *opline) {
+ if (use_chain >= 0) {
+ return 0;
+ }
+ zend_ssa_var *var = &ctx->ssa->vars[free_var];
+ int def = var->definition;
+
+ if (def >= 0) {
+ zend_ssa_op *def_op = &ctx->ssa->ops[def];
+
+ if (def_op->result_def == free_var
+ && var->phi_use_chain == NULL
+ && var->use_chain == (opline - ctx->op_array->opcodes)) {
+ zend_op *def_opline = &ctx->op_array->opcodes[def];
+
+ switch (def_opline->opcode) {
+ case ZEND_ASSIGN:
+ case ZEND_ASSIGN_REF:
+ case ZEND_ASSIGN_DIM:
+ case ZEND_ASSIGN_OBJ:
+ 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:
+ case ZEND_PRE_INC:
+ case ZEND_POST_INC:
+ case ZEND_PRE_DEC:
+ case ZEND_POST_DEC:
+ case ZEND_PRE_INC_OBJ:
+ case ZEND_POST_INC_OBJ:
+ case ZEND_PRE_DEC_OBJ:
+ case ZEND_POST_DEC_OBJ:
+ case ZEND_DO_ICALL:
+ case ZEND_DO_UCALL:
+ case ZEND_DO_FCALL_BY_NAME:
+ case ZEND_DO_FCALL:
+ case ZEND_INCLUDE_OR_EVAL:
+ case ZEND_YIELD:
+ case ZEND_YIELD_FROM:
+ case ZEND_ASSERT_CHECK:
+ def_opline->result_type = IS_UNUSED;
+ def_opline->result.var = 0;
+ def_op->result_def = -1;
+ var->definition = -1;
+ return 1;
+ default:
+ break;
+ }
+ }
+ }
+ 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;
+ 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;
+ }
+
+ if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) && !is_var_dead(ctx, ssa_op->op1_use)) {
+ if (!try_remove_var_def(ctx, ssa_op->op1_use, ssa_op->op1_use_chain, opline)) {
+ free_var = ssa_op->op1_use;
+ free_var_type = opline->op1_type;
+ }
+ }
+ if ((opline->op2_type & (IS_VAR|IS_TMP_VAR)) && !is_var_dead(ctx, ssa_op->op2_use)) {
+ if (!try_remove_var_def(ctx, ssa_op->op2_use, ssa_op->op2_use_chain, opline)) {
+ if (free_var >= 0) {
+ // TODO: We can't free two vars. Keep instruction alive.
+ zend_bitset_excl(ctx->instr_dead, opline - ctx->op_array->opcodes);
+ return 0;
+ }
+ 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) {
+ 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 0;
+ }
+ 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_count == 1 && block->successors[0] != block_num + 1) {
+ opline->opcode = ZEND_BOOL;
+ }
+ break;
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ if (block->successors_count == 1 && 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);
+ }*/
+ }
+}
+
+static inline zend_bool may_break_varargs(const zend_op_array *op_array, const zend_ssa *ssa, const zend_ssa_op *ssa_op) {
+ if (ssa_op->op1_def >= 0
+ && ssa->vars[ssa_op->op1_def].var < op_array->num_args) {
+ return 1;
+ }
+ if (ssa_op->op2_def >= 0
+ && ssa->vars[ssa_op->op2_def].var < op_array->num_args) {
+ return 1;
+ }
+ if (ssa_op->result_def >= 0
+ && ssa->vars[ssa_op->result_def].var < op_array->num_args) {
+ return 1;
+ }
+ return 0;
+}
+
+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 that changes arguments may affect vararg functions. */
+ zend_bool has_varargs = ssa->cfg.vararg;
+
+ 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);
+ ctx.phi_worklist_no_val = alloca(sizeof(zend_ulong) * ctx.phi_worklist_len);
+ memset(ctx.phi_worklist_no_val, 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, 0, 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 reacable instruction without side effects as dead */
+ int b = ssa->cfg.blocks_count;
+ while (b > 0) {
+ b--;
+ zend_basic_block *block = &ssa->cfg.blocks[b];
+ if (!(block->flags & ZEND_BB_REACHABLE)) {
+ continue;
+ }
+ i = block->start + block->len;
+ while (i > block->start) {
+ i--;
+
+ if (zend_bitset_in(ctx.instr_worklist, i)) {
+ zend_bitset_excl(ctx.instr_worklist, i);
+ add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], 0);
+ } else if (may_have_side_effects(op_array, ssa, &op_array->opcodes[i], &ssa->ops[i], ctx.reorder_dtor_effects)
+ || zend_may_throw(&op_array->opcodes[i], op_array, ssa)
+ || (has_varargs && may_break_varargs(op_array, ssa, &ssa->ops[i]))) {
+ add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], 0);
+ } else {
+ zend_bitset_incl(ctx.instr_dead, i);
+ }
+
+ }
+ }
+
+ /* 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], 1);
+ }
+ while ((i = zend_bitset_pop_first(ctx.phi_worklist, ctx.phi_worklist_len)) >= 0) {
+ zend_bitset_excl(ctx.phi_dead, i);
+ zend_bitset_excl(ctx.phi_worklist_no_val, i);
+ add_phi_sources_to_worklists(&ctx, ssa->vars[i].definition_phi, 1);
+ }
+ }
+
+ /* Eliminate dead instructions */
+ ZEND_BITSET_FOREACH(ctx.instr_dead, ctx.instr_worklist_len, i) {
+ removed_ops += dce_instr(&ctx, &op_array->opcodes[i], &ssa->ops[i]);
+ } ZEND_BITSET_FOREACH_END();
+
+ /* Improper uses don't count as "uses" for the purpose of instruction elimination,
+ * but we have to retain phis defining them.
+ * Propagate this information backwards, marking any phi with an improperly used
+ * target as non-dead. */
+ while ((i = zend_bitset_pop_first(ctx.phi_worklist_no_val, 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_no_val(&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);
+ } else {
+ /* Remove trivial phis (phis with identical source operands) */
+ try_remove_trivial_phi(&ctx, phi);
+ }
+ } FOREACH_PHI_END();
+
+ removed_ops += simplify_jumps(ssa, op_array);
+
+ return removed_ops;
+}
diff --git a/ext/opcache/Optimizer/dfa_pass.c b/ext/opcache/Optimizer/dfa_pass.c
index 1b589eed45..642e0d3ead 100644
--- a/ext/opcache/Optimizer/dfa_pass.c
+++ b/ext/opcache/Optimizer/dfa_pass.c
@@ -31,6 +31,14 @@
#include "zend_inference.h"
#include "zend_dump.h"
+#ifndef ZEND_DEBUG_DFA
+# define ZEND_DEBUG_DFA ZEND_DEBUG
+#endif
+
+#if ZEND_DEBUG_DFA
+# include "ssa_integrity.c"
+#endif
+
int zend_dfa_analyze_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, uint32_t *flags)
{
uint32_t build_flags;
@@ -87,7 +95,7 @@ int zend_dfa_analyze_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx,
}
if (ctx->debug_level & ZEND_DUMP_DFA_SSA) {
- zend_dump_op_array(op_array, ZEND_DUMP_SSA, "before dfa pass", ssa);
+ zend_dump_op_array(op_array, ZEND_DUMP_SSA, "dfa ssa", ssa);
}
@@ -153,6 +161,7 @@ static void zend_ssa_remove_nops(zend_op_array *op_array, zend_ssa *ssa)
if (i != target) {
op_array->opcodes[target] = op_array->opcodes[i];
ssa->ops[target] = ssa->ops[i];
+ ssa->cfg.map[target] = b - blocks;
}
target++;
}
@@ -171,6 +180,9 @@ static void zend_ssa_remove_nops(zend_op_array *op_array, zend_ssa *ssa)
new_opline = op_array->opcodes + target - 1;
zend_optimizer_migrate_jump(op_array, new_opline, opline);
}
+ } else {
+ b->start = target;
+ b->len = 0;
}
}
@@ -319,7 +331,126 @@ static zend_bool opline_supports_assign_contraction(
return 1;
}
-void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa)
+int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa)
+{
+ zend_func_info *func_info = ZEND_FUNC_INFO(op_array);
+ int removed_ops = 0;
+
+ if (func_info->callee_info) {
+ zend_call_info *call_info = func_info->callee_info;
+
+ do {
+ if (call_info->caller_call_opline->opcode == ZEND_DO_ICALL
+ && call_info->callee_func
+ && ZSTR_LEN(call_info->callee_func->common.function_name) == sizeof("in_array")-1
+ && memcmp(ZSTR_VAL(call_info->callee_func->common.function_name), "in_array", sizeof("in_array")-1) == 0
+ && (call_info->caller_init_opline->extended_value == 2
+ || (call_info->caller_init_opline->extended_value == 3
+ && (call_info->caller_call_opline - 1)->opcode == ZEND_SEND_VAL
+ && (call_info->caller_call_opline - 1)->op1_type == IS_CONST))) {
+
+ zend_op *send_array;
+ zend_op *send_needly;
+ zend_bool strict = 0;
+
+ if (call_info->caller_init_opline->extended_value == 2) {
+ send_array = call_info->caller_call_opline - 1;
+ send_needly = call_info->caller_call_opline - 2;
+ } else {
+ if (zend_is_true(CT_CONSTANT_EX(op_array, (call_info->caller_call_opline - 1)->op1.constant))) {
+ strict = 1;
+ }
+ send_array = call_info->caller_call_opline - 2;
+ send_needly = call_info->caller_call_opline - 3;
+ }
+
+ if (send_array->opcode == ZEND_SEND_VAL
+ && send_array->op1_type == IS_CONST
+ && Z_TYPE_P(CT_CONSTANT_EX(op_array, send_array->op1.constant)) == IS_ARRAY
+ && (send_needly->opcode == ZEND_SEND_VAL
+ || send_needly->opcode == ZEND_SEND_VAR)
+ ) {
+ int ok = 1;
+
+ HashTable *src = Z_ARRVAL_P(CT_CONSTANT_EX(op_array, send_array->op1.constant));
+ HashTable *dst;
+ zval *val, tmp;
+ zend_ulong idx;
+
+ ZVAL_TRUE(&tmp);
+ dst = emalloc(sizeof(HashTable));
+ zend_hash_init(dst, zend_hash_num_elements(src), NULL, ZVAL_PTR_DTOR, 0);
+ if (strict) {
+ ZEND_HASH_FOREACH_VAL(src, val) {
+ if (Z_TYPE_P(val) == IS_STRING) {
+ zend_hash_add(dst, Z_STR_P(val), &tmp);
+ } else if (Z_TYPE_P(val) == IS_LONG) {
+ zend_hash_index_add(dst, Z_LVAL_P(val), &tmp);
+ } else {
+ zend_array_destroy(dst);
+ ok = 0;
+ break;
+ }
+ } ZEND_HASH_FOREACH_END();
+ } else {
+ ZEND_HASH_FOREACH_VAL(src, val) {
+ if (Z_TYPE_P(val) != IS_STRING || ZEND_HANDLE_NUMERIC(Z_STR_P(val), idx)) {
+ zend_array_destroy(dst);
+ ok = 0;
+ break;
+ }
+ zend_hash_add(dst, Z_STR_P(val), &tmp);
+ } ZEND_HASH_FOREACH_END();
+ }
+
+ if (ok) {
+ uint32_t op_num = send_needly - op_array->opcodes;
+ zend_ssa_op *ssa_op = ssa->ops + op_num;
+
+ if (ssa_op->op1_use >= 0) {
+ /* Reconstruct SSA */
+ int var_num = ssa_op->op1_use;
+ zend_ssa_var *var = ssa->vars + var_num;
+
+ ZEND_ASSERT(ssa_op->op1_def < 0);
+ zend_ssa_unlink_use_chain(ssa, op_num, ssa_op->op1_use);
+ ssa_op->op1_use = -1;
+ ssa_op->op1_use_chain = -1;
+ op_num = call_info->caller_call_opline - op_array->opcodes;
+ ssa_op = ssa->ops + op_num;
+ ssa_op->op1_use = var_num;
+ ssa_op->op1_use_chain = var->use_chain;
+ var->use_chain = op_num;
+ }
+
+ ZVAL_ARR(&tmp, dst);
+
+ /* Update opcode */
+ call_info->caller_call_opline->opcode = ZEND_IN_ARRAY;
+ call_info->caller_call_opline->extended_value = strict;
+ call_info->caller_call_opline->op1_type = send_needly->op1_type;
+ call_info->caller_call_opline->op1.num = send_needly->op1.num;
+ call_info->caller_call_opline->op2_type = IS_CONST;
+ call_info->caller_call_opline->op2.constant = zend_optimizer_add_literal(op_array, &tmp);
+ if (call_info->caller_init_opline->extended_value == 3) {
+ MAKE_NOP(call_info->caller_call_opline - 1);
+ }
+ MAKE_NOP(call_info->caller_init_opline);
+ MAKE_NOP(send_needly);
+ MAKE_NOP(send_array);
+ removed_ops++;
+
+ }
+ }
+ }
+ call_info = call_info->next_callee;
+ } while (call_info);
+ }
+
+ return removed_ops;
+}
+
+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) {
zend_dump_op_array(op_array, ZEND_DUMP_SSA, "before dfa pass", ssa);
@@ -332,6 +463,38 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx
zend_op *opline;
zval tmp;
+ if (ZEND_OPTIMIZER_PASS_8 & ctx->optimization_level) {
+ if (sccp_optimize_op_array(ctx, op_array, ssa, call_map)) {
+ remove_nops = 1;
+ }
+#if ZEND_DEBUG_DFA
+ ssa_verify_integrity(op_array, ssa, "after sccp");
+#endif
+ if (ZEND_FUNC_INFO(op_array)) {
+ if (zend_dfa_optimize_calls(op_array, ssa)) {
+ remove_nops = 1;
+ }
+ }
+ if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_8) {
+ zend_dump_op_array(op_array, ZEND_DUMP_SSA, "after sccp pass", ssa);
+ }
+#if ZEND_DEBUG_DFA
+ ssa_verify_integrity(op_array, ssa, "after calls");
+#endif
+ }
+
+ 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_PASS_14) {
+ zend_dump_op_array(op_array, ZEND_DUMP_SSA, "after dce pass", ssa);
+ }
+#if ZEND_DEBUG_DFA
+ ssa_verify_integrity(op_array, ssa, "after dce");
+#endif
+ }
+
for (v = op_array->last_var; v < ssa->vars_count; v++) {
op_1 = ssa->vars[v].definition;
@@ -470,24 +633,28 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx
// op_1: ASSIGN #orig_var.CV [undef,scalar] -> #v.CV, CONST|TMPVAR => QM_ASSIGN v.CV, CONST|TMPVAR
- if (zend_ssa_unlink_use_chain(ssa, op_1, orig_var)) {
- /* Reconstruct SSA */
- ssa->ops[op_1].result_def = v;
- ssa->ops[op_1].op1_def = -1;
- ssa->ops[op_1].op1_use = ssa->ops[op_1].op2_use;
- ssa->ops[op_1].op1_use_chain = ssa->ops[op_1].op2_use_chain;
- ssa->ops[op_1].op2_use = -1;
- ssa->ops[op_1].op2_use_chain = -1;
-
- /* Update opcode */
- opline->result_type = opline->op1_type;
- opline->result.var = opline->op1.var;
- opline->op1_type = opline->op2_type;
- opline->op1.var = opline->op2.var;
- opline->op2_type = IS_UNUSED;
- opline->op2.var = 0;
- opline->opcode = ZEND_QM_ASSIGN;
+ if (ssa->ops[op_1].op1_use != ssa->ops[op_1].op2_use) {
+ zend_ssa_unlink_use_chain(ssa, op_1, orig_var);
+ } else {
+ ssa->ops[op_1].op2_use_chain = ssa->ops[op_1].op1_use_chain;
}
+
+ /* Reconstruct SSA */
+ ssa->ops[op_1].result_def = v;
+ ssa->ops[op_1].op1_def = -1;
+ ssa->ops[op_1].op1_use = ssa->ops[op_1].op2_use;
+ ssa->ops[op_1].op1_use_chain = ssa->ops[op_1].op2_use_chain;
+ ssa->ops[op_1].op2_use = -1;
+ ssa->ops[op_1].op2_use_chain = -1;
+
+ /* Update opcode */
+ opline->result_type = opline->op1_type;
+ opline->result.var = opline->op1.var;
+ opline->op1_type = opline->op2_type;
+ opline->op1.var = opline->op2.var;
+ opline->op2_type = IS_UNUSED;
+ opline->op2.var = 0;
+ opline->opcode = ZEND_QM_ASSIGN;
}
}
@@ -577,8 +744,15 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx
}
}
+#if ZEND_DEBUG_DFA
+ ssa_verify_integrity(op_array, ssa, "after dfa");
+#endif
+
if (remove_nops) {
zend_ssa_remove_nops(op_array, ssa);
+#if ZEND_DEBUG_DFA
+ ssa_verify_integrity(op_array, ssa, "after nop");
+#endif
}
}
@@ -598,7 +772,7 @@ void zend_optimize_dfa(zend_op_array *op_array, zend_optimizer_ctx *ctx)
return;
}
- zend_dfa_optimize_op_array(op_array, ctx, &ssa);
+ zend_dfa_optimize_op_array(op_array, ctx, &ssa, NULL);
/* Destroy SSA */
zend_arena_release(&ctx->arena, checkpoint);
diff --git a/ext/opcache/Optimizer/sccp.c b/ext/opcache/Optimizer/sccp.c
new file mode 100644
index 0000000000..326603a0e4
--- /dev/null
+++ b/ext/opcache/Optimizer/sccp.c
@@ -0,0 +1,1445 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, SCCP - Sparse Conditional Constant Propagation |
+ +----------------------------------------------------------------------+
+ | 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 "php.h"
+#include "zend_type_info.h"
+#include "ZendAccelerator.h"
+#include "Optimizer/zend_optimizer_internal.h"
+#include "Optimizer/zend_call_graph.h"
+#include "Optimizer/scdf.h"
+#include "Optimizer/zend_dump.h"
+#include "ext/standard/php_string.h"
+
+/* This implements sparse conditional constant propagation (SCCP) based on the SCDF framework. The
+ * used value lattice is defined as follows:
+ *
+ * BOT < {constant values} < TOP
+ *
+ * TOP indicates an underdefined value, i.e. that we do not yet know the value of variable.
+ * BOT indicates an overdefined value, i.e. that we know the variable to be non-constant.
+ *
+ * All variables are optimistically initialized to TOP, apart from the implicit variables defined
+ * at the start of the first block. Note that variables that MAY_BE_REF are *not* initialized to
+ * BOT. We rely on the fact that any operation resulting in a reference will produce a BOT anyway.
+ * This is better because such operations might never be reached due to the conditional nature of
+ * the algorithm.
+ *
+ * The meet operation for phi functions is defined as follows:
+ * BOT + any = BOT
+ * TOP + any = any
+ * C_i + C_i = C_i (i.e. two equal constants)
+ * C_i + C_j = BOT (i.e. two different constants)
+ *
+ * When evaluating instructions TOP and BOT are handled as follows:
+ * a) If any operand is BOT, the result is BOT. The main exception to this is op1 of ASSIGN, which
+ * is ignored. However, if the op1 MAY_BE_REF we do have to propagate the BOT.
+ * b) Otherwise, if the instruction can never be evaluated (either in general, or with the
+ * specific modifiers) the result is BOT.
+ * c) Otherwise, if any operand is TOP, the result is TOP.
+ * d) Otherwise (at this point all operands are known and constant), if we can compute the result
+ * for these specific constants (without throwing notices or similar) then that is the result.
+ * e) Otherwise the result is BOT.
+ *
+ * It is sometimes possible to determine a result even if one argument is TOP / BOT, e.g. for things
+ * like BOT*0. Right now we don't bother with this -- the only thing that is done is evaluating
+ * TYPE_CHECKS based on the type information.
+ *
+ * Feasible successors for conditional branches are determined as follows:
+ * a) If we don't support the branch type or branch on BOT, all successors are feasible.
+ * b) Otherwise, if we branch on TOP none of the successors are feasible.
+ * c) Otherwise (we branch on a constant), the feasible successors are marked based on the constant
+ * (usually only one successor will be feasible).
+ */
+
+#if 0
+#define SCP_DEBUG(...) php_printf(__VA_ARGS__)
+#else
+#define SCP_DEBUG(...)
+#endif
+
+typedef struct _sccp_ctx {
+ scdf_ctx scdf;
+ zend_call_info **call_map;
+ zval *values;
+ zval top;
+ zval bot;
+} sccp_ctx;
+
+#define TOP ((zend_uchar)-1)
+#define BOT ((zend_uchar)-2)
+#define IS_TOP(zv) (Z_TYPE_P(zv) == TOP)
+#define IS_BOT(zv) (Z_TYPE_P(zv) == BOT)
+
+#define MAKE_TOP(zv) (Z_TYPE_INFO_P(zv) = TOP)
+#define MAKE_BOT(zv) (Z_TYPE_INFO_P(zv) = BOT)
+
+static inline zend_bool value_known(zval *zv) {
+ return !IS_TOP(zv) && !IS_BOT(zv);
+}
+
+/* Sets new value for variable and ensures that it is lower or equal
+ * the previous one in the constant propagation lattice. */
+static void set_value(scdf_ctx *scdf, sccp_ctx *ctx, int var, zval *new) {
+ zval *value = &ctx->values[var];
+ if (IS_BOT(value) || IS_TOP(new)) {
+ return;
+ }
+
+ if (IS_BOT(new)) {
+ SCP_DEBUG("Lowering var %d to BOT\n", var);
+ } else {
+ SCP_DEBUG("Lowering var %d to %Z\n", var, new);
+ }
+
+ if (IS_TOP(value) || IS_BOT(new)) {
+ zval_ptr_dtor_nogc(value);
+ ZVAL_COPY(value, new);
+ scdf_add_to_worklist(scdf, var);
+ return;
+ }
+
+#if ZEND_DEBUG
+ ZEND_ASSERT(zend_is_identical(value, new));
+#endif
+}
+
+static zval *get_op1_value(sccp_ctx *ctx, zend_op *opline, zend_ssa_op *ssa_op) {
+ if (opline->op1_type == IS_CONST) {
+ return CT_CONSTANT_EX(ctx->scdf.op_array, opline->op1.constant);
+ } else if (ssa_op->op1_use != -1) {
+ return &ctx->values[ssa_op->op1_use];
+ } else {
+ return NULL;
+ }
+}
+
+static zval *get_op2_value(sccp_ctx *ctx, zend_op *opline, zend_ssa_op *ssa_op) {
+ if (opline->op2_type == IS_CONST) {
+ return CT_CONSTANT_EX(ctx->scdf.op_array, opline->op2.constant);
+ } else if (ssa_op->op2_use != -1) {
+ return &ctx->values[ssa_op->op2_use];
+ } else {
+ return NULL;
+ }
+}
+
+static zend_bool can_replace_op1(
+ const zend_op_array *op_array, zend_op *opline, zend_ssa_op *ssa_op) {
+ switch (opline->opcode) {
+ case ZEND_PRE_INC:
+ case ZEND_PRE_DEC:
+ case ZEND_PRE_INC_OBJ:
+ case ZEND_PRE_DEC_OBJ:
+ case ZEND_POST_INC:
+ case ZEND_POST_DEC:
+ case ZEND_POST_INC_OBJ:
+ case ZEND_POST_DEC_OBJ:
+ case ZEND_ASSIGN:
+ case ZEND_ASSIGN_REF:
+ case ZEND_ASSIGN_DIM:
+ case ZEND_ASSIGN_OBJ:
+ 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:
+ case ZEND_FETCH_DIM_W:
+ case ZEND_FETCH_DIM_RW:
+ case ZEND_FETCH_DIM_UNSET:
+ case ZEND_FETCH_DIM_FUNC_ARG:
+ case ZEND_FETCH_OBJ_W:
+ case ZEND_FETCH_OBJ_RW:
+ case ZEND_FETCH_OBJ_UNSET:
+ case ZEND_FETCH_OBJ_FUNC_ARG:
+ case ZEND_UNSET_DIM:
+ case ZEND_UNSET_OBJ:
+ case ZEND_SEND_REF:
+ case ZEND_SEND_VAR_EX:
+ case ZEND_SEND_UNPACK:
+ case ZEND_SEND_ARRAY:
+ case ZEND_SEND_USER:
+ case ZEND_FE_RESET_RW:
+ return 0;
+ /* Do not accept CONST */
+ case ZEND_VERIFY_ABSTRACT_CLASS:
+ case ZEND_ADD_INTERFACE:
+ case ZEND_ADD_TRAIT:
+ case ZEND_BIND_TRAITS:
+ case ZEND_ROPE_ADD:
+ case ZEND_ROPE_END:
+ case ZEND_BIND_STATIC:
+ case ZEND_BIND_GLOBAL:
+ case ZEND_MAKE_REF:
+ return 0;
+ case ZEND_UNSET_VAR:
+ case ZEND_ISSET_ISEMPTY_VAR:
+ /* CV has special meaning here - cannot simply be replaced */
+ return (opline->extended_value & ZEND_QUICK_SET) == 0;
+ case ZEND_INIT_ARRAY:
+ case ZEND_ADD_ARRAY_ELEMENT:
+ return !(opline->extended_value & ZEND_ARRAY_ELEMENT_REF);
+ case ZEND_YIELD:
+ return !(op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE);
+ case ZEND_VERIFY_RETURN_TYPE:
+ // TODO: This would require a non-local change ???
+ return 0;
+ default:
+ if (ssa_op->op1_def != -1) {
+ ZEND_ASSERT(0);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static zend_bool can_replace_op2(
+ const zend_op_array *op_array, zend_op *opline, zend_ssa_op *ssa_op) {
+ switch (opline->opcode) {
+ /* Do not accept CONST */
+ case ZEND_DECLARE_INHERITED_CLASS:
+ case ZEND_DECLARE_INHERITED_CLASS_DELAYED:
+ case ZEND_DECLARE_ANON_INHERITED_CLASS:
+ case ZEND_BIND_LEXICAL:
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ return 0;
+ }
+ return 1;
+}
+
+static zend_bool try_replace_op1(
+ sccp_ctx *ctx, zend_op *opline, zend_ssa_op *ssa_op, int var, zval *value) {
+ if (ssa_op->op1_use == var && can_replace_op1(ctx->scdf.op_array, opline, ssa_op)) {
+ zval zv;
+ ZVAL_COPY(&zv, value);
+ if (zend_optimizer_update_op1_const(ctx->scdf.op_array, opline, &zv)) {
+ return 1;
+ } else {
+ // TODO: check the following special cases ???
+ switch (opline->opcode) {
+ case ZEND_FETCH_LIST:
+ case ZEND_CASE:
+ case ZEND_SWITCH_STRING:
+ case ZEND_SWITCH_LONG:
+ if (Z_TYPE(zv) == IS_STRING) {
+ zend_string_hash_val(Z_STR(zv));
+ }
+ opline->op1.constant = zend_optimizer_add_literal(ctx->scdf.op_array, &zv);
+ opline->op1_type = IS_CONST;
+ return 1;
+ }
+ zval_ptr_dtor_nogc(&zv);
+ }
+ }
+ return 0;
+}
+
+static zend_bool try_replace_op2(
+ sccp_ctx *ctx, zend_op *opline, zend_ssa_op *ssa_op, int var, zval *value) {
+ if (ssa_op->op2_use == var && can_replace_op2(ctx->scdf.op_array, opline, ssa_op)) {
+ zval zv;
+ ZVAL_COPY(&zv, value);
+ if (zend_optimizer_update_op2_const(ctx->scdf.op_array, opline, &zv)) {
+ return 1;
+ } else {
+ zval_ptr_dtor_nogc(&zv);
+ }
+ }
+ return 0;
+}
+
+static inline int zval_to_string_offset(zend_long *result, zval *op) {
+ switch (Z_TYPE_P(op)) {
+ case IS_LONG:
+ *result = Z_LVAL_P(op);
+ return SUCCESS;
+ case IS_STRING:
+ if (IS_LONG == is_numeric_string(
+ Z_STRVAL_P(op), Z_STRLEN_P(op), result, NULL, 0)) {
+ return SUCCESS;
+ }
+ return FAILURE;
+ default:
+ return FAILURE;
+ }
+}
+
+static inline int fetch_array_elem(zval **result, zval *op1, zval *op2) {
+ switch (Z_TYPE_P(op2)) {
+ case IS_NULL:
+ *result = zend_hash_find(Z_ARR_P(op1), ZSTR_EMPTY_ALLOC());
+ return SUCCESS;
+ case IS_FALSE:
+ *result = zend_hash_index_find(Z_ARR_P(op1), 0);
+ return SUCCESS;
+ case IS_TRUE:
+ *result = zend_hash_index_find(Z_ARR_P(op1), 1);
+ return SUCCESS;
+ case IS_LONG:
+ *result = zend_hash_index_find(Z_ARR_P(op1), Z_LVAL_P(op2));
+ return SUCCESS;
+ case IS_DOUBLE:
+ *result = zend_hash_index_find(Z_ARR_P(op1), zend_dval_to_lval(Z_DVAL_P(op2)));
+ return SUCCESS;
+ case IS_STRING:
+ *result = zend_symtable_find(Z_ARR_P(op1), Z_STR_P(op2));
+ return SUCCESS;
+ default:
+ return FAILURE;
+ }
+}
+
+static inline int ct_eval_fetch_dim(zval *result, zval *op1, zval *op2, int support_strings) {
+ if (Z_TYPE_P(op1) == IS_ARRAY) {
+ zval *value;
+ if (fetch_array_elem(&value, op1, op2) == SUCCESS && value) {
+ ZVAL_COPY(result, value);
+ return SUCCESS;
+ }
+ } else if (support_strings && Z_TYPE_P(op1) == IS_STRING) {
+ zend_long index;
+ if (zval_to_string_offset(&index, op2) == FAILURE) {
+ return FAILURE;
+ }
+ if (index >= 0 && index < Z_STRLEN_P(op1)) {
+ ZVAL_STR(result, zend_string_init(&Z_STRVAL_P(op1)[index], 1, 0));
+ return SUCCESS;
+ }
+ }
+ return FAILURE;
+}
+
+static inline int ct_eval_isset_dim(zval *result, uint32_t extended_value, zval *op1, zval *op2) {
+ if (Z_TYPE_P(op1) == IS_ARRAY) {
+ zval *value;
+ if (fetch_array_elem(&value, op1, op2) == FAILURE) {
+ return FAILURE;
+ }
+ if (extended_value & ZEND_ISSET) {
+ ZVAL_BOOL(result, value && Z_TYPE_P(value) != IS_NULL);
+ } else {
+ ZEND_ASSERT(extended_value & ZEND_ISEMPTY);
+ ZVAL_BOOL(result, !value || !zend_is_true(value));
+ }
+ return SUCCESS;
+ } else if (Z_TYPE_P(op1) == IS_STRING) {
+ // TODO
+ return FAILURE;
+ } else {
+ ZVAL_BOOL(result, extended_value != ZEND_ISSET);
+ return SUCCESS;
+ }
+}
+
+static inline int ct_eval_add_array_elem(zval *result, zval *value, zval *key) {
+ if (!key) {
+ if ((value = zend_hash_next_index_insert(Z_ARR_P(result), value))) {
+ Z_TRY_ADDREF_P(value);
+ return SUCCESS;
+ }
+ return FAILURE;
+ }
+
+ switch (Z_TYPE_P(key)) {
+ case IS_NULL:
+ value = zend_hash_update(Z_ARR_P(result), ZSTR_EMPTY_ALLOC(), value);
+ break;
+ case IS_FALSE:
+ value = zend_hash_index_update(Z_ARR_P(result), 0, value);
+ break;
+ case IS_TRUE:
+ value = zend_hash_index_update(Z_ARR_P(result), 1, value);
+ break;
+ case IS_LONG:
+ value = zend_hash_index_update(Z_ARR_P(result), Z_LVAL_P(key), value);
+ break;
+ case IS_DOUBLE:
+ value = zend_hash_index_update(
+ Z_ARR_P(result), zend_dval_to_lval(Z_DVAL_P(key)), value);
+ break;
+ case IS_STRING:
+ value = zend_symtable_update(Z_ARR_P(result), Z_STR_P(key), value);
+ break;
+ default:
+ return FAILURE;
+ }
+
+ Z_TRY_ADDREF_P(value);
+ return SUCCESS;
+}
+
+static inline int ct_eval_assign_dim(zval *result, zval *value, zval *key) {
+ switch (Z_TYPE_P(result)) {
+ case IS_NULL:
+ case IS_FALSE:
+ array_init(result);
+ /* break missing intentionally */
+ case IS_ARRAY:
+ return ct_eval_add_array_elem(result, value, key);
+ case IS_STRING:
+ // TODO Before enabling this case, make sure ARRAY_DIM result op is correct
+#if 0
+ zend_long index;
+ zend_string *new_str, *value_str;
+ if (!key || Z_TYPE_P(value) == IS_ARRAY
+ || zval_to_string_offset(&index, key) == FAILURE || index < 0) {
+ return FAILURE;
+ }
+
+ if (index >= Z_STRLEN_P(result)) {
+ new_str = zend_string_alloc(index + 1, 0);
+ memcpy(ZSTR_VAL(new_str), Z_STRVAL_P(result), Z_STRLEN_P(result));
+ memset(ZSTR_VAL(new_str) + Z_STRLEN_P(result), ' ', index - Z_STRLEN_P(result));
+ ZSTR_VAL(new_str)[index + 1] = 0;
+ } else {
+ new_str = zend_string_init(Z_STRVAL_P(result), Z_STRLEN_P(result), 0);
+ }
+
+ value_str = zval_get_string(value);
+ ZVAL_STR(result, new_str);
+ Z_STRVAL_P(result)[index] = ZSTR_VAL(value_str)[0];
+ zend_string_release(value_str);
+#endif
+ return FAILURE;
+ default:
+ return FAILURE;
+ }
+}
+
+static inline int ct_eval_incdec(zval *result, zend_uchar opcode, zval *op1) {
+ ZVAL_COPY(result, op1);
+ if (opcode == ZEND_PRE_INC || opcode == ZEND_POST_INC) {
+ increment_function(result);
+ } else {
+ decrement_function(result);
+ }
+ return SUCCESS;
+}
+
+static inline int ct_eval_isset_isempty(zval *result, uint32_t extended_value, zval *op1) {
+ if (!(extended_value & ZEND_QUICK_SET)) {
+ return FAILURE;
+ }
+
+ if (extended_value & ZEND_ISSET) {
+ ZVAL_BOOL(result, Z_TYPE_P(op1) != IS_NULL);
+ } else {
+ ZEND_ASSERT(extended_value & ZEND_ISEMPTY);
+ ZVAL_BOOL(result, !zend_is_true(op1));
+ }
+ return SUCCESS;
+}
+
+static inline void ct_eval_type_check(zval *result, uint32_t type, zval *op1) {
+ if (type == _IS_BOOL) {
+ ZVAL_BOOL(result, Z_TYPE_P(op1) == IS_TRUE || Z_TYPE_P(op1) == IS_FALSE);
+ } else {
+ ZVAL_BOOL(result, Z_TYPE_P(op1) == type);
+ }
+}
+
+/* The functions chosen here are simple to implement and either likely to affect a branch,
+ * or just happened to be commonly used with constant operands in WP (need to test other
+ * applications as well, of course). */
+static inline int ct_eval_func_call(
+ zval *result, zend_string *name, uint32_t num_args, zval **args) {
+ uint32_t i;
+ zend_execute_data *execute_data;
+ zend_function *func;
+ int overflow;
+
+ if (zend_string_equals_literal(name, "chr")) {
+ zend_long c;
+ if (num_args != 1 || Z_TYPE_P(args[0]) != IS_LONG) {
+ return FAILURE;
+ }
+
+ c = Z_LVAL_P(args[0]) & 0xff;
+ ZVAL_INTERNED_STR(result, ZSTR_CHAR(c));
+ return SUCCESS;
+ } else if (zend_string_equals_literal(name, "count")) {
+ if (num_args != 1 || Z_TYPE_P(args[0]) != IS_ARRAY) {
+ return FAILURE;
+ }
+
+ ZVAL_LONG(result, zend_hash_num_elements(Z_ARRVAL_P(args[0])));
+ return SUCCESS;
+ } else if (zend_string_equals_literal(name, "ini_get")) {
+ zend_ini_entry *ini_entry;
+
+ if (num_args != 1 || Z_TYPE_P(args[0]) != IS_STRING) {
+ return FAILURE;
+ }
+
+ ini_entry = zend_hash_find_ptr(EG(ini_directives), Z_STR_P(args[0]));
+ if (!ini_entry) {
+ ZVAL_FALSE(result);
+ } else if (ini_entry->modifiable != ZEND_INI_SYSTEM) {
+ return FAILURE;
+ } else if (ini_entry->value) {
+ ZVAL_STR_COPY(result, ini_entry->value);
+ } else {
+ ZVAL_EMPTY_STRING(result);
+ }
+ return SUCCESS;
+ } else if (zend_string_equals_literal(name, "in_array")) {
+ if (num_args != 2 || Z_TYPE_P(args[1]) != IS_ARRAY) {
+ return FAILURE;
+ }
+ /* pass */
+ } else if (zend_string_equals_literal(name, "strpos")) {
+ if (num_args != 2
+ || Z_TYPE_P(args[0]) != IS_STRING
+ || Z_TYPE_P(args[1]) != IS_STRING
+ || !Z_STRLEN_P(args[1])) {
+ return FAILURE;
+ }
+ /* pass */
+ } else if (zend_string_equals_literal(name, "array_key_exists")) {
+ if (num_args != 2 || Z_TYPE_P(args[1]) != IS_ARRAY ||
+ (Z_TYPE_P(args[0]) != IS_LONG && Z_TYPE_P(args[0]) != IS_STRING
+ && Z_TYPE_P(args[0]) != IS_NULL)) {
+ return FAILURE;
+ }
+ /* pass */
+ } else if (zend_string_equals_literal(name, "trim")
+ || zend_string_equals_literal(name, "rtrim")
+ || zend_string_equals_literal(name, "ltrim")) {
+ if ((num_args < 1 || num_args > 2) || Z_TYPE_P(args[0]) != IS_STRING
+ || (num_args == 2 && Z_TYPE_P(args[1]) != IS_STRING)) {
+ return FAILURE;
+ }
+ /* pass */
+ } else if (zend_string_equals_literal(name, "array_keys")
+ || zend_string_equals_literal(name, "array_values")) {
+ if (num_args != 1 || Z_TYPE_P(args[0]) != IS_ARRAY) {
+ return FAILURE;
+ }
+ /* pass */
+ } else if (zend_string_equals_literal(name, "array_flip")) {
+ zval *entry;
+
+ if (num_args != 1 || Z_TYPE_P(args[0]) != IS_ARRAY) {
+ return FAILURE;
+ }
+ ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args[0]), entry) {
+ if (Z_TYPE_P(entry) != IS_LONG && Z_TYPE_P(entry) != IS_STRING) {
+ return FAILURE;
+ }
+ } ZEND_HASH_FOREACH_END();
+ /* pass */
+ } else if (zend_string_equals_literal(name, "str_repeat")) {
+ if (num_args != 2
+ || Z_TYPE_P(args[0]) != IS_STRING
+ || Z_TYPE_P(args[1]) != IS_LONG
+ || zend_safe_address(Z_STRLEN_P(args[0]), Z_LVAL_P(args[1]), 0, &overflow) > 64 * 1024
+ || overflow) {
+ return FAILURE;
+ }
+ /* pass */
+ } else if ((zend_string_equals_literal(name, "array_merge")
+ || zend_string_equals_literal(name, "array_replace")
+ || zend_string_equals_literal(name, "array_merge_recursive")
+ || zend_string_equals_literal(name, "array_merge_recursive")
+ || zend_string_equals_literal(name, "array_diff")
+ || zend_string_equals_literal(name, "array_diff_assoc")
+ || zend_string_equals_literal(name, "array_diff_key"))
+ && num_args > 0) {
+ for (i = 0; i < num_args; i++) {
+ if (Z_TYPE_P(args[i]) != IS_ARRAY) {
+ return FAILURE;
+ }
+ }
+ /* pass */
+ } else if (zend_string_equals_literal(name, "implode")) {
+ zval *entry;
+
+ if (!(num_args == 1 && Z_TYPE_P(args[0]) == IS_ARRAY)
+ && !(num_args == 2 && Z_TYPE_P(args[0]) == IS_STRING && Z_TYPE_P(args[1]) == IS_ARRAY)
+ && !(num_args == 2 && Z_TYPE_P(args[0]) == IS_ARRAY && Z_TYPE_P(args[1]) == IS_STRING)) {
+ return FAILURE;
+ }
+
+ if (Z_TYPE_P(args[0]) == IS_ARRAY) {
+ ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args[0]), entry) {
+ if (Z_TYPE_P(entry) > IS_STRING) {
+ return FAILURE;
+ }
+ } ZEND_HASH_FOREACH_END();
+ } else {
+ ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args[1]), entry) {
+ if (Z_TYPE_P(entry) > IS_STRING) {
+ return FAILURE;
+ }
+ } ZEND_HASH_FOREACH_END();
+ }
+ /* pass */
+ } else if (zend_string_equals_literal(name, "version_comapre")) {
+ if ((num_args != 2 && (num_args != 3 || Z_TYPE_P(args[2]) != IS_STRING))
+ || Z_TYPE_P(args[0]) != IS_STRING
+ || Z_TYPE_P(args[1]) != IS_STRING) {
+ return FAILURE;
+ }
+ /* pass */
+ } else {
+ return FAILURE;
+ }
+
+ func = zend_hash_find_ptr(CG(function_table), name);
+ if (!func || func->type != ZEND_INTERNAL_FUNCTION) {
+ return FAILURE;
+ }
+
+ execute_data = safe_emalloc(num_args, sizeof(zval), ZEND_CALL_FRAME_SLOT * sizeof(zval));
+ memset(execute_data, 0, sizeof(zend_execute_data));
+ EX(func) = func;
+ EX_NUM_ARGS() = num_args;
+ for (i = 0; i < num_args; i++) {
+ ZVAL_COPY(EX_VAR_NUM(i), args[i]);
+ }
+ func->internal_function.handler(execute_data, result);
+ for (i = 0; i < num_args; i++) {
+ zval_ptr_dtor_nogc(EX_VAR_NUM(i));
+ }
+ efree(execute_data);
+ return SUCCESS;
+}
+
+#define SET_RESULT(op, zv) do { \
+ if (ssa_op->op##_def >= 0) { \
+ set_value(scdf, ctx, ssa_op->op##_def, zv); \
+ } \
+} while (0)
+#define SET_RESULT_BOT(op) SET_RESULT(op, &ctx->bot)
+#define SET_RESULT_TOP(op) SET_RESULT(op, &ctx->top)
+
+#define SKIP_IF_TOP(op) if (IS_TOP(op)) break;
+
+static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_op) {
+ sccp_ctx *ctx = (sccp_ctx *) scdf;
+ zval *op1, *op2, zv; /* zv is a temporary to hold result values */
+
+ op1 = get_op1_value(ctx, opline, ssa_op);
+ op2 = get_op2_value(ctx, opline, ssa_op);
+
+ switch (opline->opcode) {
+ case ZEND_ASSIGN:
+ /* The value of op1 is irrelevant here, because we are overwriting it
+ * -- unless it can be a reference, in which case we propagate a BOT. */
+ if (IS_BOT(op1) && (ctx->scdf.ssa->var_info[ssa_op->op1_use].type & MAY_BE_REF)) {
+ SET_RESULT_BOT(op1);
+ } else {
+ SET_RESULT(op1, op2);
+ }
+
+ SET_RESULT(result, op2);
+ return;
+ case ZEND_TYPE_CHECK:
+ /* We may be able to evaluate TYPE_CHECK based on type inference info,
+ * even if we don't know the precise value. */
+ if (!value_known(op1)) {
+ uint32_t type = ctx->scdf.ssa->var_info[ssa_op->op1_use].type;
+ uint32_t expected_type = opline->extended_value == _IS_BOOL
+ ? (MAY_BE_TRUE|MAY_BE_FALSE) : (1 << opline->extended_value);
+ if (!(type & expected_type) && !(type & MAY_BE_UNDEF)) {
+ ZVAL_FALSE(&zv);
+ SET_RESULT(result, &zv);
+ return;
+ } else if (!(type & ((MAY_BE_ANY|MAY_BE_UNDEF) - expected_type))
+ && opline->extended_value != IS_RESOURCE) {
+ ZVAL_TRUE(&zv);
+ SET_RESULT(result, &zv);
+ return;
+ }
+ }
+ break;
+ case ZEND_ASSIGN_DIM:
+ /* If $a in $a[$b]=$c is UNDEF, treat it like NULL. There is no warning. */
+ if ((ctx->scdf.ssa->var_info[ssa_op->op1_use].type & MAY_BE_ANY) == 0) {
+ op1 = &EG(uninitialized_zval);
+ }
+ break;
+ case ZEND_SEND_VAL:
+ case ZEND_SEND_VAR:
+ {
+ /* If the value of a SEND for an ICALL changes, we need to reconsider the
+ * ICALL result value. Otherwise we can ignore the opcode. */
+ zend_call_info *call;
+ if (!ctx->call_map) {
+ return;
+ }
+
+ call = ctx->call_map[opline - ctx->scdf.op_array->opcodes];
+ if (IS_TOP(op1) || !call || call->caller_call_opline->opcode != ZEND_DO_ICALL) {
+ return;
+ }
+
+ opline = call->caller_call_opline;
+ ssa_op = &ctx->scdf.ssa->ops[opline - ctx->scdf.op_array->opcodes];
+ break;
+ }
+ }
+
+ if ((op1 && IS_BOT(op1)) || (op2 && IS_BOT(op2))) {
+ /* If any operand is BOT, mark the result as BOT right away.
+ * Exceptions to this rule are handled above. */
+ SET_RESULT_BOT(result);
+ SET_RESULT_BOT(op1);
+ SET_RESULT_BOT(op2);
+ return;
+ }
+
+ switch (opline->opcode) {
+ case ZEND_ADD:
+ case ZEND_SUB:
+ case ZEND_MUL:
+ case ZEND_DIV:
+ case ZEND_MOD:
+ case ZEND_POW:
+ case ZEND_SL:
+ case ZEND_SR:
+ case ZEND_CONCAT:
+ case ZEND_FAST_CONCAT:
+ case ZEND_IS_EQUAL:
+ case ZEND_IS_NOT_EQUAL:
+ case ZEND_IS_SMALLER:
+ case ZEND_IS_SMALLER_OR_EQUAL:
+ case ZEND_IS_IDENTICAL:
+ case ZEND_IS_NOT_IDENTICAL:
+ case ZEND_BW_OR:
+ case ZEND_BW_AND:
+ case ZEND_BW_XOR:
+ case ZEND_BOOL_XOR:
+ case ZEND_CASE:
+ SKIP_IF_TOP(op1);
+ SKIP_IF_TOP(op2);
+
+ if (zend_optimizer_eval_binary_op(&zv, opline->opcode, op1, op2) == SUCCESS) {
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ SET_RESULT_BOT(result);
+ break;
+ 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:
+ /* Obj/dim compound assign */
+ if (opline->extended_value) {
+ SET_RESULT_BOT(op1);
+ SET_RESULT_BOT(result);
+ break;
+ }
+
+ SKIP_IF_TOP(op1);
+ SKIP_IF_TOP(op2);
+
+ if (zend_optimizer_eval_binary_op(&zv, zend_compound_assign_to_binary_op(opline->opcode), op1, op2) == SUCCESS) {
+ SET_RESULT(op1, &zv);
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ SET_RESULT_BOT(op1);
+ SET_RESULT_BOT(result);
+ break;
+ case ZEND_PRE_INC:
+ case ZEND_PRE_DEC:
+ SKIP_IF_TOP(op1);
+ if (ct_eval_incdec(&zv, opline->opcode, op1) == SUCCESS) {
+ SET_RESULT(op1, &zv);
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ SET_RESULT_BOT(op1);
+ SET_RESULT_BOT(result);
+ break;
+ case ZEND_POST_INC:
+ case ZEND_POST_DEC:
+ SKIP_IF_TOP(op1);
+ SET_RESULT(result, op1);
+ if (ct_eval_incdec(&zv, opline->opcode, op1) == SUCCESS) {
+ SET_RESULT(op1, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ SET_RESULT_BOT(op1);
+ break;
+ case ZEND_BW_NOT:
+ case ZEND_BOOL_NOT:
+ SKIP_IF_TOP(op1);
+ if (zend_optimizer_eval_unary_op(&zv, opline->opcode, op1) == SUCCESS) {
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ SET_RESULT_BOT(result);
+ break;
+ case ZEND_CAST:
+ SKIP_IF_TOP(op1);
+ if (zend_optimizer_eval_cast(&zv, opline->extended_value, op1) == SUCCESS) {
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ SET_RESULT_BOT(result);
+ break;
+ case ZEND_BOOL:
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ SKIP_IF_TOP(op1);
+ ZVAL_BOOL(&zv, zend_is_true(op1));
+ SET_RESULT(result, &zv);
+ break;
+ case ZEND_STRLEN:
+ SKIP_IF_TOP(op1);
+ if (zend_optimizer_eval_strlen(&zv, op1) == SUCCESS) {
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ SET_RESULT_BOT(result);
+ break;
+ case ZEND_COUNT:
+ SKIP_IF_TOP(op1);
+ if (Z_TYPE_P(op1) == IS_ARRAY) {
+ ZVAL_LONG(&zv, zend_hash_num_elements(Z_ARRVAL_P(op1)));
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ SET_RESULT_BOT(result);
+ break;
+ case ZEND_FETCH_DIM_R:
+ case ZEND_FETCH_DIM_IS:
+ case ZEND_FETCH_LIST:
+ SKIP_IF_TOP(op1);
+ SKIP_IF_TOP(op2);
+
+ if (ct_eval_fetch_dim(&zv, op1, op2, (opline->opcode != ZEND_FETCH_LIST)) == SUCCESS) {
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ SET_RESULT_BOT(result);
+ break;
+ case ZEND_ISSET_ISEMPTY_DIM_OBJ:
+ SKIP_IF_TOP(op1);
+ SKIP_IF_TOP(op2);
+
+ if (ct_eval_isset_dim(&zv, opline->extended_value, op1, op2) == SUCCESS) {
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ SET_RESULT_BOT(result);
+ break;
+ case ZEND_QM_ASSIGN:
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ SET_RESULT(result, op1);
+ break;
+ case ZEND_FETCH_CLASS:
+ if (!op1) {
+ SET_RESULT_BOT(result);
+ break;
+ }
+ SET_RESULT(result, op1);
+ break;
+ case ZEND_ISSET_ISEMPTY_VAR:
+ SKIP_IF_TOP(op1);
+ if (ct_eval_isset_isempty(&zv, opline->extended_value, op1) == SUCCESS) {
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ SET_RESULT_BOT(result);
+ break;
+ case ZEND_TYPE_CHECK:
+ SKIP_IF_TOP(op1);
+ ct_eval_type_check(&zv, opline->extended_value, op1);
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ case ZEND_INSTANCEOF:
+ SKIP_IF_TOP(op1);
+ ZVAL_FALSE(&zv);
+ SET_RESULT(result, &zv);
+ break;
+ case ZEND_ROPE_INIT:
+ SKIP_IF_TOP(op2);
+ if (zend_optimizer_eval_cast(&zv, IS_STRING, op2) == SUCCESS) {
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ SET_RESULT_BOT(result);
+ break;
+ case ZEND_ROPE_ADD:
+ case ZEND_ROPE_END:
+ // TODO The way this is currently implemented will result in quadratic runtime
+ // This is not necessary, the way the algorithm works it's okay to reuse the same
+ // string for all SSA vars with some extra checks
+ SKIP_IF_TOP(op1);
+ SKIP_IF_TOP(op2);
+ if (zend_optimizer_eval_binary_op(&zv, ZEND_CONCAT, op1, op2) == SUCCESS) {
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ SET_RESULT_BOT(result);
+ break;
+ case ZEND_INIT_ARRAY:
+ case ZEND_ADD_ARRAY_ELEMENT:
+ {
+ zval *result = NULL;
+ if (opline->extended_value & ZEND_ARRAY_ELEMENT_REF) {
+ SET_RESULT_BOT(result);
+ SET_RESULT_BOT(op1);
+ break;
+ }
+
+ if (opline->opcode == ZEND_ADD_ARRAY_ELEMENT) {
+ result = &ctx->values[ssa_op->result_use];
+ if (IS_BOT(result)) {
+ SET_RESULT_BOT(result);
+ break;
+ }
+ SKIP_IF_TOP(result);
+ }
+
+ SKIP_IF_TOP(op1);
+ if (op2) {
+ SKIP_IF_TOP(op2);
+ }
+
+ /* We want to avoid keeping around intermediate arrays for each SSA variable in the
+ * ADD_ARRAY_ELEMENT chain. We do this by only keeping the array on the last opcode
+ * and use a NULL value everywhere else. */
+ if (Z_TYPE(ctx->values[ssa_op->result_def]) == IS_NULL) {
+ break;
+ }
+
+ if (result) {
+ ZVAL_COPY_VALUE(&zv, result);
+ ZVAL_NULL(result);
+ } else {
+ array_init(&zv);
+ }
+
+ if (ct_eval_add_array_elem(&zv, op1, op2) == SUCCESS) {
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ SET_RESULT_BOT(result);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ case ZEND_ASSIGN_DIM:
+ {
+ zval *data = get_op1_value(ctx, opline+1, ssa_op+1);
+ if (IS_BOT(data)) {
+ SET_RESULT_BOT(op1);
+ SET_RESULT_BOT(result);
+ break;
+ }
+
+ SKIP_IF_TOP(data);
+ SKIP_IF_TOP(op1);
+ if (op2) {
+ SKIP_IF_TOP(op2);
+ }
+
+ ZVAL_DUP(&zv, op1);
+ if (ct_eval_assign_dim(&zv, data, op2) == SUCCESS) {
+ SET_RESULT(result, data);
+ SET_RESULT(op1, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ SET_RESULT_BOT(result);
+ SET_RESULT_BOT(op1);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ case ZEND_DO_ICALL:
+ {
+ zend_call_info *call;
+ zval *name, *args[2] = {NULL};
+ int i;
+
+ if (!ctx->call_map) {
+ SET_RESULT_BOT(result);
+ break;
+ }
+
+ call = ctx->call_map[opline - ctx->scdf.op_array->opcodes];
+ name = CT_CONSTANT_EX(ctx->scdf.op_array, call->caller_init_opline->op2.constant);
+
+ /* We already know it can't be evaluated, don't bother checking again */
+ if (ssa_op->result_def < 0 || IS_BOT(&ctx->values[ssa_op->result_def])) {
+ break;
+ }
+
+ /* We're only interested in functions with one, two or three arguments right now */
+ if (call->num_args == 0 || call->num_args > 3) {
+ SET_RESULT_BOT(result);
+ break;
+ }
+
+ for (i = 0; i < call->num_args; i++) {
+ zend_op *opline = call->arg_info[i].opline;
+ if (opline->opcode != ZEND_SEND_VAL && opline->opcode != ZEND_SEND_VAR) {
+ SET_RESULT_BOT(result);
+ return;
+ }
+
+ args[i] = get_op1_value(ctx, opline,
+ &ctx->scdf.ssa->ops[opline - ctx->scdf.op_array->opcodes]);
+ if (args[i]) {
+ if (IS_BOT(args[i])) {
+ SET_RESULT_BOT(result);
+ return;
+ } else if (IS_TOP(args[i])) {
+ return;
+ }
+ }
+ }
+
+ /* We didn't get a BOT argument, so value stays the same */
+ if (!IS_TOP(&ctx->values[ssa_op->result_def])) {
+ break;
+ }
+
+ if (ct_eval_func_call(&zv, Z_STR_P(name), call->num_args, args) == SUCCESS) {
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+
+#if 0
+ /* sort out | uniq -c | sort -n */
+ fprintf(stderr, "%s\n", Z_STRVAL_P(name));
+ /*if (args[1]) {
+ php_printf("%s %Z %Z\n", Z_STRVAL_P(name), args[0], args[1]);
+ } else {
+ php_printf("%s %Z\n", Z_STRVAL_P(name), args[0]);
+ }*/
+#endif
+
+ SET_RESULT_BOT(result);
+ break;
+ }
+ default:
+ {
+ /* If we have no explicit implementation return BOT */
+ SET_RESULT_BOT(result);
+ SET_RESULT_BOT(op1);
+ SET_RESULT_BOT(op2);
+ break;
+ }
+ }
+}
+
+/* Returns whether there is a successor */
+static void sccp_mark_feasible_successors(
+ scdf_ctx *scdf,
+ int block_num, zend_basic_block *block,
+ zend_op *opline, zend_ssa_op *ssa_op) {
+ sccp_ctx *ctx = (sccp_ctx *) scdf;
+ zval *op1;
+ int s;
+
+ /* We can't determine the branch target at compile-time for these */
+ switch (opline->opcode) {
+ case ZEND_ASSERT_CHECK:
+ case ZEND_CATCH:
+ case ZEND_DECLARE_ANON_CLASS:
+ case ZEND_DECLARE_ANON_INHERITED_CLASS:
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ scdf_mark_edge_feasible(scdf, block_num, block->successors[0]);
+ scdf_mark_edge_feasible(scdf, block_num, block->successors[1]);
+ return;
+ }
+
+ op1 = get_op1_value(ctx, opline, ssa_op);
+
+ /* Branch target can be either one */
+ if (!op1 || IS_BOT(op1)) {
+ for (s = 0; s < block->successors_count; s++) {
+ scdf_mark_edge_feasible(scdf, block_num, block->successors[s]);
+ }
+ return;
+ }
+
+ /* Branch target not yet known */
+ if (IS_TOP(op1)) {
+ return;
+ }
+
+ switch (opline->opcode) {
+ case ZEND_JMPZ:
+ case ZEND_JMPZNZ:
+ case ZEND_JMPZ_EX:
+ s = zend_is_true(op1);
+ break;
+ case ZEND_JMPNZ:
+ case ZEND_JMPNZ_EX:
+ case ZEND_JMP_SET:
+ s = !zend_is_true(op1);
+ break;
+ case ZEND_COALESCE:
+ s = (Z_TYPE_P(op1) == IS_NULL);
+ break;
+ case ZEND_FE_RESET_R:
+ case ZEND_FE_RESET_RW:
+ if (Z_TYPE_P(op1) != IS_ARRAY) {
+ scdf_mark_edge_feasible(scdf, block_num, block->successors[0]);
+ scdf_mark_edge_feasible(scdf, block_num, block->successors[1]);
+ return;
+ }
+ s = zend_hash_num_elements(Z_ARR_P(op1)) != 0;
+ break;
+ default:
+ for (s = 0; s < block->successors_count; s++) {
+ scdf_mark_edge_feasible(scdf, block_num, block->successors[s]);
+ }
+ return;
+ }
+ scdf_mark_edge_feasible(scdf, block_num, block->successors[s]);
+}
+
+static void join_phi_values(zval *a, zval *b) {
+ if (IS_BOT(a) || IS_TOP(b)) {
+ return;
+ }
+ if (IS_TOP(a)) {
+ zval_ptr_dtor_nogc(a);
+ ZVAL_COPY(a, b);
+ return;
+ }
+ if (IS_BOT(b) || !zend_is_identical(a, b)) {
+ zval_ptr_dtor_nogc(a);
+ MAKE_BOT(a);
+ }
+}
+
+static void sccp_visit_phi(scdf_ctx *scdf, zend_ssa_phi *phi) {
+ sccp_ctx *ctx = (sccp_ctx *) scdf;
+ zend_ssa *ssa = scdf->ssa;
+ ZEND_ASSERT(phi->ssa_var >= 0);
+ if (!IS_BOT(&ctx->values[phi->ssa_var])) {
+ zend_basic_block *block = &ssa->cfg.blocks[phi->block];
+ int *predecessors = &ssa->cfg.predecessors[block->predecessor_offset];
+
+ int i;
+ zval result;
+ MAKE_TOP(&result);
+ SCP_DEBUG("Handling PHI(");
+ if (phi->pi >= 0) {
+ ZEND_ASSERT(phi->sources[0] >= 0);
+ if (scdf_is_edge_feasible(scdf, phi->pi, phi->block)) {
+ join_phi_values(&result, &ctx->values[phi->sources[0]]);
+ }
+ } else {
+ for (i = 0; i < block->predecessors_count; i++) {
+ ZEND_ASSERT(phi->sources[i] >= 0);
+ if (scdf_is_edge_feasible(scdf, predecessors[i], phi->block)) {
+ SCP_DEBUG("val, ");
+ join_phi_values(&result, &ctx->values[phi->sources[i]]);
+ } else {
+ SCP_DEBUG("--, ");
+ }
+ }
+ }
+ SCP_DEBUG(")\n");
+
+ set_value(scdf, ctx, phi->ssa_var, &result);
+ zval_ptr_dtor_nogc(&result);
+ }
+}
+
+static zval *value_from_type_and_range(sccp_ctx *ctx, int var_num, zval *tmp) {
+ zend_ssa *ssa = ctx->scdf.ssa;
+ zend_ssa_var_info *info = &ssa->var_info[var_num];
+
+ if (ssa->vars[var_num].var >= ctx->scdf.op_array->last_var) {
+ // TODO Non-CVs may cause issues with FREEs
+ return NULL;
+ }
+
+ if (info->type & MAY_BE_UNDEF) {
+ return NULL;
+ }
+
+ if (!(info->type & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_NULL))) {
+ ZVAL_NULL(tmp);
+ return tmp;
+ }
+ if (!(info->type & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_FALSE))) {
+ ZVAL_FALSE(tmp);
+ return tmp;
+ }
+ if (!(info->type & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_TRUE))) {
+ ZVAL_TRUE(tmp);
+ return tmp;
+ }
+
+ if (!(info->type & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG))
+ && info->has_range
+ && !info->range.overflow && !info->range.underflow
+ && info->range.min == info->range.max) {
+ ZVAL_LONG(tmp, info->range.min);
+ return tmp;
+ }
+
+ return NULL;
+}
+
+/* This will try to replace uses of SSA variables we have determined to be constant. Not all uses
+ * can be replaced, because some instructions don't accept constant operands or only accept them
+ * if they have a certain type. */
+static int replace_constant_operands(sccp_ctx *ctx) {
+ zend_ssa *ssa = ctx->scdf.ssa;
+ zend_op_array *op_array = ctx->scdf.op_array;
+ int i;
+ zval tmp;
+ int removed_ops = 0;
+
+ /* We iterate the variables backwards, so we can eliminate sequences like INIT_ROPE
+ * and INIT_ARRAY. */
+ for (i = ssa->vars_count - 1; i >= op_array->last_var; i--) {
+ zend_ssa_var *var = &ssa->vars[i];
+ zval *value;
+ int use;
+
+ if (value_known(&ctx->values[i])) {
+ value = &ctx->values[i];
+ } else {
+ value = value_from_type_and_range(ctx, i, &tmp);
+ if (!value) {
+ continue;
+ }
+ }
+
+ FOREACH_USE(var, use) {
+ zend_op *opline = &op_array->opcodes[use];
+ zend_ssa_op *ssa_op = &ssa->ops[use];
+ if (try_replace_op1(ctx, opline, ssa_op, i, value)) {
+ if (opline->opcode == ZEND_NOP) {
+ removed_ops++;
+ }
+ ZEND_ASSERT(ssa_op->op1_def == -1);
+ if (ssa_op->op1_use != ssa_op->op2_use) {
+ zend_ssa_unlink_use_chain(ssa, use, ssa_op->op1_use);
+ } else {
+ ssa_op->op2_use_chain = ssa_op->op1_use_chain;
+ }
+ ssa_op->op1_use = -1;
+ ssa_op->op1_use_chain = -1;
+ }
+ if (try_replace_op2(ctx, opline, ssa_op, i, value)) {
+ ZEND_ASSERT(ssa_op->op2_def == -1);
+ zend_ssa_unlink_use_chain(ssa, use, ssa_op->op2_use);
+ ssa_op->op2_use = -1;
+ ssa_op->op2_use_chain = -1;
+ }
+ } FOREACH_USE_END();
+
+ /* This is a basic DCE pass we run after SCCP. It only works on those instructions those result
+ * value(s) were determined by SCCP. It removes dead computational instructions and converts
+ * CV-affecting instructions into CONST ASSIGNs. This basic DCE is performed for multiple reasons:
+ * a) During operand replacement we eliminate FREEs. The corresponding computational instructions
+ * must be removed to avoid leaks. This way SCCP can run independently of the full DCE pass.
+ * b) The main DCE pass relies on type analysis to determine whether instructions have side-effects
+ * and can't be DCEd. This means that it will not be able collect all instructions rendered dead
+ * by SCCP, because they may have potentially side-effecting types, but the actual values are
+ * not. As such doing DCE here will allow us to eliminate more dead code in combination.
+ * c) The ordinary DCE pass cannot collect dead calls. However SCCP can result in dead calls, which
+ * we need to collect. */
+
+ if (var->definition >= 0 && value_known(&ctx->values[i])) {
+ zend_op *opline = &op_array->opcodes[var->definition];
+ zend_ssa_op *ssa_op = &ssa->ops[var->definition];
+ if (opline->opcode == ZEND_ASSIGN) {
+ /* Leave assigns to DCE (due to dtor effects) */
+ continue;
+ }
+
+ if (ssa_op->result_def == i
+ && ssa_op->op1_def < 0
+ && ssa_op->op2_def < 0
+ && var->use_chain < 0
+ && var->phi_use_chain == NULL) {
+ if (opline->opcode == ZEND_DO_ICALL) {
+ /* Call instruction -> remove opcodes that are part of the call */
+ zend_call_info *call;
+ int i;
+
+ ZEND_ASSERT(ctx->call_map);
+ call = ctx->call_map[var->definition];
+ ZEND_ASSERT(call);
+ ZEND_ASSERT(call->caller_call_opline == opline);
+ if (opline->result_type & (IS_TMP_VAR|IS_VAR)) {
+ zend_optimizer_remove_live_range_ex(op_array, opline->result.var, var->definition + 1);
+ }
+ zend_ssa_remove_result_def(ssa, ssa_op);
+ zend_ssa_remove_instr(ssa, opline, ssa_op);
+ zend_ssa_remove_instr(ssa, call->caller_init_opline,
+ &ssa->ops[call->caller_init_opline - op_array->opcodes]);
+
+ for (i = 0; i < call->num_args; i++) {
+ zend_ssa_remove_instr(ssa, call->arg_info[i].opline,
+ &ssa->ops[call->arg_info[i].opline - op_array->opcodes]);
+ }
+ removed_ops = call->num_args + 2;
+
+ // TODO: remove call_info completely???
+ call->callee_func = NULL;
+ } else {
+ /* Ordinary computational instruction -> remove it */
+ if (opline->result_type & (IS_TMP_VAR|IS_VAR)) {
+ zend_optimizer_remove_live_range_ex(op_array, opline->result.var, var->definition + 1);
+ }
+ zend_ssa_remove_result_def(ssa, ssa_op);
+ zend_ssa_remove_instr(ssa, opline, ssa_op);
+ removed_ops++;
+ }
+ } else if (ssa_op->op1_def == i) {
+ /* Compound assign or incdec -> convert to direct ASSIGN */
+
+ /* Destroy previous op2 */
+ if (opline->op2_type == IS_CONST) {
+ literal_dtor(&ZEND_OP2_LITERAL(opline));
+ } else if (ssa_op->op2_use >= 0) {
+ if (ssa_op->op2_use != ssa_op->op1_use) {
+ zend_ssa_unlink_use_chain(ssa, var->definition, ssa_op->op2_use);
+ }
+ ssa_op->op2_use = -1;
+ ssa_op->op2_use_chain = -1;
+ }
+
+ /* Mark result unused, if possible */
+ if (ssa_op->result_def >= 0
+ && ssa->vars[ssa_op->result_def].use_chain < 0
+ && ssa->vars[ssa_op->result_def].phi_use_chain == NULL) {
+ if (opline->result_type & (IS_TMP_VAR|IS_VAR)) {
+ zend_optimizer_remove_live_range_ex(op_array, opline->result.var, var->definition + 1);
+ }
+ zend_ssa_remove_result_def(ssa, ssa_op);
+ opline->result_type = IS_UNUSED;
+ }
+
+ /* Remove OP_DATA opcode */
+ if (opline->opcode == ZEND_ASSIGN_DIM) {
+ removed_ops++;
+ zend_ssa_remove_instr(ssa, opline + 1, ssa_op + 1);
+ }
+
+ /* Convert to ASSIGN */
+ opline->opcode = ZEND_ASSIGN;
+ opline->op2_type = IS_CONST;
+ opline->op2.constant = zend_optimizer_add_literal(op_array, value);
+ Z_TRY_ADDREF_P(value);
+ }
+ }
+ if (var->definition_phi
+ && value_known(&ctx->values[i])
+ && var->use_chain < 0
+ && var->phi_use_chain == NULL) {
+ zend_ssa_remove_phi(ssa, var->definition_phi);
+ }
+ }
+
+ return removed_ops;
+}
+
+static void sccp_context_init(zend_optimizer_ctx *ctx, sccp_ctx *sccp,
+ zend_ssa *ssa, zend_op_array *op_array, zend_call_info **call_map) {
+ int i;
+ sccp->call_map = call_map;
+ sccp->values = zend_arena_alloc(&ctx->arena, sizeof(zval) * ssa->vars_count);
+
+ MAKE_TOP(&sccp->top);
+ MAKE_BOT(&sccp->bot);
+
+ i = 0;
+ for (; i < op_array->last_var; ++i) {
+ /* These are all undefined variables, which we have to mark BOT.
+ * Otherwise the undefined variable warning might not be preserved. */
+ MAKE_BOT(&sccp->values[i]);
+ }
+ for (; i < ssa->vars_count; ++i) {
+ if (ssa->vars[i].alias) {
+ MAKE_BOT(&sccp->values[i]);
+ } else {
+ MAKE_TOP(&sccp->values[i]);
+ }
+ }
+}
+
+static void sccp_context_free(sccp_ctx *sccp) {
+ int i;
+ for (i = sccp->scdf.op_array->last_var; i < sccp->scdf.ssa->vars_count; ++i) {
+ zval_ptr_dtor_nogc(&sccp->values[i]);
+ }
+}
+
+int sccp_optimize_op_array(zend_optimizer_ctx *ctx, zend_op_array *op_array, zend_ssa *ssa, zend_call_info **call_map)
+{
+ sccp_ctx sccp;
+ int removed_ops = 0;
+ void *checkpoint = zend_arena_checkpoint(ctx->arena);
+
+ sccp_context_init(ctx, &sccp, ssa, op_array, call_map);
+
+ sccp.scdf.handlers.visit_instr = sccp_visit_instr;
+ sccp.scdf.handlers.visit_phi = sccp_visit_phi;
+ sccp.scdf.handlers.mark_feasible_successors = sccp_mark_feasible_successors;
+
+ scdf_init(ctx, &sccp.scdf, op_array, ssa);
+ scdf_solve(&sccp.scdf, "SCCP");
+
+ removed_ops += scdf_remove_unreachable_blocks(&sccp.scdf);
+ removed_ops += replace_constant_operands(&sccp);
+
+ sccp_context_free(&sccp);
+ zend_arena_release(&ctx->arena, checkpoint);
+
+ return removed_ops;
+}
diff --git a/ext/opcache/Optimizer/scdf.c b/ext/opcache/Optimizer/scdf.c
new file mode 100644
index 0000000000..add8741af6
--- /dev/null
+++ b/ext/opcache/Optimizer/scdf.c
@@ -0,0 +1,228 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, Sparse Conditional Data Flow Propagation Framework |
+ +----------------------------------------------------------------------+
+ | 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/scdf.h"
+
+/* This defines a generic framework for sparse conditional dataflow propagation. The algorithm is
+ * based on "Sparse conditional constant propagation" by Wegman and Zadeck. We're using a
+ * generalized implementation as described in chapter 8.3 of the SSA book.
+ *
+ * Every SSA variable is associated with an element on a finite-height lattice, those value can only
+ * ever be lowered during the operation of the algorithm. If a value is lowered all instructions and
+ * phis using that value need to be reconsidered (this is done by adding the variable to a
+ * worklist). For phi functions the result is computed by applying the meet operation to the
+ * operands. This continues until a fixed point is reached.
+ *
+ * The algorithm is control-flow sensitive: All blocks except the start block are initially assumed
+ * to be unreachable. When considering a branch instruction, we determine the feasible successors
+ * based on the current state of the variable lattice. If a new edge becomes feasible we either have
+ * to mark the successor block executable and consider all instructions in it, or, if the target is
+ * already executable, we only have to reconsider the phi functions (as we only consider phi
+ * operands which are associated with a feasible edge).
+ *
+ * The generic framework requires the definition of three functions:
+ * * visit_instr() should recompute the lattice values of all SSA variables defined by an
+ * instruction.
+ * * visit_phi() should recompute the lattice value of the SSA variable defined by the phi. While
+ * doing this it should only consider operands for which scfg_is_edge_feasible() returns true.
+ * * get_feasible_successors() should determine the feasible successors for a branch instruction.
+ * Note that this callback only needs to handle conditional branches (with two successors).
+ */
+
+#if 0
+#define DEBUG_PRINT(...) fprintf(stderr, __VA_ARGS__)
+#else
+#define DEBUG_PRINT(...)
+#endif
+
+void scdf_mark_edge_feasible(scdf_ctx *scdf, int from, int to) {
+ uint32_t edge = scdf_edge(&scdf->ssa->cfg, from, to);
+
+ if (zend_bitset_in(scdf->feasible_edges, edge)) {
+ /* We already handled this edge */
+ return;
+ }
+
+ DEBUG_PRINT("Marking edge %d->%d feasible\n", from, to);
+ zend_bitset_incl(scdf->feasible_edges, edge);
+
+ if (!zend_bitset_in(scdf->executable_blocks, to)) {
+ if (!zend_bitset_in(scdf->block_worklist, to)) {
+ DEBUG_PRINT("Adding block %d to worklist\n", to);
+ }
+ zend_bitset_incl(scdf->block_worklist, to);
+ } else {
+ /* Block is already executable, only a new edge became feasible.
+ * Reevaluate phi nodes to account for changed source operands. */
+ zend_ssa_block *ssa_block = &scdf->ssa->blocks[to];
+ zend_ssa_phi *phi;
+ for (phi = ssa_block->phis; phi; phi = phi->next) {
+ zend_bitset_excl(scdf->phi_var_worklist, phi->ssa_var);
+ scdf->handlers.visit_phi(scdf, phi);
+ }
+ }
+}
+
+void scdf_init(zend_optimizer_ctx *ctx, scdf_ctx *scdf, zend_op_array *op_array, zend_ssa *ssa) {
+ uint32_t edges_count = 0;
+ int b;
+
+ for (b = 0; b < ssa->cfg.blocks_count; b++) {
+ edges_count += ssa->cfg.blocks[b].predecessors_count;
+ }
+
+ scdf->op_array = op_array;
+ scdf->ssa = ssa;
+
+ scdf->instr_worklist_len = zend_bitset_len(op_array->last);
+ scdf->phi_var_worklist_len = zend_bitset_len(ssa->vars_count);
+ scdf->block_worklist_len = zend_bitset_len(ssa->cfg.blocks_count);
+
+ scdf->instr_worklist = zend_arena_calloc(&ctx->arena,
+ scdf->instr_worklist_len + scdf->phi_var_worklist_len + 2 * scdf->block_worklist_len + zend_bitset_len(edges_count),
+ sizeof(zend_ulong));
+
+ scdf->phi_var_worklist = scdf->instr_worklist + scdf->instr_worklist_len;
+ scdf->block_worklist = scdf->phi_var_worklist + scdf->phi_var_worklist_len;
+ scdf->executable_blocks = scdf->block_worklist + scdf->block_worklist_len;
+ scdf->feasible_edges = scdf->executable_blocks + scdf->block_worklist_len;
+
+ zend_bitset_incl(scdf->block_worklist, 0);
+ zend_bitset_incl(scdf->executable_blocks, 0);
+}
+
+void scdf_solve(scdf_ctx *scdf, const char *name) {
+ zend_ssa *ssa = scdf->ssa;
+ DEBUG_PRINT("Start SCDF solve (%s)\n", name);
+ while (!zend_bitset_empty(scdf->instr_worklist, scdf->instr_worklist_len)
+ || !zend_bitset_empty(scdf->phi_var_worklist, scdf->phi_var_worklist_len)
+ || !zend_bitset_empty(scdf->block_worklist, scdf->block_worklist_len)
+ ) {
+ int i;
+ while ((i = zend_bitset_pop_first(scdf->phi_var_worklist, scdf->phi_var_worklist_len)) >= 0) {
+ zend_ssa_phi *phi = ssa->vars[i].definition_phi;
+ ZEND_ASSERT(phi);
+ if (zend_bitset_in(scdf->executable_blocks, phi->block)) {
+ scdf->handlers.visit_phi(scdf, phi);
+ }
+ }
+
+ while ((i = zend_bitset_pop_first(scdf->instr_worklist, scdf->instr_worklist_len)) >= 0) {
+ int block_num = ssa->cfg.map[i];
+ if (zend_bitset_in(scdf->executable_blocks, block_num)) {
+ zend_basic_block *block = &ssa->cfg.blocks[block_num];
+ zend_op *opline = &scdf->op_array->opcodes[i];
+ zend_ssa_op *ssa_op = &ssa->ops[i];
+ if (opline->opcode == ZEND_OP_DATA) {
+ opline--;
+ ssa_op--;
+ }
+ scdf->handlers.visit_instr(scdf, opline, ssa_op);
+ if (i == block->start + block->len - 1) {
+ if (block->successors_count == 1) {
+ scdf_mark_edge_feasible(scdf, block_num, block->successors[0]);
+ } else if (block->successors_count > 1) {
+ scdf->handlers.mark_feasible_successors(scdf, block_num, block, opline, ssa_op);
+ }
+ }
+ }
+ }
+
+ while ((i = zend_bitset_pop_first(scdf->block_worklist, scdf->block_worklist_len)) >= 0) {
+ /* This block is now live. Interpret phis and instructions in it. */
+ zend_basic_block *block = &ssa->cfg.blocks[i];
+ zend_ssa_block *ssa_block = &ssa->blocks[i];
+
+ DEBUG_PRINT("Pop block %d from worklist\n", i);
+ zend_bitset_incl(scdf->executable_blocks, i);
+
+ {
+ zend_ssa_phi *phi;
+ for (phi = ssa_block->phis; phi; phi = phi->next) {
+ zend_bitset_excl(scdf->phi_var_worklist, phi->ssa_var);
+ scdf->handlers.visit_phi(scdf, phi);
+ }
+ }
+
+ if (block->len == 0) {
+ /* Zero length blocks don't have a last instruction that would normally do this */
+ scdf_mark_edge_feasible(scdf, i, block->successors[0]);
+ } else {
+ zend_op *opline;
+ int j, end = block->start + block->len;
+ for (j = block->start; j < end; j++) {
+ opline = &scdf->op_array->opcodes[j];
+ zend_bitset_excl(scdf->instr_worklist, j);
+ if (opline->opcode != ZEND_OP_DATA) {
+ scdf->handlers.visit_instr(scdf, opline, &ssa->ops[j]);
+ }
+ }
+ if (block->successors_count == 1) {
+ scdf_mark_edge_feasible(scdf, i, block->successors[0]);
+ } else if (block->successors_count > 1) {
+ if (opline->opcode == ZEND_OP_DATA) {
+ opline--;
+ j--;
+ }
+ scdf->handlers.mark_feasible_successors(scdf, i, block, opline, &ssa->ops[j-1]);
+ }
+ }
+ }
+ }
+}
+
+/* If a live range starts in a reachable block and ends in an unreachable block, we should
+ * not eliminate the latter. While it cannot be reached, the FREE opcode of the loop var
+ * is necessary for the correctness of temporary compaction. */
+static zend_bool kept_alive_by_live_range(scdf_ctx *scdf, uint32_t block) {
+ uint32_t i;
+ const zend_op_array *op_array = scdf->op_array;
+ const zend_cfg *cfg = &scdf->ssa->cfg;
+ for (i = 0; i < op_array->last_live_range; i++) {
+ zend_live_range *live_range = &op_array->live_range[i];
+ uint32_t start_block = cfg->map[live_range->start];
+ uint32_t end_block = cfg->map[live_range->end];
+
+ if (end_block == block && start_block != block
+ && zend_bitset_in(scdf->executable_blocks, start_block)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* Removes unreachable blocks. This will remove both the instructions (and phis) in the
+ * blocks, as well as remove them from the successor / predecessor lists and mark them
+ * unreachable. Blocks already marked unreachable are not removed. */
+int scdf_remove_unreachable_blocks(scdf_ctx *scdf) {
+ zend_ssa *ssa = scdf->ssa;
+ int i;
+ int removed_ops = 0;
+
+ for (i = 0; i < ssa->cfg.blocks_count; i++) {
+ if (!zend_bitset_in(scdf->executable_blocks, i)
+ && (ssa->cfg.blocks[i].flags & ZEND_BB_REACHABLE)
+ && !kept_alive_by_live_range(scdf, i)) {
+ removed_ops += ssa->cfg.blocks[i].len;
+ zend_ssa_remove_block(scdf->op_array, ssa, i);
+ }
+ }
+ return removed_ops;
+}
diff --git a/ext/opcache/Optimizer/scdf.h b/ext/opcache/Optimizer/scdf.h
new file mode 100644
index 0000000000..6291534286
--- /dev/null
+++ b/ext/opcache/Optimizer/scdf.h
@@ -0,0 +1,99 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, Call Graph |
+ +----------------------------------------------------------------------+
+ | 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> |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef _SCDF_H
+#define _SCDF_H
+
+#include "zend_bitset.h"
+
+typedef struct _scdf_ctx {
+ zend_op_array *op_array;
+ zend_ssa *ssa;
+ zend_bitset instr_worklist;
+ /* Represent phi-instructions through the defining var */
+ zend_bitset phi_var_worklist;
+ zend_bitset block_worklist;
+ zend_bitset executable_blocks;
+ /* 1 bit per edge, see scdf_edge(cfg, from, to) */
+ zend_bitset feasible_edges;
+ uint32_t instr_worklist_len;
+ uint32_t phi_var_worklist_len;
+ uint32_t block_worklist_len;
+
+ struct {
+ void (*visit_instr)(
+ struct _scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_op);
+ void (*visit_phi)(
+ struct _scdf_ctx *scdf, zend_ssa_phi *phi);
+ void (*mark_feasible_successors)(
+ struct _scdf_ctx *scdf, int block_num, zend_basic_block *block,
+ zend_op *opline, zend_ssa_op *ssa_op);
+ } handlers;
+} scdf_ctx;
+
+void scdf_init(zend_optimizer_ctx *ctx, scdf_ctx *scdf, zend_op_array *op_array, zend_ssa *ssa);
+void scdf_solve(scdf_ctx *scdf, const char *name);
+
+int scdf_remove_unreachable_blocks(scdf_ctx *scdf);
+
+/* Add uses to worklist */
+static inline void scdf_add_to_worklist(scdf_ctx *scdf, int var_num) {
+ zend_ssa *ssa = scdf->ssa;
+ zend_ssa_var *var = &ssa->vars[var_num];
+ int use;
+ zend_ssa_phi *phi;
+ FOREACH_USE(var, use) {
+ zend_bitset_incl(scdf->instr_worklist, use);
+ } FOREACH_USE_END();
+ FOREACH_PHI_USE(var, phi) {
+ zend_bitset_incl(scdf->phi_var_worklist, phi->ssa_var);
+ } FOREACH_PHI_USE_END();
+}
+
+/* This should usually not be necessary, however it's used for type narrowing. */
+static inline void scdf_add_def_to_worklist(scdf_ctx *scdf, int var_num) {
+ zend_ssa_var *var = &scdf->ssa->vars[var_num];
+ if (var->definition >= 0) {
+ zend_bitset_incl(scdf->instr_worklist, var->definition);
+ } else if (var->definition_phi) {
+ zend_bitset_incl(scdf->phi_var_worklist, var_num);
+ }
+}
+
+static inline uint32_t scdf_edge(zend_cfg *cfg, int from, int to) {
+ zend_basic_block *to_block = cfg->blocks + to;
+ int i;
+
+ for (i = 0; i < to_block->predecessors_count; i++) {
+ uint32_t edge = to_block->predecessor_offset + i;
+
+ if (cfg->predecessors[edge] == from) {
+ return edge;
+ }
+ }
+ ZEND_ASSERT(0);
+}
+
+static inline zend_bool scdf_is_edge_feasible(scdf_ctx *scdf, int from, int to) {
+ uint32_t edge = scdf_edge(&scdf->ssa->cfg, from, to);
+ return zend_bitset_in(scdf->feasible_edges, edge);
+}
+
+void scdf_mark_edge_feasible(scdf_ctx *scdf, int from, int to);
+
+#endif
diff --git a/ext/opcache/Optimizer/ssa_integrity.c b/ext/opcache/Optimizer/ssa_integrity.c
new file mode 100644
index 0000000000..ae3495b903
--- /dev/null
+++ b/ext/opcache/Optimizer/ssa_integrity.c
@@ -0,0 +1,369 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, SSA validation |
+ +----------------------------------------------------------------------+
+ | 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"
+
+/* The ssa_verify_integrity() function ensures that that certain invariants of the SSA form and
+ * CFG are upheld and prints messages to stderr if this is not the case. */
+
+static inline zend_bool is_in_use_chain(zend_ssa *ssa, int var, int check) {
+ int use;
+ FOREACH_USE(&ssa->vars[var], use) {
+ if (use == check) {
+ return 1;
+ }
+ } FOREACH_USE_END();
+ return 0;
+}
+
+static inline zend_bool is_in_phi_use_chain(zend_ssa *ssa, int var, zend_ssa_phi *check) {
+ zend_ssa_phi *phi;
+ FOREACH_PHI_USE(&ssa->vars[var], phi) {
+ if (phi == check) {
+ return 1;
+ }
+ } FOREACH_PHI_USE_END();
+ return 0;
+}
+
+static inline zend_bool is_used_by_op(zend_ssa *ssa, int op, int check) {
+ zend_ssa_op *ssa_op = &ssa->ops[op];
+ return (ssa_op->op1_use == check)
+ || (ssa_op->op2_use == check)
+ || (ssa_op->result_use == check);
+}
+
+static inline zend_bool is_defined_by_op(zend_ssa *ssa, int op, int check) {
+ zend_ssa_op *ssa_op = &ssa->ops[op];
+ return (ssa_op->op1_def == check)
+ || (ssa_op->op2_def == check)
+ || (ssa_op->result_def == check);
+}
+
+static inline zend_bool is_in_phi_sources(zend_ssa *ssa, zend_ssa_phi *phi, int check) {
+ int source;
+ FOREACH_PHI_SOURCE(phi, source) {
+ if (source == check) {
+ return 1;
+ }
+ } FOREACH_PHI_SOURCE_END();
+ return 0;
+}
+
+static inline zend_bool is_in_predecessors(zend_cfg *cfg, zend_basic_block *block, int check) {
+ int i, *predecessors = &cfg->predecessors[block->predecessor_offset];
+ for (i = 0; i < block->predecessors_count; i++) {
+ if (predecessors[i] == check) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static inline zend_bool is_in_successors(zend_basic_block *block, int check) {
+ int s;
+ for (s = 0; s < block->successors_count; s++) {
+ if (block->successors[s] == check) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static inline zend_bool is_var_type(zend_uchar type) {
+ return type == IS_CV || type == IS_VAR || type == IS_TMP_VAR;
+}
+
+#define FAIL(...) do { \
+ if (status == SUCCESS) { \
+ fprintf(stderr, "\nIn function %s::%s (%s):\n", \
+ op_array->scope ? ZSTR_VAL(op_array->scope->name) : "", \
+ op_array->function_name ? ZSTR_VAL(op_array->function_name) : "{main}", extra); \
+ } \
+ fprintf(stderr, __VA_ARGS__); \
+ status = FAILURE; \
+} while (0)
+
+#define VARFMT "%d (%s%s)"
+#define VAR(i) \
+ (i), (ssa->vars[i].var < op_array->last_var ? "CV $" : "TMP"), \
+ (ssa->vars[i].var < op_array->last_var ? ZSTR_VAL(op_array->vars[ssa->vars[i].var]) : "")
+
+#define INSTRFMT "%d (%s)"
+#define INSTR(i) \
+ (i), (zend_get_opcode_name(op_array->opcodes[i].opcode))
+
+int ssa_verify_integrity(zend_op_array *op_array, zend_ssa *ssa, const char *extra) {
+ zend_cfg *cfg = &ssa->cfg;
+ zend_ssa_phi *phi;
+ int i, status = SUCCESS;
+
+ /* Vars */
+ for (i = 0; i < ssa->vars_count; i++) {
+ zend_ssa_var *var = &ssa->vars[i];
+ int use, c;
+ uint32_t type = ssa->var_info[i].type;
+
+ if (var->definition < 0 && !var->definition_phi && i > op_array->last_var) {
+ if (var->use_chain >= 0) {
+ FAIL("var " VARFMT " without def has op uses\n", VAR(i));
+ }
+ if (var->phi_use_chain) {
+ FAIL("var " VARFMT " without def has phi uses\n", VAR(i));
+ }
+ }
+ if (var->definition >= 0 && var->definition_phi) {
+ FAIL("var " VARFMT " has both def and def_phi\n", VAR(i));
+ }
+ if (var->definition >= 0) {
+ if (!is_defined_by_op(ssa, var->definition, i)) {
+ FAIL("var " VARFMT " not defined by op " INSTRFMT "\n",
+ VAR(i), INSTR(var->definition));
+ }
+ }
+ if (var->definition_phi) {
+ if (var->definition_phi->ssa_var != i) {
+ FAIL("var " VARFMT " not defined by given phi\n", VAR(i));
+ }
+ }
+
+ c = 0;
+ FOREACH_USE(var, use) {
+ if (++c > 10000) {
+ FAIL("cycle in uses of " VARFMT "\n", VAR(i));
+ return status;
+ }
+ if (!is_used_by_op(ssa, use, i)) {
+ fprintf(stderr, "var " VARFMT " not in uses of op %d\n", VAR(i), use);
+ }
+ } FOREACH_USE_END();
+
+ c = 0;
+ FOREACH_PHI_USE(var, phi) {
+ if (++c > 10000) {
+ FAIL("cycle in phi uses of " VARFMT "\n", VAR(i));
+ return status;
+ }
+ if (!is_in_phi_sources(ssa, phi, i)) {
+ FAIL("var " VARFMT " not in phi sources of %d\n", VAR(i), phi->ssa_var);
+ }
+ } FOREACH_PHI_USE_END();
+
+ if ((type & MAY_BE_ARRAY_KEY_ANY) && !(type & MAY_BE_ARRAY_OF_ANY)) {
+ FAIL("var " VARFMT " has array key type but not value type\n", VAR(i));
+ }
+ if ((type & MAY_BE_ARRAY_OF_ANY) && !(type & MAY_BE_ARRAY_KEY_ANY)) {
+ FAIL("var " VARFMT " has array value type but not key type\n", VAR(i));
+ }
+ }
+
+ /* Instructions */
+ FOREACH_INSTR_NUM(i) {
+ zend_ssa_op *ssa_op = &ssa->ops[i];
+ zend_op *opline = &op_array->opcodes[i];
+ if (is_var_type(opline->op1_type)) {
+ if (ssa_op->op1_use < 0 && ssa_op->op1_def < 0) {
+ FAIL("var op1 of " INSTRFMT " does not use/def an ssa var\n", INSTR(i));
+ }
+ } else {
+ if (ssa_op->op1_use >= 0 || ssa_op->op1_def >= 0) {
+ FAIL("non-var op1 of " INSTRFMT " uses or defs an ssa var\n", INSTR(i));
+ }
+ }
+ if (is_var_type(opline->op2_type)) {
+ if (ssa_op->op2_use < 0 && ssa_op->op2_def < 0) {
+ FAIL("var op2 of " INSTRFMT " does not use/def an ssa var\n", INSTR(i));
+ }
+ } else {
+ if (ssa_op->op2_use >= 0 || ssa_op->op2_def >= 0) {
+ FAIL("non-var op2 of " INSTRFMT " uses or defs an ssa var\n", INSTR(i));
+ }
+ }
+ if (is_var_type(opline->result_type)) {
+ if (ssa_op->result_use < 0 && ssa_op->result_def < 0) {
+ FAIL("var result of " INSTRFMT " does not use/def an ssa var\n", INSTR(i));
+ }
+ } else {
+ if (ssa_op->result_use >= 0 || ssa_op->result_def >= 0) {
+ FAIL("non-var result of " INSTRFMT " uses or defs an ssa var\n", INSTR(i));
+ }
+ }
+
+ if (ssa_op->op1_use >= 0) {
+ if (ssa_op->op1_use >= ssa->vars_count) {
+ FAIL("op1 use %d out of range\n", ssa_op->op1_use);
+ }
+ if (!is_in_use_chain(ssa, ssa_op->op1_use, i)) {
+ FAIL("op1 use of " VARFMT " in " INSTRFMT " not in use chain\n",
+ VAR(ssa_op->op1_use), INSTR(i));
+ }
+ if (VAR_NUM(opline->op1.var) != ssa->vars[ssa_op->op1_use].var) {
+ FAIL("op1 use of " VARFMT " does not match op %d of " INSTRFMT "\n",
+ VAR(ssa_op->op1_use), VAR_NUM(opline->op1.var), INSTR(i));
+ }
+ }
+ if (ssa_op->op2_use >= 0) {
+ if (ssa_op->op2_use >= ssa->vars_count) {
+ FAIL("op2 use %d out of range\n", ssa_op->op2_use);
+ }
+ if (!is_in_use_chain(ssa, ssa_op->op2_use, i)) {
+ FAIL("op1 use of " VARFMT " in " INSTRFMT " not in use chain\n",
+ VAR(ssa_op->op2_use), INSTR(i));
+ }
+ if (VAR_NUM(opline->op2.var) != ssa->vars[ssa_op->op2_use].var) {
+ FAIL("op2 use of " VARFMT " does not match op %d of " INSTRFMT "\n",
+ VAR(ssa_op->op2_use), VAR_NUM(opline->op2.var), INSTR(i));
+ }
+ }
+ if (ssa_op->result_use >= 0) {
+ if (ssa_op->result_use >= ssa->vars_count) {
+ FAIL("result use %d out of range\n", ssa_op->result_use);
+ }
+ if (!is_in_use_chain(ssa, ssa_op->result_use, i)) {
+ FAIL("result use of " VARFMT " in " INSTRFMT " not in use chain\n",
+ VAR(ssa_op->result_use), INSTR(i));
+ }
+ if (VAR_NUM(opline->result.var) != ssa->vars[ssa_op->result_use].var) {
+ FAIL("result use of " VARFMT " does not match op %d of " INSTRFMT "\n",
+ VAR(ssa_op->result_use), VAR_NUM(opline->result.var), INSTR(i));
+ }
+ }
+ if (ssa_op->op1_def >= 0) {
+ if (ssa_op->op1_def >= ssa->vars_count) {
+ FAIL("op1 def %d out of range\n", ssa_op->op1_def);
+ }
+ if (ssa->vars[ssa_op->op1_def].definition != i) {
+ FAIL("op1 def of " VARFMT " in " INSTRFMT " invalid\n",
+ VAR(ssa_op->op1_def), INSTR(i));
+ }
+ if (VAR_NUM(opline->op1.var) != ssa->vars[ssa_op->op1_def].var) {
+ FAIL("op1 def of " VARFMT " does not match op %d of " INSTRFMT "\n",
+ VAR(ssa_op->op1_def), VAR_NUM(opline->op1.var), INSTR(i));
+ }
+ }
+ if (ssa_op->op2_def >= 0) {
+ if (ssa_op->op2_def >= ssa->vars_count) {
+ FAIL("op2 def %d out of range\n", ssa_op->op2_def);
+ }
+ if (ssa->vars[ssa_op->op2_def].definition != i) {
+ FAIL("op2 def of " VARFMT " in " INSTRFMT " invalid\n",
+ VAR(ssa_op->op2_def), INSTR(i));
+ }
+ if (VAR_NUM(opline->op2.var) != ssa->vars[ssa_op->op2_def].var) {
+ FAIL("op2 def of " VARFMT " does not match op %d of " INSTRFMT "\n",
+ VAR(ssa_op->op2_def), VAR_NUM(opline->op2.var), INSTR(i));
+ }
+ }
+ if (ssa_op->result_def >= 0) {
+ if (ssa_op->result_def >= ssa->vars_count) {
+ FAIL("result def %d out of range\n", ssa_op->result_def);
+ }
+ if (ssa->vars[ssa_op->result_def].definition != i) {
+ FAIL("result def of " VARFMT " in " INSTRFMT " invalid\n",
+ VAR(ssa_op->result_def), INSTR(i));
+ }
+ if (VAR_NUM(opline->result.var) != ssa->vars[ssa_op->result_def].var) {
+ FAIL("result def of " VARFMT " does not match op %d of " INSTRFMT "\n",
+ VAR(ssa_op->result_def), VAR_NUM(opline->result.var), INSTR(i));
+ }
+ }
+ } FOREACH_INSTR_NUM_END();
+
+ /* Phis */
+ FOREACH_PHI(phi) {
+ int source;
+ FOREACH_PHI_SOURCE(phi, source) {
+ if (source < 0) {
+ FAIL(VARFMT " negative source\n", VAR(phi->ssa_var));
+ }
+ if (!is_in_phi_use_chain(ssa, source, phi)) {
+ FAIL(VARFMT " not in phi use chain of %d\n", VAR(phi->ssa_var), source);
+ }
+ if (ssa->vars[source].var != ssa->vars[phi->ssa_var].var) {
+ FAIL(VARFMT " source of phi for " VARFMT "\n", VAR(source), VAR(phi->ssa_var));
+ }
+ } FOREACH_PHI_SOURCE_END();
+ if (ssa->vars[phi->ssa_var].definition_phi != phi) {
+ FAIL(VARFMT " does not define this phi\n", VAR(phi->ssa_var));
+ }
+ } FOREACH_PHI_END();
+
+ /* Blocks */
+ for (i = 0; i < cfg->blocks_count; i++) {
+ zend_basic_block *block = &cfg->blocks[i];
+ int *predecessors = &cfg->predecessors[block->predecessor_offset];
+ int s, j;
+
+ if (i != 0 && block->start < (block-1)->start + (block-1)->len) {
+ FAIL("Block %d start %d smaller previous end %d\n",
+ i, block->start, (block-1)->start + (block-1)->len);
+ }
+ if (i != cfg->blocks_count-1 && block->start + block->len > (block+1)->start) {
+ FAIL("Block %d end %d greater next start %d\n",
+ i, block->start + block->len, (block+1)->start);
+ }
+
+ for (j = block->start; j < block->start + block->len; j++) {
+ if (cfg->map[j] != i) {
+ FAIL("Instr " INSTRFMT " not associated with block %d\n", INSTR(j), i);
+ }
+ }
+
+ if (!(block->flags & ZEND_BB_REACHABLE)) {
+ if (ssa->blocks[i].phis) {
+ FAIL("Unreachable block %d has phis\n", i);
+ }
+ continue;
+ }
+
+ for (s = 0; s < block->successors_count; s++) {
+ zend_basic_block *next_block;
+ if (block->successors[s] < 0) {
+ FAIL("Successor number %d of %d negative", s, i);
+ }
+ next_block = &cfg->blocks[block->successors[s]];
+ if (!(next_block->flags & ZEND_BB_REACHABLE)) {
+ FAIL("Successor %d of %d not reachable\n", block->successors[s], i);
+ }
+ if (!is_in_predecessors(cfg, next_block, i)) {
+ FAIL("Block %d predecessors missing %d\n", block->successors[s], i);
+ }
+ }
+
+ for (j = 0; j < block->predecessors_count; j++) {
+ if (predecessors[j] >= 0) {
+ int k;
+ zend_basic_block *prev_block = &cfg->blocks[predecessors[j]];
+ if (!(prev_block->flags & ZEND_BB_REACHABLE)) {
+ FAIL("Predecessor %d of %d not reachable\n", predecessors[j], i);
+ }
+ if (!is_in_successors(prev_block, i)) {
+ FAIL("Block %d successors missing %d\n", predecessors[j], i);
+ }
+ for (k = 0; k < block->predecessors_count; k++) {
+ if (k != j && predecessors[k] == predecessors[j]) {
+ FAIL("Block %d has duplicate predecessor %d\n", i, predecessors[j]);
+ }
+ }
+ }
+ }
+ }
+
+ return status;
+}
diff --git a/ext/opcache/Optimizer/zend_cfg.c b/ext/opcache/Optimizer/zend_cfg.c
index 6ae5880c8f..9a7529aeee 100644
--- a/ext/opcache/Optimizer/zend_cfg.c
+++ b/ext/opcache/Optimizer/zend_cfg.c
@@ -441,6 +441,9 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b
flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS;
}
break;
+ case ZEND_FUNC_GET_ARGS:
+ flags |= ZEND_FUNC_VARARG;
+ break;
}
}
@@ -598,7 +601,8 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b
/* Build CFG, Step 4, Mark Reachable Basic Blocks */
zend_mark_reachable_blocks(op_array, cfg, 0);
- cfg->dynamic = (flags & ZEND_FUNC_INDIRECT_VAR_ACCESS);
+ cfg->dynamic = (flags & ZEND_FUNC_INDIRECT_VAR_ACCESS) != 0;
+ cfg->vararg = (flags & ZEND_FUNC_VARARG) != 0;
if (func_flags) {
*func_flags |= flags;
@@ -646,9 +650,13 @@ int zend_cfg_build_predecessors(zend_arena **arena, zend_cfg *cfg) /* {{{ */
for (j = 0; j < cfg->blocks_count; j++) {
if (blocks[j].flags & ZEND_BB_REACHABLE) {
for (s = 0; s < blocks[j].successors_count; s++) {
- zend_basic_block *b = blocks + blocks[j].successors[s];
- predecessors[b->predecessor_offset + b->predecessors_count] = j;
- b->predecessors_count++;
+ /* SWITCH_STRING/LONG may have few following identical successors */
+ if (s == 0 || blocks[j].successors[s-1] != blocks[j].successors[s]) {
+ zend_basic_block *b = blocks + blocks[j].successors[s];
+
+ predecessors[b->predecessor_offset + b->predecessors_count] = j;
+ b->predecessors_count++;
+ }
}
}
}
diff --git a/ext/opcache/Optimizer/zend_cfg.h b/ext/opcache/Optimizer/zend_cfg.h
index ccff7d4425..b5b5683f12 100644
--- a/ext/opcache/Optimizer/zend_cfg.h
+++ b/ext/opcache/Optimizer/zend_cfg.h
@@ -93,6 +93,7 @@ typedef struct _zend_cfg {
unsigned int split_at_calls : 1;
unsigned int split_at_recv : 1;
unsigned int dynamic : 1; /* accesses varables by name */
+ unsigned int vararg : 1; /* uses func_get_args() */
} zend_cfg;
/* Build Flags */
diff --git a/ext/opcache/Optimizer/zend_inference.c b/ext/opcache/Optimizer/zend_inference.c
index 5234bc8ef6..1e52474897 100644
--- a/ext/opcache/Optimizer/zend_inference.c
+++ b/ext/opcache/Optimizer/zend_inference.c
@@ -282,7 +282,8 @@ int zend_ssa_find_false_dependencies(const zend_op_array *op_array, zend_ssa *ss
}
} else {
for (j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) {
- if (p->sources[j] >= 0 && ssa->vars[p->sources[j]].no_val) {
+ ZEND_ASSERT(p->sources[j] >= 0);
+ if (ssa->vars[p->sources[j]].no_val) {
ssa_vars[p->sources[j]].no_val = 0; /* used indirectly */
zend_bitset_incl(worklist, p->sources[j]);
}
@@ -854,7 +855,8 @@ int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int
}
} else {
for (i = 0; i < ssa->cfg.blocks[p->block].predecessors_count; i++) {
- if (p->sources[i] >= 0 && ssa->var_info[p->sources[i]].has_range) {
+ ZEND_ASSERT(p->sources[i] >= 0);
+ if (ssa->var_info[p->sources[i]].has_range) {
/* union */
tmp->underflow |= ssa->var_info[p->sources[i]].range.underflow;
tmp->min = MIN(tmp->min, ssa->var_info[p->sources[i]].range.min);
@@ -3328,17 +3330,18 @@ int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script
}
UPDATE_SSA_TYPE(tmp, j);
for (i = 0; i < blocks[p->block].predecessors_count; i++) {
- if (p->sources[i] >= 0) {
- zend_ssa_var_info *info = &ssa_var_info[p->sources[i]];
- if (info->type & MAY_BE_OBJECT) {
- if (first) {
- ce = info->ce;
- is_instanceof = info->is_instanceof;
- first = 0;
- } else {
- is_instanceof |= info->is_instanceof;
- ce = join_class_entries(ce, info->ce, &is_instanceof);
- }
+ zend_ssa_var_info *info;
+
+ ZEND_ASSERT(p->sources[i] >= 0);
+ info = &ssa_var_info[p->sources[i]];
+ if (info->type & MAY_BE_OBJECT) {
+ if (first) {
+ ce = info->ce;
+ is_instanceof = info->is_instanceof;
+ first = 0;
+ } else {
+ is_instanceof |= info->is_instanceof;
+ ce = join_class_entries(ce, info->ce, &is_instanceof);
}
}
}
@@ -3924,6 +3927,302 @@ 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;
+ }
+ return 1;
+ 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:
+ case ZEND_ISSET_ISEMPTY_VAR:
+ 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|MAY_BE_RESOURCE));
+ 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|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT)) || (t2 & (MAY_BE_OBJECT|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_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:
+ case ZEND_UNSET_VAR:
+ 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_COUNT:
+ return (t1 & MAY_BE_ANY) != MAY_BE_ARRAY;
+ 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_FETCH_IS:
+ return (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
+ case ZEND_ISSET_ISEMPTY_DIM_OBJ:
+ return (t1 & MAY_BE_OBJECT) || (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
+ case ZEND_FETCH_DIM_IS:
+ return (t1 & MAY_BE_OBJECT) || (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ 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_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
diff --git a/ext/opcache/Optimizer/zend_inference.h b/ext/opcache/Optimizer/zend_inference.h
index fecb124142..c10946a638 100644
--- a/ext/opcache/Optimizer/zend_inference.h
+++ b/ext/opcache/Optimizer/zend_inference.h
@@ -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 */
diff --git a/ext/opcache/Optimizer/zend_optimizer.c b/ext/opcache/Optimizer/zend_optimizer.c
index 89f5de800d..afd1c15597 100644
--- a/ext/opcache/Optimizer/zend_optimizer.c
+++ b/ext/opcache/Optimizer/zend_optimizer.c
@@ -53,6 +53,25 @@ void zend_optimizer_collect_constant(zend_optimizer_ctx *ctx, zval *name, zval*
zend_hash_add(ctx->constants, Z_STR_P(name), &val);
}
+zend_uchar zend_compound_assign_to_binary_op(zend_uchar opcode)
+{
+ switch (opcode) {
+ case ZEND_ASSIGN_ADD: return ZEND_ADD;
+ case ZEND_ASSIGN_SUB: return ZEND_SUB;
+ case ZEND_ASSIGN_MUL: return ZEND_MUL;
+ case ZEND_ASSIGN_DIV: return ZEND_DIV;
+ case ZEND_ASSIGN_MOD: return ZEND_MOD;
+ case ZEND_ASSIGN_SL: return ZEND_SL;
+ case ZEND_ASSIGN_SR: return ZEND_SR;
+ case ZEND_ASSIGN_CONCAT: return ZEND_CONCAT;
+ case ZEND_ASSIGN_BW_OR: return ZEND_BW_OR;
+ case ZEND_ASSIGN_BW_AND: return ZEND_BW_AND;
+ case ZEND_ASSIGN_BW_XOR: return ZEND_BW_XOR;
+ case ZEND_ASSIGN_POW: return ZEND_POW;
+ EMPTY_SWITCH_DEFAULT_CASE()
+ }
+}
+
int zend_optimizer_eval_binary_op(zval *result, zend_uchar opcode, zval *op1, zval *op2) /* {{{ */
{
binary_op_type binary_op = get_binary_op(opcode);
@@ -237,11 +256,53 @@ int zend_optimizer_update_op1_const(zend_op_array *op_array,
zend_op *opline,
zval *val)
{
+ zend_op *target_opline;
+
switch (opline->opcode) {
+ case ZEND_JMPZ:
+ if (zend_is_true(val)) {
+ MAKE_NOP(opline);
+ } else {
+ opline->opcode = ZEND_JMP;
+ COPY_NODE(opline->op1, opline->op2);
+ opline->op2_type = IS_UNUSED;
+ }
+ zval_ptr_dtor_nogc(val);
+ return 1;
+ case ZEND_JMPNZ:
+ if (zend_is_true(val)) {
+ opline->opcode = ZEND_JMP;
+ COPY_NODE(opline->op1, opline->op2);
+ opline->op2_type = IS_UNUSED;
+ } else {
+ MAKE_NOP(opline);
+ }
+ zval_ptr_dtor_nogc(val);
+ return 1;
+ case ZEND_JMPZNZ:
+ if (zend_is_true(val)) {
+ target_opline = ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value);
+ } else {
+ target_opline = ZEND_OP2_JMP_ADDR(opline);
+ }
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target_opline);
+ opline->op1_type = IS_UNUSED;
+ opline->extended_value = 0;
+ opline->opcode = ZEND_JMP;
+ zval_ptr_dtor_nogc(val);
+ return 1;
case ZEND_FREE:
MAKE_NOP(opline);
zval_ptr_dtor_nogc(val);
return 1;
+ case ZEND_SEND_VAR_EX:
+ case ZEND_FETCH_DIM_W:
+ case ZEND_FETCH_DIM_RW:
+ case ZEND_FETCH_DIM_FUNC_ARG:
+ case ZEND_FETCH_DIM_UNSET:
+ case ZEND_ASSIGN_DIM:
+ case ZEND_RETURN_BY_REF:
+ return 0;
case ZEND_INIT_STATIC_METHOD_CALL:
case ZEND_CATCH:
case ZEND_FETCH_CONSTANT:
@@ -305,6 +366,8 @@ int zend_optimizer_update_op2_const(zend_op_array *op_array,
zend_op *opline,
zval *val)
{
+ zval tmp;
+
switch (opline->opcode) {
case ZEND_ASSIGN_REF:
case ZEND_FAST_CALL:
@@ -331,7 +394,13 @@ int zend_optimizer_update_op2_const(zend_op_array *op_array,
break;
case ZEND_INIT_FCALL:
REQUIRES_STRING(val);
- zend_str_tolower(Z_STRVAL_P(val), Z_STRLEN_P(val));
+ if (Z_REFCOUNT_P(val) == 1) {
+ zend_str_tolower(Z_STRVAL_P(val), Z_STRLEN_P(val));
+ } else {
+ ZVAL_STR(&tmp, zend_string_tolower(Z_STR_P(val)));
+ zval_ptr_dtor_nogc(val);
+ val = &tmp;
+ }
opline->op2.constant = zend_optimizer_add_literal(op_array, val);
alloc_cache_slots_op2(op_array, opline, 1);
break;
@@ -482,6 +551,23 @@ void zend_optimizer_remove_live_range(zend_op_array *op_array, uint32_t var)
}
}
+void zend_optimizer_remove_live_range_ex(zend_op_array *op_array, uint32_t var, uint32_t start)
+{
+ uint32_t i = 0;
+
+ while (i < op_array->last_live_range) {
+ if (op_array->live_range[i].var == var
+ && op_array->live_range[i].start == start) {
+ op_array->last_live_range--;
+ if (i < op_array->last_live_range) {
+ memmove(&op_array->live_range[i], &op_array->live_range[i+1], (op_array->last_live_range - i) * sizeof(zend_live_range));
+ }
+ break;
+ }
+ i++;
+ }
+}
+
int zend_optimizer_replace_by_const(zend_op_array *op_array,
zend_op *opline,
zend_uchar type,
@@ -973,13 +1059,24 @@ static void zend_optimize(zend_op_array *op_array,
/* pass 11:
* - Compact literals table
*/
- if (ZEND_OPTIMIZER_PASS_11 & ctx->optimization_level) {
+ if ((ZEND_OPTIMIZER_PASS_11 & ctx->optimization_level) &&
+ (!(ZEND_OPTIMIZER_PASS_6 & ctx->optimization_level) ||
+ !(ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level))) {
zend_optimizer_compact_literals(op_array, ctx);
if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_11) {
zend_dump_op_array(op_array, 0, "after pass 11", NULL);
}
}
+ 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;
}
@@ -1176,7 +1273,7 @@ int zend_optimize_script(zend_script *script, zend_long optimization_level, zend
for (i = 0; i < call_graph.op_arrays_count; i++) {
func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]);
if (func_info) {
- zend_dfa_optimize_op_array(call_graph.op_arrays[i], &ctx, &func_info->ssa);
+ zend_dfa_optimize_op_array(call_graph.op_arrays[i], &ctx, &func_info->ssa, func_info->call_map);
}
}
@@ -1186,6 +1283,24 @@ int zend_optimize_script(zend_script *script, zend_long optimization_level, zend
}
}
+ if (ZEND_OPTIMIZER_PASS_11 & optimization_level) {
+ for (i = 0; i < call_graph.op_arrays_count; i++) {
+ zend_optimizer_compact_literals(call_graph.op_arrays[i], &ctx);
+ if (debug_level & ZEND_DUMP_AFTER_PASS_11) {
+ zend_dump_op_array(call_graph.op_arrays[i], 0, "after pass 11", NULL);
+ }
+ }
+ }
+
+ 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]);
diff --git a/ext/opcache/Optimizer/zend_optimizer.h b/ext/opcache/Optimizer/zend_optimizer.h
index 69c89d7234..93354448ef 100644
--- a/ext/opcache/Optimizer/zend_optimizer.h
+++ b/ext/opcache/Optimizer/zend_optimizer.h
@@ -32,13 +32,13 @@
#define ZEND_OPTIMIZER_PASS_5 (1<<4) /* CFG based optimization */
#define ZEND_OPTIMIZER_PASS_6 (1<<5) /* DFA based optimization */
#define ZEND_OPTIMIZER_PASS_7 (1<<6) /* CALL GRAPH optimization */
-#define ZEND_OPTIMIZER_PASS_8 (1<<7)
+#define ZEND_OPTIMIZER_PASS_8 (1<<7) /* SCCP (constant propagation) */
#define ZEND_OPTIMIZER_PASS_9 (1<<8) /* TMP VAR usage */
#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 */
diff --git a/ext/opcache/Optimizer/zend_optimizer_internal.h b/ext/opcache/Optimizer/zend_optimizer_internal.h
index b5d4729100..aa37456b2b 100644
--- a/ext/opcache/Optimizer/zend_optimizer_internal.h
+++ b/ext/opcache/Optimizer/zend_optimizer_internal.h
@@ -23,6 +23,7 @@
#define ZEND_OPTIMIZER_INTERNAL_H
#include "zend_ssa.h"
+#include "zend_func_info.h"
#define ZEND_OP1_LITERAL(opline) (op_array)->literals[(opline)->op1.constant]
#define ZEND_OP1_JMP_ADDR(opline) OP_JMP_ADDR(opline, (opline)->op1)
@@ -91,6 +92,7 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array,
zval *val);
void zend_optimizer_remove_live_range(zend_op_array *op_array, uint32_t var);
+void zend_optimizer_remove_live_range_ex(zend_op_array *op_array, uint32_t var, uint32_t start);
void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx);
void zend_optimizer_pass2(zend_op_array *op_array);
void zend_optimizer_pass3(zend_op_array *op_array);
@@ -98,15 +100,19 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx);
void zend_optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx);
void zend_optimize_dfa(zend_op_array *op_array, zend_optimizer_ctx *ctx);
int zend_dfa_analyze_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, uint32_t *flags);
-void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa);
+void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, zend_call_info **call_map);
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);
uint32_t zend_optimizer_classify_function(zend_string *name, uint32_t num_args);
void zend_optimizer_migrate_jump(zend_op_array *op_array, zend_op *new_opline, zend_op *opline);
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);
#endif
diff --git a/ext/opcache/Optimizer/zend_ssa.c b/ext/opcache/Optimizer/zend_ssa.c
index 927ebb096c..b0eb87995d 100644
--- a/ext/opcache/Optimizer/zend_ssa.c
+++ b/ext/opcache/Optimizer/zend_ssa.c
@@ -13,6 +13,7 @@
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov <dmitry@zend.com> |
+ | Nikita Popov <nikic@php.net> |
+----------------------------------------------------------------------+
*/
@@ -22,6 +23,7 @@
#include "zend_ssa.h"
#include "zend_dump.h"
#include "zend_inference.h"
+#include "Optimizer/zend_optimizer_internal.h"
static zend_bool dominates(const zend_basic_block *blocks, int a, int b) {
while (blocks[b].level > blocks[a].level) {
@@ -1059,15 +1061,16 @@ int zend_ssa_compute_use_def_chains(zend_arena **arena, const zend_op_array *op_
ssa_vars[phi->ssa_var].var = phi->var;
ssa_vars[phi->ssa_var].definition_phi = phi;
if (phi->pi >= 0) {
- if (phi->sources[0] >= 0) {
- zend_ssa_phi *p = ssa_vars[phi->sources[0]].phi_use_chain;
- while (p && p != phi) {
- p = zend_ssa_next_use_phi(ssa, phi->sources[0], p);
- }
- if (!p) {
- phi->use_chains[0] = ssa_vars[phi->sources[0]].phi_use_chain;
- ssa_vars[phi->sources[0]].phi_use_chain = phi;
- }
+ zend_ssa_phi *p;
+
+ ZEND_ASSERT(phi->sources[0] >= 0);
+ p = ssa_vars[phi->sources[0]].phi_use_chain;
+ while (p && p != phi) {
+ p = zend_ssa_next_use_phi(ssa, phi->sources[0], p);
+ }
+ if (!p) {
+ phi->use_chains[0] = ssa_vars[phi->sources[0]].phi_use_chain;
+ ssa_vars[phi->sources[0]].phi_use_chain = phi;
}
if (phi->has_range_constraint) {
/* min and max variables can't be used together */
@@ -1084,15 +1087,16 @@ int zend_ssa_compute_use_def_chains(zend_arena **arena, const zend_op_array *op_
int j;
for (j = 0; j < ssa->cfg.blocks[i].predecessors_count; j++) {
- if (phi->sources[j] >= 0) {
- zend_ssa_phi *p = ssa_vars[phi->sources[j]].phi_use_chain;
- while (p && p != phi) {
- p = zend_ssa_next_use_phi(ssa, phi->sources[j], p);
- }
- if (!p) {
- phi->use_chains[j] = ssa_vars[phi->sources[j]].phi_use_chain;
- ssa_vars[phi->sources[j]].phi_use_chain = phi;
- }
+ zend_ssa_phi *p;
+
+ ZEND_ASSERT(phi->sources[j] >= 0);
+ p = ssa_vars[phi->sources[j]].phi_use_chain;
+ while (p && p != phi) {
+ p = zend_ssa_next_use_phi(ssa, phi->sources[j], p);
+ }
+ if (!p) {
+ phi->use_chains[j] = ssa_vars[phi->sources[j]].phi_use_chain;
+ ssa_vars[phi->sources[j]].phi_use_chain = phi;
}
}
}
@@ -1161,6 +1165,420 @@ int zend_ssa_unlink_use_chain(zend_ssa *ssa, int op, int var) /* {{{ */
}
/* }}} */
+void zend_ssa_remove_instr(zend_ssa *ssa, zend_op *opline, zend_ssa_op *ssa_op) /* {{{ */
+{
+ if (ssa_op->result_use >= 0) {
+ zend_ssa_unlink_use_chain(ssa, ssa_op - ssa->ops, ssa_op->result_use);
+ ssa_op->result_use = -1;
+ ssa_op->res_use_chain = -1;
+ }
+ if (ssa_op->op1_use >= 0) {
+ if (ssa_op->op1_use != ssa_op->op2_use) {
+ zend_ssa_unlink_use_chain(ssa, ssa_op - ssa->ops, ssa_op->op1_use);
+ } else {
+ ssa_op->op2_use_chain = ssa_op->op1_use_chain;
+ }
+ ssa_op->op1_use = -1;
+ ssa_op->op1_use_chain = -1;
+ }
+ if (ssa_op->op2_use >= 0) {
+ zend_ssa_unlink_use_chain(ssa, ssa_op - ssa->ops, ssa_op->op2_use);
+ ssa_op->op2_use = -1;
+ ssa_op->op2_use_chain = -1;
+ }
+
+ /* We let the caller make sure that all defs are gone */
+ ZEND_ASSERT(ssa_op->result_def == -1);
+ ZEND_ASSERT(ssa_op->op1_def == -1);
+ ZEND_ASSERT(ssa_op->op2_def == -1);
+
+ MAKE_NOP(opline);
+}
+/* }}} */
+
+static inline zend_ssa_phi **zend_ssa_next_use_phi_ptr(zend_ssa *ssa, int var, zend_ssa_phi *p) /* {{{ */
+{
+ if (p->pi >= 0) {
+ return &p->use_chains[0];
+ } else {
+ int j;
+ for (j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) {
+ if (p->sources[j] == var) {
+ return &p->use_chains[j];
+ }
+ }
+ }
+ ZEND_ASSERT(0);
+ return NULL;
+}
+/* }}} */
+
+/* May be called even if source is not used in the phi (useful when removing uses in a phi
+ * with multiple identical operands) */
+static inline void zend_ssa_remove_use_of_phi_source(zend_ssa *ssa, zend_ssa_phi *phi, int source) /* {{{ */
+{
+ zend_ssa_phi **cur = &ssa->vars[source].phi_use_chain;
+ while (*cur && *cur != phi) {
+ cur = zend_ssa_next_use_phi_ptr(ssa, source, *cur);
+ }
+ if (*cur) {
+ *cur = zend_ssa_next_use_phi(ssa, source, *cur);
+ }
+}
+/* }}} */
+
+static void zend_ssa_remove_uses_of_phi_sources(zend_ssa *ssa, zend_ssa_phi *phi) /* {{{ */
+{
+ int source;
+ FOREACH_PHI_SOURCE(phi, source) {
+ zend_ssa_remove_use_of_phi_source(ssa, phi, source);
+ } FOREACH_PHI_SOURCE_END();
+}
+/* }}} */
+
+static void zend_ssa_remove_phi_from_block(zend_ssa *ssa, zend_ssa_phi *phi) /* {{{ */
+{
+ zend_ssa_block *block = &ssa->blocks[phi->block];
+ zend_ssa_phi **cur = &block->phis;
+ while (*cur != phi) {
+ ZEND_ASSERT(*cur != NULL);
+ cur = &(*cur)->next;
+ }
+ *cur = (*cur)->next;
+}
+/* }}} */
+
+static inline void zend_ssa_remove_defs_of_instr(zend_ssa *ssa, zend_ssa_op *ssa_op) /* {{{ */
+{
+ if (ssa_op->op1_def >= 0) {
+ zend_ssa_remove_uses_of_var(ssa, ssa_op->op1_def);
+ zend_ssa_remove_op1_def(ssa, ssa_op);
+ }
+ if (ssa_op->op2_def >= 0) {
+ zend_ssa_remove_uses_of_var(ssa, ssa_op->op2_def);
+ zend_ssa_remove_op2_def(ssa, ssa_op);
+ }
+ if (ssa_op->result_def >= 0) {
+ zend_ssa_remove_uses_of_var(ssa, ssa_op->result_def);
+ zend_ssa_remove_result_def(ssa, ssa_op);
+ }
+}
+/* }}} */
+
+static inline void zend_ssa_remove_phi_source(zend_ssa *ssa, zend_ssa_phi *phi, int pred_offset, int predecessors_count) /* {{{ */
+{
+ int j, var_num = phi->sources[pred_offset];
+
+ predecessors_count--;
+ if (pred_offset < predecessors_count) {
+ memmove(phi->sources + pred_offset, phi->sources + pred_offset + 1, (predecessors_count - pred_offset) * sizeof(uint32_t));
+ }
+
+ /* Check if they same var is used in a different phi operand as well, in this case we don't
+ * need to adjust the use chain (but may have to move the next pointer). */
+ for (j = 0; j < predecessors_count; j++) {
+ if (phi->sources[j] == var_num) {
+ if (j < pred_offset) {
+ ZEND_ASSERT(phi->use_chains[pred_offset] == NULL);
+ return;
+ }
+ if (j >= pred_offset) {
+ phi->use_chains[j] = phi->use_chains[pred_offset];
+ phi->use_chains[pred_offset] = NULL;
+ return;
+ }
+ }
+ }
+
+ /* Variable only used in one operand, remove the phi from the use chain. */
+ zend_ssa_remove_use_of_phi_source(ssa, phi, var_num);
+ phi->use_chains[pred_offset] = NULL;
+}
+/* }}} */
+
+void zend_ssa_remove_phi(zend_ssa *ssa, zend_ssa_phi *phi) /* {{{ */
+{
+ ZEND_ASSERT(phi->ssa_var >= 0);
+ ZEND_ASSERT(ssa->vars[phi->ssa_var].use_chain < 0
+ && ssa->vars[phi->ssa_var].phi_use_chain == NULL);
+ zend_ssa_remove_uses_of_phi_sources(ssa, phi);
+ zend_ssa_remove_phi_from_block(ssa, phi);
+ ssa->vars[phi->ssa_var].definition_phi = NULL;
+ phi->ssa_var = -1;
+}
+/* }}} */
+
+void zend_ssa_remove_uses_of_var(zend_ssa *ssa, int var_num) /* {{{ */
+{
+ zend_ssa_var *var = &ssa->vars[var_num];
+ zend_ssa_phi *phi;
+ int use;
+ FOREACH_PHI_USE(var, phi) {
+ int i, end = NUM_PHI_SOURCES(phi);
+ for (i = 0; i < end; i++) {
+ if (phi->sources[i] == var_num) {
+ phi->use_chains[i] = NULL;
+ }
+ }
+ } FOREACH_PHI_USE_END();
+ var->phi_use_chain = NULL;
+ FOREACH_USE(var, use) {
+ zend_ssa_op *ssa_op = &ssa->ops[use];
+ if (ssa_op->op1_use == var_num) {
+ ssa_op->op1_use = -1;
+ ssa_op->op1_use_chain = -1;
+ }
+ if (ssa_op->op2_use == var_num) {
+ ssa_op->op2_use = -1;
+ ssa_op->op2_use_chain = -1;
+ }
+ if (ssa_op->result_use == var_num) {
+ ssa_op->result_use = -1;
+ ssa_op->res_use_chain = -1;
+ }
+ } FOREACH_USE_END();
+ var->use_chain = -1;
+}
+/* }}} */
+
+void zend_ssa_remove_block(zend_op_array *op_array, zend_ssa *ssa, int i) /* {{{ */
+{
+ zend_basic_block *block = &ssa->cfg.blocks[i];
+ zend_ssa_block *ssa_block = &ssa->blocks[i];
+ int *predecessors;
+ zend_ssa_phi *phi;
+ int j, s;
+
+ block->flags &= ~ZEND_BB_REACHABLE;
+
+ /* Removes phis in this block */
+ for (phi = ssa_block->phis; phi; phi = phi->next) {
+ zend_ssa_remove_uses_of_var(ssa, phi->ssa_var);
+ zend_ssa_remove_phi(ssa, phi);
+ }
+
+ /* Remove instructions in this block */
+ for (j = block->start; j < block->start + block->len; j++) {
+ if (op_array->opcodes[j].opcode == ZEND_NOP) {
+ continue;
+ }
+
+ if (op_array->opcodes[j].result_type & (IS_TMP_VAR|IS_VAR)) {
+ zend_optimizer_remove_live_range_ex(op_array, op_array->opcodes[j].result.var, j + 1);
+ }
+ zend_ssa_remove_defs_of_instr(ssa, &ssa->ops[j]);
+ zend_ssa_remove_instr(ssa, &op_array->opcodes[j], &ssa->ops[j]);
+ }
+
+ for (s = 0; s < block->successors_count; s++) {
+ zend_basic_block *next_block = &ssa->cfg.blocks[block->successors[s]];
+ zend_ssa_block *next_ssa_block = &ssa->blocks[block->successors[s]];
+ zend_ssa_phi *phi;
+
+ /* Find at which predecessor offset this block is referenced */
+ int pred_offset = -1;
+ predecessors = &ssa->cfg.predecessors[next_block->predecessor_offset];
+ for (j = 0; j < next_block->predecessors_count; j++) {
+ if (predecessors[j] == i) {
+ pred_offset = j;
+ break;
+ }
+ }
+ ZEND_ASSERT(pred_offset != -1);
+
+ /* For phis in successor blocks, remove the operands associated with this block */
+ for (phi = next_ssa_block->phis; phi; phi = phi->next) {
+ if (phi->pi >= 0) {
+ if (phi->pi == i) {
+ zend_ssa_remove_uses_of_var(ssa, phi->ssa_var);
+ zend_ssa_remove_phi(ssa, phi);
+ }
+ } else {
+ ZEND_ASSERT(phi->sources[pred_offset] >= 0);
+ zend_ssa_remove_phi_source(ssa, phi, pred_offset, next_block->predecessors_count);
+ }
+ }
+
+ /* Remove this predecessor */
+ next_block->predecessors_count--;
+ if (pred_offset < next_block->predecessors_count) {
+ predecessors = &ssa->cfg.predecessors[next_block->predecessor_offset + pred_offset];
+ memmove(predecessors, predecessors + 1, (next_block->predecessors_count - pred_offset) * sizeof(uint32_t));
+ }
+ }
+
+ /* Remove successors of predecessors */
+ predecessors = &ssa->cfg.predecessors[block->predecessor_offset];
+ for (j = 0; j < block->predecessors_count; j++) {
+ if (predecessors[j] >= 0) {
+ zend_basic_block *prev_block = &ssa->cfg.blocks[predecessors[j]];
+
+ for (s = 0; s < prev_block->successors_count; s++) {
+ if (prev_block->successors[s] == i) {
+ memmove(prev_block->successors + s,
+ prev_block->successors + s + 1,
+ sizeof(int) * (prev_block->successors_count - s - 1));
+ prev_block->successors_count--;
+ s--;
+ }
+ }
+ }
+ }
+
+ block->successors_count = 0;
+ block->predecessors_count = 0;
+
+ /* Remove from dominators tree */
+ if (block->idom >= 0) {
+ j = ssa->cfg.blocks[block->idom].children;
+ if (j == i) {
+ ssa->cfg.blocks[block->idom].children = block->next_child;
+ } else if (j >= 0) {
+ while (ssa->cfg.blocks[j].next_child >= 0) {
+ if (ssa->cfg.blocks[j].next_child == i) {
+ ssa->cfg.blocks[j].next_child = block->next_child;
+ break;
+ }
+ j = ssa->cfg.blocks[j].next_child;
+ }
+ }
+ }
+ block->idom = -1;
+ block->level = -1;
+ block->children = -1;
+ block->next_child = -1;
+}
+/* }}} */
+
+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
diff --git a/ext/opcache/Optimizer/zend_ssa.h b/ext/opcache/Optimizer/zend_ssa.h
index b8e5d8c3e8..c7724fa032 100644
--- a/ext/opcache/Optimizer/zend_ssa.h
+++ b/ext/opcache/Optimizer/zend_ssa.h
@@ -139,6 +139,41 @@ int zend_build_ssa(zend_arena **arena, const zend_script *script, const zend_op_
int zend_ssa_compute_use_def_chains(zend_arena **arena, const zend_op_array *op_array, zend_ssa *ssa);
int zend_ssa_unlink_use_chain(zend_ssa *ssa, int op, int var);
+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)
+{
+ ZEND_ASSERT(var->definition >= 0);
+ ZEND_ASSERT(var->use_chain < 0);
+ ZEND_ASSERT(!var->phi_use_chain);
+ var->definition = -1;
+}
+
+static zend_always_inline void zend_ssa_remove_result_def(zend_ssa *ssa, zend_ssa_op *ssa_op)
+{
+ zend_ssa_var *var = &ssa->vars[ssa_op->result_def];
+ _zend_ssa_remove_def(var);
+ ssa_op->result_def = -1;
+}
+
+static zend_always_inline void zend_ssa_remove_op1_def(zend_ssa *ssa, zend_ssa_op *ssa_op)
+{
+ zend_ssa_var *var = &ssa->vars[ssa_op->op1_def];
+ _zend_ssa_remove_def(var);
+ ssa_op->op1_def = -1;
+}
+
+static zend_always_inline void zend_ssa_remove_op2_def(zend_ssa *ssa, zend_ssa_op *ssa_op)
+{
+ zend_ssa_var *var = &ssa->vars[ssa_op->op2_def];
+ _zend_ssa_remove_def(var);
+ ssa_op->op2_def = -1;
+}
+
END_EXTERN_C()
static zend_always_inline int zend_ssa_next_use(const zend_ssa_op *ssa_op, int var, int use)
@@ -180,6 +215,95 @@ 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))
+
+/* FOREACH_USE and FOREACH_PHI_USE explicitly support "continue"
+ * and changing the use chain of the current element */
+#define FOREACH_USE(var, use) do { \
+ int _var_num = (var) - ssa->vars, next; \
+ for (use = (var)->use_chain; use >= 0; use = next) { \
+ next = zend_ssa_next_use(ssa->ops, _var_num, use);
+#define FOREACH_USE_END() \
+ } \
+} while (0)
+
+#define FOREACH_PHI_USE(var, phi) do { \
+ int _var_num = (var) - ssa->vars; \
+ zend_ssa_phi *next_phi; \
+ for (phi = (var)->phi_use_chain; phi; phi = next_phi) { \
+ next_phi = zend_ssa_next_use_phi(ssa, _var_num, phi);
+#define FOREACH_PHI_USE_END() \
+ } \
+} while (0)
+
+#define FOREACH_PHI_SOURCE(phi, source) do { \
+ zend_ssa_phi *_phi = (phi); \
+ int _i, _end = NUM_PHI_SOURCES(phi); \
+ for (_i = 0; _i < _end; _i++) { \
+ ZEND_ASSERT(_phi->sources[_i] >= 0); \
+ source = _phi->sources[_i];
+#define FOREACH_PHI_SOURCE_END() \
+ } \
+} 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 */
/*
diff --git a/ext/opcache/config.m4 b/ext/opcache/config.m4
index ded7f3dab2..7b500f023d 100644
--- a/ext/opcache/config.m4
+++ b/ext/opcache/config.m4
@@ -410,6 +410,10 @@ fi
Optimizer/zend_inference.c \
Optimizer/zend_func_info.c \
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)
diff --git a/ext/opcache/config.w32 b/ext/opcache/config.w32
index 35c4645620..c2b757dd06 100644
--- a/ext/opcache/config.w32
+++ b/ext/opcache/config.w32
@@ -23,7 +23,7 @@ if (PHP_OPCACHE != "no") {
zend_shared_alloc.c \
shared_alloc_win32.c", true, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
- ADD_SOURCES(configure_module_dirname + "/Optimizer", "zend_optimizer.c pass1_5.c pass2.c pass3.c optimize_func_calls.c block_pass.c optimize_temp_vars_5.c nop_removal.c compact_literals.c zend_cfg.c zend_dfg.c dfa_pass.c zend_ssa.c zend_inference.c zend_func_info.c zend_call_graph.c zend_dump.c", "opcache", "OptimizerObj");
+ ADD_SOURCES(configure_module_dirname + "/Optimizer", "zend_optimizer.c pass1_5.c pass2.c pass3.c optimize_func_calls.c block_pass.c optimize_temp_vars_5.c nop_removal.c compact_literals.c zend_cfg.c zend_dfg.c dfa_pass.c zend_ssa.c zend_inference.c zend_func_info.c zend_call_graph.c sccp.c scdf.c dce.c compact_vars.c zend_dump.c", "opcache", "OptimizerObj");
ADD_FLAG('CFLAGS_OPCACHE', "/I " + configure_module_dirname);
diff --git a/ext/opcache/tests/ssa_bug_007.phpt b/ext/opcache/tests/ssa_bug_007.phpt
new file mode 100644
index 0000000000..84d9e34b31
--- /dev/null
+++ b/ext/opcache/tests/ssa_bug_007.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Incorrect CFG/SSA construction for SWITCH with few identical successors
+--FILE--
+<?php
+function render($properties) {
+ foreach ($properties as $key => $value) {
+ switch ($key) {
+ case 'Trapped':
+ if ($value == null) {
+ $docInfo->$key = 1;
+ }
+ case 'CreationDate':
+ case 'ModDate':
+ $docInfo->$key = 2;
+ break;
+ }
+ }
+}
+?>
+OK
+--EXPECT--
+OK
+