summaryrefslogtreecommitdiff
path: root/Zend/Optimizer
diff options
context:
space:
mode:
Diffstat (limited to 'Zend/Optimizer')
-rw-r--r--Zend/Optimizer/block_pass.c1937
-rw-r--r--Zend/Optimizer/compact_literals.c843
-rw-r--r--Zend/Optimizer/compact_vars.c123
-rw-r--r--Zend/Optimizer/dce.c628
-rw-r--r--Zend/Optimizer/dfa_pass.c1637
-rw-r--r--Zend/Optimizer/escape_analysis.c539
-rw-r--r--Zend/Optimizer/nop_removal.c106
-rw-r--r--Zend/Optimizer/optimize_func_calls.c337
-rw-r--r--Zend/Optimizer/optimize_temp_vars_5.c187
-rw-r--r--Zend/Optimizer/pass1.c685
-rw-r--r--Zend/Optimizer/pass3.c356
-rw-r--r--Zend/Optimizer/sccp.c2528
-rw-r--r--Zend/Optimizer/scdf.c229
-rw-r--r--Zend/Optimizer/scdf.h99
-rw-r--r--Zend/Optimizer/ssa_integrity.c378
-rw-r--r--Zend/Optimizer/zend_call_graph.c271
-rw-r--r--Zend/Optimizer/zend_call_graph.h69
-rw-r--r--Zend/Optimizer/zend_cfg.c909
-rw-r--r--Zend/Optimizer/zend_cfg.h127
-rw-r--r--Zend/Optimizer/zend_dfg.c333
-rw-r--r--Zend/Optimizer/zend_dfg.h51
-rw-r--r--Zend/Optimizer/zend_dump.c1239
-rw-r--r--Zend/Optimizer/zend_dump.h48
-rw-r--r--Zend/Optimizer/zend_func_info.c973
-rw-r--r--Zend/Optimizer/zend_func_info.h68
-rw-r--r--Zend/Optimizer/zend_inference.c4680
-rw-r--r--Zend/Optimizer/zend_inference.h288
-rw-r--r--Zend/Optimizer/zend_optimizer.c1568
-rw-r--r--Zend/Optimizer/zend_optimizer.h98
-rw-r--r--Zend/Optimizer/zend_optimizer_internal.h123
-rw-r--r--Zend/Optimizer/zend_ssa.c1628
-rw-r--r--Zend/Optimizer/zend_ssa.h326
-rw-r--r--Zend/Optimizer/zend_worklist.h121
33 files changed, 23532 insertions, 0 deletions
diff --git a/Zend/Optimizer/block_pass.c b/Zend/Optimizer/block_pass.c
new file mode 100644
index 0000000000..e833ff3dbf
--- /dev/null
+++ b/Zend/Optimizer/block_pass.c
@@ -0,0 +1,1937 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend OPcache |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Andi Gutmans <andi@php.net> |
+ | Zeev Suraski <zeev@php.net> |
+ | Stanislav Malyshev <stas@zend.com> |
+ | Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#include "php.h"
+#include "Optimizer/zend_optimizer.h"
+#include "Optimizer/zend_optimizer_internal.h"
+#include "zend_API.h"
+#include "zend_constants.h"
+#include "zend_execute.h"
+#include "zend_vm.h"
+#include "zend_bitset.h"
+#include "zend_cfg.h"
+#include "zend_dump.h"
+
+/* Checks if a constant (like "true") may be replaced by its value */
+int zend_optimizer_get_persistent_constant(zend_string *name, zval *result, int copy)
+{
+ zend_constant *c = zend_hash_find_ptr(EG(zend_constants), name);
+ if (c) {
+ if ((ZEND_CONSTANT_FLAGS(c) & CONST_PERSISTENT)
+ && !(ZEND_CONSTANT_FLAGS(c) & CONST_DEPRECATED)
+ && (!(ZEND_CONSTANT_FLAGS(c) & CONST_NO_FILE_CACHE)
+ || !(CG(compiler_options) & ZEND_COMPILE_WITH_FILE_CACHE))) {
+ ZVAL_COPY_VALUE(result, &c->value);
+ if (copy) {
+ Z_TRY_ADDREF_P(result);
+ }
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ /* Special constants null/true/false can always be substituted. */
+ c = zend_get_special_const(ZSTR_VAL(name), ZSTR_LEN(name));
+ if (c) {
+ ZVAL_COPY_VALUE(result, &c->value);
+ return 1;
+ }
+ return 0;
+}
+
+/* Data dependencies macros */
+
+#define VAR_SOURCE(op) Tsource[VAR_NUM(op.var)]
+#define SET_VAR_SOURCE(opline) Tsource[VAR_NUM(opline->result.var)] = opline
+
+static void strip_leading_nops(zend_op_array *op_array, zend_basic_block *b)
+{
+ zend_op *opcodes = op_array->opcodes;
+
+ do {
+ b->start++;
+ b->len--;
+ } while (b->len > 0 && opcodes[b->start].opcode == ZEND_NOP);
+}
+
+static void strip_nops(zend_op_array *op_array, zend_basic_block *b)
+{
+ uint32_t i, j;
+
+ if (b->len == 0) {
+ return;
+ }
+
+ if (op_array->opcodes[b->start].opcode == ZEND_NOP) {
+ strip_leading_nops(op_array, b);
+ }
+
+ if (b->len == 0) {
+ return;
+ }
+
+ /* strip the inside NOPs */
+ i = j = b->start + 1;
+ while (i < b->start + b->len) {
+ if (op_array->opcodes[i].opcode != ZEND_NOP) {
+ if (i != j) {
+ op_array->opcodes[j] = op_array->opcodes[i];
+ }
+ j++;
+ }
+ i++;
+ }
+ b->len = j - b->start;
+ while (j < i) {
+ MAKE_NOP(op_array->opcodes + j);
+ j++;
+ }
+}
+
+static int get_const_switch_target(zend_cfg *cfg, zend_op_array *op_array, zend_basic_block *block, zend_op *opline, zval *val) {
+ HashTable *jumptable = Z_ARRVAL(ZEND_OP2_LITERAL(opline));
+ zval *zv;
+ if ((opline->opcode == ZEND_SWITCH_LONG && Z_TYPE_P(val) != IS_LONG)
+ || (opline->opcode == ZEND_SWITCH_STRING && Z_TYPE_P(val) != IS_STRING)) {
+ /* fallback to next block */
+ return block->successors[block->successors_count - 1];
+ }
+ if (opline->opcode == ZEND_MATCH && Z_TYPE_P(val) != IS_LONG && Z_TYPE_P(val) != IS_STRING) {
+ /* always jump to the default arm */
+ return block->successors[block->successors_count - 1];
+ }
+ if (Z_TYPE_P(val) == IS_LONG) {
+ zv = zend_hash_index_find(jumptable, Z_LVAL_P(val));
+ } else {
+ ZEND_ASSERT(Z_TYPE_P(val) == IS_STRING);
+ zv = zend_hash_find(jumptable, Z_STR_P(val));
+ }
+ if (!zv) {
+ /* default */
+ return block->successors[block->successors_count - (opline->opcode == ZEND_MATCH ? 1 : 2)];
+ }
+ return cfg->map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv))];
+}
+
+static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array, zend_bitset used_ext, zend_cfg *cfg, zend_op **Tsource, uint32_t *opt_count)
+{
+ zend_op *opline, *src;
+ zend_op *end, *last_op = NULL;
+
+ if (block->len == 0) {
+ return;
+ }
+
+ if (op_array->opcodes[block->start].opcode == ZEND_NOP) {
+ /* remove leading NOPs */
+ strip_leading_nops(op_array, block);
+ }
+
+ opline = op_array->opcodes + block->start;
+ end = opline + block->len;
+ while (opline < end) {
+ /* Constant Propagation: strip X = QM_ASSIGN(const) */
+ if (opline->op1_type == IS_TMP_VAR &&
+ opline->opcode != ZEND_FREE) {
+ src = VAR_SOURCE(opline->op1);
+ if (src &&
+ src->opcode == ZEND_QM_ASSIGN &&
+ src->op1_type == IS_CONST
+ ) {
+ znode_op op1 = opline->op1;
+ if (opline->opcode == ZEND_VERIFY_RETURN_TYPE) {
+ COPY_NODE(opline->result, opline->op1);
+ COPY_NODE(opline->op1, src->op1);
+ VAR_SOURCE(op1) = NULL;
+ MAKE_NOP(src);
+ ++(*opt_count);
+ } else {
+ zval c;
+ ZVAL_COPY(&c, &ZEND_OP1_LITERAL(src));
+ if (zend_optimizer_update_op1_const(op_array, opline, &c)) {
+ VAR_SOURCE(op1) = NULL;
+ literal_dtor(&ZEND_OP1_LITERAL(src));
+ MAKE_NOP(src);
+ ++(*opt_count);
+ } else {
+ zval_ptr_dtor_nogc(&c);
+ }
+ }
+ }
+ }
+
+ /* Constant Propagation: strip X = QM_ASSIGN(const) */
+ if (opline->op2_type == IS_TMP_VAR) {
+ src = VAR_SOURCE(opline->op2);
+ if (src &&
+ src->opcode == ZEND_QM_ASSIGN &&
+ src->op1_type == IS_CONST) {
+
+ znode_op op2 = opline->op2;
+ zval c;
+
+ ZVAL_COPY(&c, &ZEND_OP1_LITERAL(src));
+ if (zend_optimizer_update_op2_const(op_array, opline, &c)) {
+ VAR_SOURCE(op2) = NULL;
+ literal_dtor(&ZEND_OP1_LITERAL(src));
+ MAKE_NOP(src);
+ ++(*opt_count);
+ } else {
+ zval_ptr_dtor_nogc(&c);
+ }
+ }
+ }
+
+ switch (opline->opcode) {
+ case ZEND_ECHO:
+ if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
+ src = VAR_SOURCE(opline->op1);
+ if (src &&
+ src->opcode == ZEND_CAST &&
+ src->extended_value == IS_STRING) {
+ /* T = CAST(X, String), ECHO(T) => NOP, ECHO(X) */
+ VAR_SOURCE(opline->op1) = NULL;
+ COPY_NODE(opline->op1, src->op1);
+ MAKE_NOP(src);
+ ++(*opt_count);
+ }
+ } else if (opline->op1_type == IS_CONST &&
+ Z_TYPE(ZEND_OP1_LITERAL(opline)) != IS_DOUBLE) {
+ if (last_op == opline - 1) {
+ /* compress consecutive ECHO's.
+ * Float to string conversion may be affected by current
+ * locale setting.
+ */
+ int l, old_len;
+
+ if (Z_TYPE(ZEND_OP1_LITERAL(opline)) != IS_STRING) {
+ convert_to_string(&ZEND_OP1_LITERAL(opline));
+ }
+ if (Z_TYPE(ZEND_OP1_LITERAL(last_op)) != IS_STRING) {
+ convert_to_string(&ZEND_OP1_LITERAL(last_op));
+ }
+ old_len = Z_STRLEN(ZEND_OP1_LITERAL(last_op));
+ l = old_len + Z_STRLEN(ZEND_OP1_LITERAL(opline));
+ if (!Z_REFCOUNTED(ZEND_OP1_LITERAL(last_op))) {
+ zend_string *tmp = zend_string_alloc(l, 0);
+ memcpy(ZSTR_VAL(tmp), Z_STRVAL(ZEND_OP1_LITERAL(last_op)), old_len);
+ Z_STR(ZEND_OP1_LITERAL(last_op)) = tmp;
+ } else {
+ Z_STR(ZEND_OP1_LITERAL(last_op)) = zend_string_extend(Z_STR(ZEND_OP1_LITERAL(last_op)), l, 0);
+ }
+ Z_TYPE_INFO(ZEND_OP1_LITERAL(last_op)) = IS_STRING_EX;
+ memcpy(Z_STRVAL(ZEND_OP1_LITERAL(last_op)) + old_len, Z_STRVAL(ZEND_OP1_LITERAL(opline)), Z_STRLEN(ZEND_OP1_LITERAL(opline)));
+ Z_STRVAL(ZEND_OP1_LITERAL(last_op))[l] = '\0';
+ zval_ptr_dtor_nogc(&ZEND_OP1_LITERAL(opline));
+ ZVAL_STR(&ZEND_OP1_LITERAL(opline), zend_new_interned_string(Z_STR(ZEND_OP1_LITERAL(last_op))));
+ ZVAL_NULL(&ZEND_OP1_LITERAL(last_op));
+ MAKE_NOP(last_op);
+ ++(*opt_count);
+ }
+ last_op = opline;
+ }
+ break;
+
+ case ZEND_FREE:
+ if (opline->op1_type == IS_TMP_VAR) {
+ src = VAR_SOURCE(opline->op1);
+ if (src) {
+ switch (src->opcode) {
+ case ZEND_BOOL:
+ case ZEND_BOOL_NOT:
+ /* T = BOOL(X), FREE(T) => T = BOOL(X) */
+ /* The remaining BOOL is removed by a separate optimization */
+ VAR_SOURCE(opline->op1) = NULL;
+ MAKE_NOP(opline);
+ ++(*opt_count);
+ break;
+ case ZEND_ASSIGN:
+ case ZEND_ASSIGN_DIM:
+ case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_STATIC_PROP:
+ case ZEND_ASSIGN_OP:
+ case ZEND_ASSIGN_DIM_OP:
+ case ZEND_ASSIGN_OBJ_OP:
+ case ZEND_ASSIGN_STATIC_PROP_OP:
+ case ZEND_PRE_INC:
+ case ZEND_PRE_DEC:
+ case ZEND_PRE_INC_OBJ:
+ case ZEND_PRE_DEC_OBJ:
+ case ZEND_PRE_INC_STATIC_PROP:
+ case ZEND_PRE_DEC_STATIC_PROP:
+ src->result_type = IS_UNUSED;
+ VAR_SOURCE(opline->op1) = NULL;
+ MAKE_NOP(opline);
+ ++(*opt_count);
+ break;
+ default:
+ break;
+ }
+ }
+ } else if (opline->op1_type == IS_VAR) {
+ src = VAR_SOURCE(opline->op1);
+ /* V = OP, FREE(V) => OP. NOP */
+ if (src &&
+ src->opcode != ZEND_FETCH_R &&
+ src->opcode != ZEND_FETCH_STATIC_PROP_R &&
+ src->opcode != ZEND_FETCH_DIM_R &&
+ src->opcode != ZEND_FETCH_OBJ_R &&
+ src->opcode != ZEND_NEW) {
+ src->result_type = IS_UNUSED;
+ MAKE_NOP(opline);
+ ++(*opt_count);
+ if (src->opcode == ZEND_QM_ASSIGN) {
+ if (src->op1_type & (IS_VAR|IS_TMP_VAR)) {
+ src->opcode = ZEND_FREE;
+ } else {
+ MAKE_NOP(src);
+ }
+ }
+ }
+ }
+ break;
+
+#if 0
+ /* pre-evaluate functions:
+ constant(x)
+ function_exists(x)
+ extension_loaded(x)
+ BAD: interacts badly with Accelerator
+ */
+ if((opline->op1_type & IS_VAR) &&
+ VAR_SOURCE(opline->op1) && VAR_SOURCE(opline->op1)->opcode == ZEND_DO_CF_FCALL &&
+ VAR_SOURCE(opline->op1)->extended_value == 1) {
+ zend_op *fcall = VAR_SOURCE(opline->op1);
+ zend_op *sv = fcall-1;
+ if(sv >= block->start_opline && sv->opcode == ZEND_SEND_VAL &&
+ sv->op1_type == IS_CONST && Z_TYPE(OPLINE_OP1_LITERAL(sv)) == IS_STRING &&
+ Z_LVAL(OPLINE_OP2_LITERAL(sv)) == 1
+ ) {
+ zval *arg = &OPLINE_OP1_LITERAL(sv);
+ char *fname = FUNCTION_CACHE->funcs[Z_LVAL(ZEND_OP1_LITERAL(fcall))].function_name;
+ int flen = FUNCTION_CACHE->funcs[Z_LVAL(ZEND_OP1_LITERAL(fcall))].name_len;
+ if((flen == sizeof("function_exists")-1 && zend_binary_strcasecmp(fname, flen, "function_exists", sizeof("function_exists")-1) == 0) ||
+ (flen == sizeof("is_callable")-1 && zend_binary_strcasecmp(fname, flen, "is_callable", sizeof("is_callable")-1) == 0)
+ ) {
+ zend_function *function;
+ if((function = zend_hash_find_ptr(EG(function_table), Z_STR_P(arg))) != NULL) {
+ literal_dtor(arg);
+ MAKE_NOP(sv);
+ MAKE_NOP(fcall);
+ LITERAL_BOOL(opline->op1, 1);
+ opline->op1_type = IS_CONST;
+ }
+ } else if(flen == sizeof("constant")-1 && zend_binary_strcasecmp(fname, flen, "constant", sizeof("constant")-1) == 0) {
+ zval c;
+ if(zend_optimizer_get_persistent_constant(Z_STR_P(arg), &c, 1 ELS_CC) != 0) {
+ literal_dtor(arg);
+ MAKE_NOP(sv);
+ MAKE_NOP(fcall);
+ ZEND_OP1_LITERAL(opline) = zend_optimizer_add_literal(op_array, &c);
+ /* no copy ctor - get already copied it */
+ opline->op1_type = IS_CONST;
+ }
+ } else if(flen == sizeof("extension_loaded")-1 && zend_binary_strcasecmp(fname, flen, "extension_loaded", sizeof("extension_loaded")-1) == 0) {
+ if(zend_hash_exists(&module_registry, Z_STR_P(arg))) {
+ literal_dtor(arg);
+ MAKE_NOP(sv);
+ MAKE_NOP(fcall);
+ LITERAL_BOOL(opline->op1, 1);
+ opline->op1_type = IS_CONST;
+ }
+ }
+ }
+ }
+#endif
+
+ case ZEND_FETCH_LIST_R:
+ case ZEND_FETCH_LIST_W:
+ if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
+ /* LIST variable will be deleted later by FREE */
+ Tsource[VAR_NUM(opline->op1.var)] = NULL;
+ }
+ break;
+
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ case ZEND_MATCH:
+ if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
+ /* SWITCH variable will be deleted later by FREE, so we can't optimize it */
+ Tsource[VAR_NUM(opline->op1.var)] = NULL;
+ break;
+ }
+ if (opline->op1_type == IS_CONST) {
+ int target = get_const_switch_target(cfg, op_array, block, opline, &ZEND_OP1_LITERAL(opline));
+ literal_dtor(&ZEND_OP1_LITERAL(opline));
+ literal_dtor(&ZEND_OP2_LITERAL(opline));
+ opline->opcode = ZEND_JMP;
+ opline->op1_type = IS_UNUSED;
+ opline->op2_type = IS_UNUSED;
+ block->successors_count = 1;
+ block->successors[0] = target;
+ }
+ break;
+
+ case ZEND_CASE:
+ case ZEND_CASE_STRICT:
+ case ZEND_COPY_TMP:
+ if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
+ /* Variable will be deleted later by FREE, so we can't optimize it */
+ Tsource[VAR_NUM(opline->op1.var)] = NULL;
+ break;
+ }
+ /* break missing intentionally */
+
+ case ZEND_IS_EQUAL:
+ case ZEND_IS_NOT_EQUAL:
+ if (opline->op1_type == IS_CONST &&
+ opline->op2_type == IS_CONST) {
+ goto optimize_constant_binary_op;
+ }
+ /* IS_EQ(TRUE, X) => BOOL(X)
+ * IS_EQ(FALSE, X) => BOOL_NOT(X)
+ * IS_NOT_EQ(TRUE, X) => BOOL_NOT(X)
+ * IS_NOT_EQ(FALSE, X) => BOOL(X)
+ * CASE(TRUE, X) => BOOL(X)
+ * CASE(FALSE, X) => BOOL_NOT(X)
+ */
+ if (opline->op1_type == IS_CONST &&
+ (Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_FALSE ||
+ Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_TRUE)) {
+ /* Optimization of comparison with "null" is not safe,
+ * because ("0" == null) is not equal to !("0")
+ */
+ opline->opcode =
+ ((opline->opcode != ZEND_IS_NOT_EQUAL) == ((Z_TYPE(ZEND_OP1_LITERAL(opline))) == IS_TRUE)) ?
+ ZEND_BOOL : ZEND_BOOL_NOT;
+ COPY_NODE(opline->op1, opline->op2);
+ SET_UNUSED(opline->op2);
+ ++(*opt_count);
+ goto optimize_bool;
+ } else if (opline->op2_type == IS_CONST &&
+ (Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_FALSE ||
+ Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_TRUE)) {
+ /* Optimization of comparison with "null" is not safe,
+ * because ("0" == null) is not equal to !("0")
+ */
+ opline->opcode =
+ ((opline->opcode != ZEND_IS_NOT_EQUAL) == ((Z_TYPE(ZEND_OP2_LITERAL(opline))) == IS_TRUE)) ?
+ ZEND_BOOL : ZEND_BOOL_NOT;
+ SET_UNUSED(opline->op2);
+ ++(*opt_count);
+ goto optimize_bool;
+ }
+ break;
+
+ case ZEND_BOOL:
+ case ZEND_BOOL_NOT:
+ optimize_bool:
+ if (opline->op1_type == IS_CONST) {
+ goto optimize_const_unary_op;
+ }
+ if (opline->op1_type == IS_TMP_VAR &&
+ !zend_bitset_in(used_ext, VAR_NUM(opline->op1.var))) {
+ src = VAR_SOURCE(opline->op1);
+ if (src) {
+ switch (src->opcode) {
+ case ZEND_BOOL_NOT:
+ /* T = BOOL_NOT(X) + BOOL(T) -> NOP, BOOL_NOT(X) */
+ VAR_SOURCE(opline->op1) = NULL;
+ COPY_NODE(opline->op1, src->op1);
+ opline->opcode = (opline->opcode == ZEND_BOOL) ? ZEND_BOOL_NOT : ZEND_BOOL;
+ MAKE_NOP(src);
+ ++(*opt_count);
+ goto optimize_bool;
+ case ZEND_BOOL:
+ /* T = BOOL(X) + BOOL(T) -> NOP, BOOL(X) */
+ VAR_SOURCE(opline->op1) = NULL;
+ COPY_NODE(opline->op1, src->op1);
+ MAKE_NOP(src);
+ ++(*opt_count);
+ goto optimize_bool;
+ case ZEND_IS_EQUAL:
+ if (opline->opcode == ZEND_BOOL_NOT) {
+ src->opcode = ZEND_IS_NOT_EQUAL;
+ }
+ COPY_NODE(src->result, opline->result);
+ SET_VAR_SOURCE(src);
+ MAKE_NOP(opline);
+ ++(*opt_count);
+ break;
+ case ZEND_IS_NOT_EQUAL:
+ if (opline->opcode == ZEND_BOOL_NOT) {
+ src->opcode = ZEND_IS_EQUAL;
+ }
+ COPY_NODE(src->result, opline->result);
+ SET_VAR_SOURCE(src);
+ MAKE_NOP(opline);
+ ++(*opt_count);
+ break;
+ case ZEND_IS_IDENTICAL:
+ if (opline->opcode == ZEND_BOOL_NOT) {
+ src->opcode = ZEND_IS_NOT_IDENTICAL;
+ }
+ COPY_NODE(src->result, opline->result);
+ SET_VAR_SOURCE(src);
+ MAKE_NOP(opline);
+ ++(*opt_count);
+ break;
+ case ZEND_IS_NOT_IDENTICAL:
+ if (opline->opcode == ZEND_BOOL_NOT) {
+ src->opcode = ZEND_IS_IDENTICAL;
+ }
+ COPY_NODE(src->result, opline->result);
+ SET_VAR_SOURCE(src);
+ MAKE_NOP(opline);
+ ++(*opt_count);
+ break;
+ case ZEND_IS_SMALLER:
+ if (opline->opcode == ZEND_BOOL_NOT) {
+ zend_uchar tmp_type;
+ uint32_t tmp;
+
+ src->opcode = ZEND_IS_SMALLER_OR_EQUAL;
+ tmp_type = src->op1_type;
+ src->op1_type = src->op2_type;
+ src->op2_type = tmp_type;
+ tmp = src->op1.num;
+ src->op1.num = src->op2.num;
+ src->op2.num = tmp;
+ }
+ COPY_NODE(src->result, opline->result);
+ SET_VAR_SOURCE(src);
+ MAKE_NOP(opline);
+ ++(*opt_count);
+ break;
+ case ZEND_IS_SMALLER_OR_EQUAL:
+ if (opline->opcode == ZEND_BOOL_NOT) {
+ zend_uchar tmp_type;
+ uint32_t tmp;
+
+ src->opcode = ZEND_IS_SMALLER;
+ tmp_type = src->op1_type;
+ src->op1_type = src->op2_type;
+ src->op2_type = tmp_type;
+ tmp = src->op1.num;
+ src->op1.num = src->op2.num;
+ src->op2.num = tmp;
+ }
+ COPY_NODE(src->result, opline->result);
+ SET_VAR_SOURCE(src);
+ MAKE_NOP(opline);
+ ++(*opt_count);
+ break;
+ case ZEND_ISSET_ISEMPTY_CV:
+ case ZEND_ISSET_ISEMPTY_VAR:
+ case ZEND_ISSET_ISEMPTY_DIM_OBJ:
+ case ZEND_ISSET_ISEMPTY_PROP_OBJ:
+ case ZEND_ISSET_ISEMPTY_STATIC_PROP:
+ case ZEND_INSTANCEOF:
+ case ZEND_TYPE_CHECK:
+ case ZEND_DEFINED:
+ case ZEND_IN_ARRAY:
+ case ZEND_ARRAY_KEY_EXISTS:
+ if (opline->opcode == ZEND_BOOL_NOT) {
+ break;
+ }
+ COPY_NODE(src->result, opline->result);
+ SET_VAR_SOURCE(src);
+ MAKE_NOP(opline);
+ ++(*opt_count);
+ break;
+ }
+ }
+ }
+ break;
+
+ case ZEND_JMPZ:
+ case ZEND_JMPNZ:
+ while (1) {
+ if (opline->op1_type == IS_CONST) {
+ ++(*opt_count);
+ block->successors_count = 1;
+ if (zend_is_true(&ZEND_OP1_LITERAL(opline)) ==
+ (opline->opcode == ZEND_JMPZ)) {
+
+ MAKE_NOP(opline);
+ block->successors[0] = block->successors[1];
+ block->len--;
+ cfg->blocks[block->successors[0]].flags |= ZEND_BB_FOLLOW;
+ break;
+ } else {
+ zend_basic_block *next = cfg->blocks + block->successors[1];
+
+ next->flags &= ~ZEND_BB_FOLLOW;
+ if (!(next->flags & (ZEND_BB_TARGET|ZEND_BB_PROTECTED))) {
+ next->flags &= ~ZEND_BB_REACHABLE;
+ }
+ opline->opcode = ZEND_JMP;
+ COPY_NODE(opline->op1, opline->op2);
+ break;
+ }
+ } else if (opline->op1_type == IS_TMP_VAR &&
+ !zend_bitset_in(used_ext, VAR_NUM(opline->op1.var))) {
+ src = VAR_SOURCE(opline->op1);
+ if (src) {
+ if (src->opcode == ZEND_BOOL_NOT) {
+ VAR_SOURCE(opline->op1) = NULL;
+ COPY_NODE(opline->op1, src->op1);
+ /* T = BOOL_NOT(X) + JMPZ(T) -> NOP, JMPNZ(X) */
+ opline->opcode = INV_COND(opline->opcode);
+ MAKE_NOP(src);
+ ++(*opt_count);
+ continue;
+ } else if (src->opcode == ZEND_BOOL ||
+ src->opcode == ZEND_QM_ASSIGN) {
+ VAR_SOURCE(opline->op1) = NULL;
+ COPY_NODE(opline->op1, src->op1);
+ MAKE_NOP(src);
+ ++(*opt_count);
+ continue;
+ }
+ }
+ }
+ break;
+ }
+ break;
+
+ case ZEND_JMPZNZ:
+ while (1) {
+ if (opline->op1_type == IS_CONST) {
+ ++(*opt_count);
+ if (zend_is_true(&ZEND_OP1_LITERAL(opline))) {
+ zend_op *target_opline = ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value);
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target_opline);
+ block->successors[0] = block->successors[1];
+ } else {
+ zend_op *target_opline = ZEND_OP2_JMP_ADDR(opline);
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target_opline);
+ }
+ block->successors_count = 1;
+ opline->op1_type = IS_UNUSED;
+ opline->extended_value = 0;
+ opline->opcode = ZEND_JMP;
+ break;
+ } else if (opline->op1_type == IS_TMP_VAR &&
+ !zend_bitset_in(used_ext, VAR_NUM(opline->op1.var))) {
+ src = VAR_SOURCE(opline->op1);
+ if (src) {
+ if (src->opcode == ZEND_BOOL_NOT) {
+ /* T = BOOL_NOT(X) + JMPZNZ(T,L1,L2) -> NOP, JMPZNZ(X,L2,L1) */
+ uint32_t tmp;
+
+ VAR_SOURCE(opline->op1) = NULL;
+ COPY_NODE(opline->op1, src->op1);
+ tmp = block->successors[0];
+ block->successors[0] = block->successors[1];
+ block->successors[1] = tmp;
+ MAKE_NOP(src);
+ ++(*opt_count);
+ continue;
+ } else if (src->opcode == ZEND_BOOL ||
+ src->opcode == ZEND_QM_ASSIGN) {
+ VAR_SOURCE(opline->op1) = NULL;
+ COPY_NODE(opline->op1, src->op1);
+ MAKE_NOP(src);
+ ++(*opt_count);
+ continue;
+ }
+ }
+ }
+ break;
+ }
+ break;
+
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ while (1) {
+ if (opline->op1_type == IS_CONST) {
+ if (zend_is_true(&ZEND_OP1_LITERAL(opline)) ==
+ (opline->opcode == ZEND_JMPZ_EX)) {
+
+ ++(*opt_count);
+ opline->opcode = ZEND_QM_ASSIGN;
+ zval_ptr_dtor_nogc(&ZEND_OP1_LITERAL(opline));
+ ZVAL_BOOL(&ZEND_OP1_LITERAL(opline), opline->opcode == ZEND_JMPZ_EX);
+ opline->op2.num = 0;
+ block->successors_count = 1;
+ block->successors[0] = block->successors[1];
+ cfg->blocks[block->successors[0]].flags |= ZEND_BB_FOLLOW;
+ break;
+ }
+ } else if (opline->op1_type == IS_TMP_VAR &&
+ (!zend_bitset_in(used_ext, VAR_NUM(opline->op1.var)) ||
+ opline->result.var == opline->op1.var)) {
+ src = VAR_SOURCE(opline->op1);
+ if (src) {
+ if (src->opcode == ZEND_BOOL ||
+ src->opcode == ZEND_QM_ASSIGN) {
+ VAR_SOURCE(opline->op1) = NULL;
+ COPY_NODE(opline->op1, src->op1);
+ MAKE_NOP(src);
+ ++(*opt_count);
+ continue;
+ }
+ }
+ }
+ break;
+ }
+ break;
+
+ case ZEND_CONCAT:
+ case ZEND_FAST_CONCAT:
+ if (opline->op1_type == IS_CONST &&
+ opline->op2_type == IS_CONST) {
+ goto optimize_constant_binary_op;
+ }
+
+ if (opline->op2_type == IS_CONST &&
+ opline->op1_type == IS_TMP_VAR) {
+
+ src = VAR_SOURCE(opline->op1);
+ if (src &&
+ (src->opcode == ZEND_CONCAT ||
+ src->opcode == ZEND_FAST_CONCAT) &&
+ src->op2_type == IS_CONST) {
+ /* compress consecutive CONCATs */
+ int l, old_len;
+
+ if (Z_TYPE(ZEND_OP2_LITERAL(opline)) != IS_STRING) {
+ convert_to_string(&ZEND_OP2_LITERAL(opline));
+ }
+ if (Z_TYPE(ZEND_OP2_LITERAL(src)) != IS_STRING) {
+ convert_to_string(&ZEND_OP2_LITERAL(src));
+ }
+
+ VAR_SOURCE(opline->op1) = NULL;
+ COPY_NODE(opline->op1, src->op1);
+ old_len = Z_STRLEN(ZEND_OP2_LITERAL(src));
+ l = old_len + Z_STRLEN(ZEND_OP2_LITERAL(opline));
+ if (!Z_REFCOUNTED(ZEND_OP2_LITERAL(src))) {
+ zend_string *tmp = zend_string_alloc(l, 0);
+ memcpy(ZSTR_VAL(tmp), Z_STRVAL(ZEND_OP2_LITERAL(src)), old_len);
+ Z_STR(ZEND_OP2_LITERAL(src)) = tmp;
+ } else {
+ Z_STR(ZEND_OP2_LITERAL(src)) = zend_string_extend(Z_STR(ZEND_OP2_LITERAL(src)), l, 0);
+ }
+ Z_TYPE_INFO(ZEND_OP2_LITERAL(src)) = IS_STRING_EX;
+ memcpy(Z_STRVAL(ZEND_OP2_LITERAL(src)) + old_len, Z_STRVAL(ZEND_OP2_LITERAL(opline)), Z_STRLEN(ZEND_OP2_LITERAL(opline)));
+ Z_STRVAL(ZEND_OP2_LITERAL(src))[l] = '\0';
+ zval_ptr_dtor_str(&ZEND_OP2_LITERAL(opline));
+ ZVAL_STR(&ZEND_OP2_LITERAL(opline), zend_new_interned_string(Z_STR(ZEND_OP2_LITERAL(src))));
+ ZVAL_NULL(&ZEND_OP2_LITERAL(src));
+ MAKE_NOP(src);
+ ++(*opt_count);
+ }
+ }
+
+ if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
+ src = VAR_SOURCE(opline->op1);
+ if (src &&
+ src->opcode == ZEND_CAST &&
+ src->extended_value == IS_STRING &&
+ src->op1_type != IS_CONST) {
+ /* convert T1 = CAST(STRING, X), T2 = CONCAT(T1, Y) to T2 = CONCAT(X,Y) */
+ VAR_SOURCE(opline->op1) = NULL;
+ COPY_NODE(opline->op1, src->op1);
+ MAKE_NOP(src);
+ ++(*opt_count);
+ }
+ }
+ if (opline->op2_type & (IS_TMP_VAR|IS_VAR)) {
+ src = VAR_SOURCE(opline->op2);
+ if (src &&
+ src->opcode == ZEND_CAST &&
+ src->extended_value == IS_STRING &&
+ src->op1_type != IS_CONST) {
+ /* convert T1 = CAST(STRING, X), T2 = CONCAT(Y, T1) to T2 = CONCAT(Y,X) */
+ zend_op *src = VAR_SOURCE(opline->op2);
+ VAR_SOURCE(opline->op2) = NULL;
+ COPY_NODE(opline->op2, src->op1);
+ MAKE_NOP(src);
+ ++(*opt_count);
+ }
+ }
+ if (opline->op1_type == IS_CONST &&
+ Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_STRING &&
+ Z_STRLEN(ZEND_OP1_LITERAL(opline)) == 0) {
+ /* convert CONCAT('', X) => CAST(STRING, X) */
+ literal_dtor(&ZEND_OP1_LITERAL(opline));
+ opline->opcode = ZEND_CAST;
+ opline->extended_value = IS_STRING;
+ COPY_NODE(opline->op1, opline->op2);
+ opline->op2_type = IS_UNUSED;
+ opline->op2.var = 0;
+ ++(*opt_count);
+ } else if (opline->op2_type == IS_CONST &&
+ Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING &&
+ Z_STRLEN(ZEND_OP2_LITERAL(opline)) == 0) {
+ /* convert CONCAT(X, '') => CAST(STRING, X) */
+ literal_dtor(&ZEND_OP2_LITERAL(opline));
+ opline->opcode = ZEND_CAST;
+ opline->extended_value = IS_STRING;
+ opline->op2_type = IS_UNUSED;
+ opline->op2.var = 0;
+ ++(*opt_count);
+ } else if (opline->opcode == ZEND_CONCAT &&
+ (opline->op1_type == IS_CONST ||
+ (opline->op1_type == IS_TMP_VAR &&
+ VAR_SOURCE(opline->op1) &&
+ (VAR_SOURCE(opline->op1)->opcode == ZEND_FAST_CONCAT ||
+ VAR_SOURCE(opline->op1)->opcode == ZEND_ROPE_END ||
+ VAR_SOURCE(opline->op1)->opcode == ZEND_FETCH_CONSTANT ||
+ VAR_SOURCE(opline->op1)->opcode == ZEND_FETCH_CLASS_CONSTANT))) &&
+ (opline->op2_type == IS_CONST ||
+ (opline->op2_type == IS_TMP_VAR &&
+ VAR_SOURCE(opline->op2) &&
+ (VAR_SOURCE(opline->op2)->opcode == ZEND_FAST_CONCAT ||
+ VAR_SOURCE(opline->op2)->opcode == ZEND_ROPE_END ||
+ VAR_SOURCE(opline->op2)->opcode == ZEND_FETCH_CONSTANT ||
+ VAR_SOURCE(opline->op2)->opcode == ZEND_FETCH_CLASS_CONSTANT)))) {
+ opline->opcode = ZEND_FAST_CONCAT;
+ ++(*opt_count);
+ }
+ break;
+
+ case ZEND_ADD:
+ case ZEND_SUB:
+ case ZEND_MUL:
+ case ZEND_DIV:
+ case ZEND_MOD:
+ case ZEND_SL:
+ case ZEND_SR:
+ case ZEND_IS_SMALLER:
+ case ZEND_IS_SMALLER_OR_EQUAL:
+ case ZEND_IS_IDENTICAL:
+ case ZEND_IS_NOT_IDENTICAL:
+ case ZEND_BOOL_XOR:
+ case ZEND_BW_OR:
+ case ZEND_BW_AND:
+ case ZEND_BW_XOR:
+ if (opline->op1_type == IS_CONST &&
+ opline->op2_type == IS_CONST) {
+ /* evaluate constant expressions */
+ zval result;
+
+optimize_constant_binary_op:
+ if (zend_optimizer_eval_binary_op(&result, opline->opcode, &ZEND_OP1_LITERAL(opline), &ZEND_OP2_LITERAL(opline)) == SUCCESS) {
+ literal_dtor(&ZEND_OP1_LITERAL(opline));
+ literal_dtor(&ZEND_OP2_LITERAL(opline));
+ opline->opcode = ZEND_QM_ASSIGN;
+ SET_UNUSED(opline->op2);
+ zend_optimizer_update_op1_const(op_array, opline, &result);
+ ++(*opt_count);
+ }
+ }
+ break;
+
+ case ZEND_BW_NOT:
+ if (opline->op1_type == IS_CONST) {
+ /* evaluate constant unary ops */
+ zval result;
+
+optimize_const_unary_op:
+ if (zend_optimizer_eval_unary_op(&result, opline->opcode, &ZEND_OP1_LITERAL(opline)) == SUCCESS) {
+ literal_dtor(&ZEND_OP1_LITERAL(opline));
+ opline->opcode = ZEND_QM_ASSIGN;
+ zend_optimizer_update_op1_const(op_array, opline, &result);
+ ++(*opt_count);
+ }
+ }
+ break;
+
+ case ZEND_CAST:
+ if (opline->op1_type == IS_CONST) {
+ /* cast of constant operand */
+ zval result;
+
+ if (zend_optimizer_eval_cast(&result, opline->extended_value, &ZEND_OP1_LITERAL(opline)) == SUCCESS) {
+ literal_dtor(&ZEND_OP1_LITERAL(opline));
+ opline->opcode = ZEND_QM_ASSIGN;
+ opline->extended_value = 0;
+ zend_optimizer_update_op1_const(op_array, opline, &result);
+ ++(*opt_count);
+ }
+ }
+ break;
+
+ case ZEND_STRLEN:
+ if (opline->op1_type == IS_CONST) {
+ zval result;
+
+ if (zend_optimizer_eval_strlen(&result, &ZEND_OP1_LITERAL(opline)) == SUCCESS) {
+ literal_dtor(&ZEND_OP1_LITERAL(opline));
+ opline->opcode = ZEND_QM_ASSIGN;
+ zend_optimizer_update_op1_const(op_array, opline, &result);
+ ++(*opt_count);
+ }
+ }
+ break;
+
+ case ZEND_RETURN:
+ case ZEND_EXIT:
+ if (opline->op1_type == IS_TMP_VAR) {
+ src = VAR_SOURCE(opline->op1);
+ if (src && src->opcode == ZEND_QM_ASSIGN) {
+ zend_op *op = src + 1;
+ bool optimize = 1;
+
+ while (op < opline) {
+ if ((op->op1_type == opline->op1_type
+ && op->op1.var == opline->op1.var)
+ || (op->op2_type == opline->op1_type
+ && op->op2.var == opline->op1.var)) {
+ optimize = 0;
+ break;
+ }
+ op++;
+ }
+
+ if (optimize) {
+ /* T = QM_ASSIGN(X), RETURN(T) to NOP, RETURN(X) */
+ VAR_SOURCE(opline->op1) = NULL;
+ COPY_NODE(opline->op1, src->op1);
+ MAKE_NOP(src);
+ ++(*opt_count);
+ }
+ }
+ }
+ break;
+
+ case ZEND_QM_ASSIGN:
+ if (opline->op1_type == opline->result_type &&
+ opline->op1.var == opline->result.var) {
+ /* strip T = QM_ASSIGN(T) */
+ MAKE_NOP(opline);
+ ++(*opt_count);
+ } else if (opline->op1_type == IS_TMP_VAR &&
+ opline->result_type == IS_TMP_VAR &&
+ !zend_bitset_in(used_ext, VAR_NUM(opline->op1.var))) {
+ /* T1 = ..., T2 = QM_ASSIGN(T1) to T2 = ..., NOP */
+ src = VAR_SOURCE(opline->op1);
+ if (src &&
+ src->opcode != ZEND_COPY_TMP &&
+ src->opcode != ZEND_ADD_ARRAY_ELEMENT &&
+ src->opcode != ZEND_ADD_ARRAY_UNPACK &&
+ (src->opcode != ZEND_DECLARE_LAMBDA_FUNCTION ||
+ src == opline -1)) {
+ src->result.var = opline->result.var;
+ VAR_SOURCE(opline->op1) = NULL;
+ VAR_SOURCE(opline->result) = src;
+ MAKE_NOP(opline);
+ ++(*opt_count);
+ }
+ }
+ break;
+ }
+
+ /* get variable source */
+ if (opline->result_type & (IS_VAR|IS_TMP_VAR)) {
+ SET_VAR_SOURCE(opline);
+ }
+ opline++;
+ }
+}
+
+/* Rebuild plain (optimized) op_array from CFG */
+static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_optimizer_ctx *ctx)
+{
+ zend_basic_block *blocks = cfg->blocks;
+ zend_basic_block *end = blocks + cfg->blocks_count;
+ zend_basic_block *b;
+ zend_op *new_opcodes;
+ zend_op *opline;
+ uint32_t len = 0;
+ int n;
+
+ for (b = blocks; b < end; b++) {
+ if (b->len == 0) {
+ continue;
+ }
+ if (b->flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE)) {
+ if (b->flags & ZEND_BB_UNREACHABLE_FREE) {
+ /* Only keep the FREE for the loop var */
+ ZEND_ASSERT(op_array->opcodes[b->start].opcode == ZEND_FREE
+ || op_array->opcodes[b->start].opcode == ZEND_FE_FREE);
+ len += b->len = 1;
+ continue;
+ }
+
+ opline = op_array->opcodes + b->start + b->len - 1;
+ if (opline->opcode == ZEND_JMP) {
+ zend_basic_block *next = b + 1;
+
+ while (next < end && !(next->flags & ZEND_BB_REACHABLE)) {
+ next++;
+ }
+ if (next < end && next == blocks + b->successors[0]) {
+ /* JMP to the next block - strip it */
+ MAKE_NOP(opline);
+ b->len--;
+ }
+ } else if (b->len == 1 && opline->opcode == ZEND_NOP) {
+ /* skip empty block */
+ b->len--;
+ }
+ len += b->len;
+ } else {
+ /* this block will not be used, delete all constants there */
+ zend_op *op = op_array->opcodes + b->start;
+ zend_op *end = op + b->len;
+ for (; op < end; op++) {
+ if (op->op1_type == IS_CONST) {
+ literal_dtor(&ZEND_OP1_LITERAL(op));
+ }
+ if (op->op2_type == IS_CONST) {
+ literal_dtor(&ZEND_OP2_LITERAL(op));
+ }
+ }
+ }
+ }
+
+ new_opcodes = emalloc(len * sizeof(zend_op));
+ opline = new_opcodes;
+
+ /* Copy code of reachable blocks into a single buffer */
+ for (b = blocks; b < end; b++) {
+ if (b->flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE)) {
+ memcpy(opline, op_array->opcodes + b->start, b->len * sizeof(zend_op));
+ b->start = opline - new_opcodes;
+ opline += b->len;
+ }
+ }
+
+ /* adjust jump targets */
+ efree(op_array->opcodes);
+ op_array->opcodes = new_opcodes;
+ op_array->last = len;
+
+ for (b = blocks; b < end; b++) {
+ if (!(b->flags & ZEND_BB_REACHABLE) || b->len == 0) {
+ continue;
+ }
+ opline = op_array->opcodes + b->start + b->len - 1;
+ switch (opline->opcode) {
+ case ZEND_FAST_CALL:
+ case ZEND_JMP:
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op1, new_opcodes + blocks[b->successors[0]].start);
+ break;
+ case ZEND_JMPZNZ:
+ opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, new_opcodes + blocks[b->successors[1]].start);
+ /* break missing intentionally */
+ case ZEND_JMPZ:
+ case ZEND_JMPNZ:
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ case ZEND_FE_RESET_R:
+ case ZEND_FE_RESET_RW:
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ case ZEND_ASSERT_CHECK:
+ case ZEND_JMP_NULL:
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op2, new_opcodes + blocks[b->successors[0]].start);
+ break;
+ case ZEND_CATCH:
+ if (!(opline->extended_value & ZEND_LAST_CATCH)) {
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op2, new_opcodes + blocks[b->successors[0]].start);
+ }
+ break;
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, new_opcodes + blocks[b->successors[0]].start);
+ break;
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ case ZEND_MATCH:
+ {
+ HashTable *jumptable = Z_ARRVAL(ZEND_OP2_LITERAL(opline));
+ zval *zv;
+ uint32_t s = 0;
+ ZEND_ASSERT(b->successors_count == (opline->opcode == ZEND_MATCH ? 1 : 2) + zend_hash_num_elements(jumptable));
+
+ ZEND_HASH_FOREACH_VAL(jumptable, zv) {
+ Z_LVAL_P(zv) = ZEND_OPLINE_TO_OFFSET(opline, new_opcodes + blocks[b->successors[s++]].start);
+ } ZEND_HASH_FOREACH_END();
+ opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, new_opcodes + blocks[b->successors[s++]].start);
+ break;
+ }
+ }
+ }
+
+ /* adjust exception jump targets & remove unused try_catch_array entries */
+ if (op_array->last_try_catch) {
+ int i, j;
+ uint32_t *map;
+ ALLOCA_FLAG(use_heap);
+
+ map = (uint32_t *)do_alloca(sizeof(uint32_t) * op_array->last_try_catch, use_heap);
+ for (i = 0, j = 0; i< op_array->last_try_catch; i++) {
+ if (blocks[cfg->map[op_array->try_catch_array[i].try_op]].flags & ZEND_BB_REACHABLE) {
+ map[i] = j;
+ op_array->try_catch_array[j].try_op = blocks[cfg->map[op_array->try_catch_array[i].try_op]].start;
+ if (op_array->try_catch_array[i].catch_op) {
+ op_array->try_catch_array[j].catch_op = blocks[cfg->map[op_array->try_catch_array[i].catch_op]].start;
+ } else {
+ op_array->try_catch_array[j].catch_op = 0;
+ }
+ if (op_array->try_catch_array[i].finally_op) {
+ op_array->try_catch_array[j].finally_op = blocks[cfg->map[op_array->try_catch_array[i].finally_op]].start;
+ } else {
+ op_array->try_catch_array[j].finally_op = 0;
+ }
+ if (!op_array->try_catch_array[i].finally_end) {
+ op_array->try_catch_array[j].finally_end = 0;
+ } else {
+ op_array->try_catch_array[j].finally_end = blocks[cfg->map[op_array->try_catch_array[i].finally_end]].start;
+ }
+ j++;
+ }
+ }
+ if (i != j) {
+ op_array->last_try_catch = j;
+ if (j == 0) {
+ efree(op_array->try_catch_array);
+ op_array->try_catch_array = NULL;
+ }
+
+ if (op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) {
+ zend_op *opline = new_opcodes;
+ zend_op *end = opline + len;
+ while (opline < end) {
+ if (opline->opcode == ZEND_FAST_RET &&
+ opline->op2.num != (uint32_t)-1 &&
+ opline->op2.num < (uint32_t)j) {
+ opline->op2.num = map[opline->op2.num];
+ }
+ opline++;
+ }
+ }
+ }
+ free_alloca(map, use_heap);
+ }
+
+ /* adjust early binding list */
+ if (op_array->fn_flags & ZEND_ACC_EARLY_BINDING) {
+ ZEND_ASSERT(op_array == &ctx->script->main_op_array);
+ ctx->script->first_early_binding_opline =
+ zend_build_delayed_early_binding_list(op_array);
+ }
+
+ /* rebuild map (just for printing) */
+ memset(cfg->map, -1, sizeof(int) * op_array->last);
+ for (n = 0; n < cfg->blocks_count; n++) {
+ if (cfg->blocks[n].flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE)) {
+ cfg->map[cfg->blocks[n].start] = n;
+ }
+ }
+}
+
+static zend_always_inline zend_basic_block *get_target_block(const zend_cfg *cfg, zend_basic_block *block, int n, uint32_t *opt_count)
+{
+ int b;
+ zend_basic_block *target_block = cfg->blocks + block->successors[n];
+
+ if (target_block->len == 0 && !(target_block->flags & ZEND_BB_PROTECTED)) {
+ do {
+ b = target_block->successors[0];
+ target_block = cfg->blocks + b;
+ } while (target_block->len == 0 && !(target_block->flags & ZEND_BB_PROTECTED));
+ block->successors[n] = b;
+ ++(*opt_count);
+ }
+ return target_block;
+}
+
+static zend_always_inline zend_basic_block *get_follow_block(const zend_cfg *cfg, zend_basic_block *block, int n, uint32_t *opt_count)
+{
+ int b;
+ zend_basic_block *target_block = cfg->blocks + block->successors[n];
+
+ if (target_block->len == 0 && !(target_block->flags & ZEND_BB_PROTECTED)) {
+ do {
+ b = target_block->successors[0];
+ target_block = cfg->blocks + b;
+ } while (target_block->len == 0 && !(target_block->flags & ZEND_BB_PROTECTED));
+ block->successors[n] = b;
+ ++(*opt_count);
+ }
+ return target_block;
+}
+
+static zend_always_inline zend_basic_block *get_next_block(const zend_cfg *cfg, zend_basic_block *block)
+{
+ zend_basic_block *next_block = block + 1;
+ zend_basic_block *end = cfg->blocks + cfg->blocks_count;
+
+ while (1) {
+ if (next_block == end) {
+ return NULL;
+ } else if (next_block->flags & ZEND_BB_REACHABLE) {
+ break;
+ }
+ next_block++;
+ }
+ while (next_block->len == 0 && !(next_block->flags & ZEND_BB_PROTECTED)) {
+ next_block = cfg->blocks + next_block->successors[0];
+ }
+ return next_block;
+}
+
+
+/* we use "jmp_hitlist" to avoid infinity loops during jmp optimization */
+static zend_always_inline int in_hitlist(int target, int *jmp_hitlist, int jmp_hitlist_count)
+{
+ int i;
+
+ for (i = 0; i < jmp_hitlist_count; i++) {
+ if (jmp_hitlist[i] == target) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+#define CHECK_LOOP(target) \
+ if (EXPECTED(!in_hitlist(target, jmp_hitlist, jmp_hitlist_count))) { \
+ jmp_hitlist[jmp_hitlist_count++] = target; \
+ } else { \
+ break; \
+ }
+
+static void zend_jmp_optimization(zend_basic_block *block, zend_op_array *op_array, const zend_cfg *cfg, int *jmp_hitlist, uint32_t *opt_count)
+{
+ /* last_op is the last opcode of the current block */
+ zend_basic_block *target_block, *follow_block, *next_block;
+ zend_op *last_op, *target;
+ int next, jmp_hitlist_count;
+
+ if (block->len == 0) {
+ return;
+ }
+
+ last_op = op_array->opcodes + block->start + block->len - 1;
+ switch (last_op->opcode) {
+ case ZEND_JMP:
+ jmp_hitlist_count = 0;
+
+ target_block = get_target_block(cfg, block, 0, opt_count);
+ while (target_block->len == 1) {
+ target = op_array->opcodes + target_block->start;
+ if (target->opcode == ZEND_JMP) {
+ /* JMP L, L: JMP L1 -> JMP L1 */
+ next = target_block->successors[0];
+ } else {
+ break;
+ }
+ CHECK_LOOP(next);
+ block->successors[0] = next;
+ ++(*opt_count);
+ target_block = get_target_block(cfg, block, 0, opt_count);
+ }
+
+ next_block = get_next_block(cfg, block);
+ if (target_block == next_block) {
+ /* JMP(next) -> NOP */
+ MAKE_NOP(last_op);
+ ++(*opt_count);
+ block->len--;
+ } else if (target_block->len == 1) {
+ target = op_array->opcodes + target_block->start;
+ if (target->opcode == ZEND_JMPZNZ) {
+ /* JMP L, L: JMPZNZ L1,L2 -> JMPZNZ L1,L2 */
+ *last_op = *target;
+ if (last_op->op1_type == IS_CONST) {
+ zval zv;
+ ZVAL_COPY(&zv, &ZEND_OP1_LITERAL(last_op));
+ last_op->op1.constant = zend_optimizer_add_literal(op_array, &zv);
+ }
+ block->successors_count = 2;
+ block->successors[0] = target_block->successors[0];
+ block->successors[1] = target_block->successors[1];
+ ++(*opt_count);
+ goto optimize_jmpznz;
+ } else if ((target->opcode == ZEND_RETURN ||
+ target->opcode == ZEND_RETURN_BY_REF ||
+ target->opcode == ZEND_GENERATOR_RETURN ||
+ target->opcode == ZEND_EXIT) &&
+ !(op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK)) {
+ /* JMP L, L: RETURN to immediate RETURN */
+ *last_op = *target;
+ if (last_op->op1_type == IS_CONST) {
+ zval zv;
+ ZVAL_COPY(&zv, &ZEND_OP1_LITERAL(last_op));
+ last_op->op1.constant = zend_optimizer_add_literal(op_array, &zv);
+ }
+ block->successors_count = 0;
+ ++(*opt_count);
+ }
+ }
+ break;
+
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ case ZEND_JMP_NULL:
+ jmp_hitlist_count = 0;
+
+ target_block = get_target_block(cfg, block, 0, opt_count);
+ while (target_block->len == 1) {
+ target = op_array->opcodes + target_block->start;
+
+ if (target->opcode == ZEND_JMP) {
+ /* JMP_SET(X, L), L: JMP(L2) -> JMP_SET(X, L2) */
+ next = target_block->successors[0];
+ CHECK_LOOP(next);
+ block->successors[0] = next;
+ ++(*opt_count);
+ } else {
+ break;
+ }
+ target_block = get_target_block(cfg, block, 0, opt_count);
+ }
+ break;
+
+ case ZEND_JMPZ:
+ case ZEND_JMPNZ:
+ jmp_hitlist_count = 0;
+
+ target_block = get_target_block(cfg, block, 0, opt_count);
+ while (target_block->len == 1) {
+ target = op_array->opcodes + target_block->start;
+
+ if (target->opcode == ZEND_JMP) {
+ /* JMPZ(X, L), L: JMP(L2) -> JMPZ(X, L2) */
+ next = target_block->successors[0];
+ } else if (target->opcode == last_op->opcode &&
+ SAME_VAR(target->op1, last_op->op1)) {
+ /* JMPZ(X, L), L: JMPZ(X, L2) -> JMPZ(X, L2) */
+ next = target_block->successors[0];
+ } else if (target->opcode == INV_COND(last_op->opcode) &&
+ SAME_VAR(target->op1, last_op->op1)) {
+ /* JMPZ(X, L), L: JMPNZ(X, L2) -> JMPZ(X, L+1) */
+ next = target_block->successors[1];
+ } else if (target->opcode == ZEND_JMPZNZ &&
+ SAME_VAR(target->op1, last_op->op1)) {
+ /* JMPZ(X, L), L: JMPZNZ(X, L2, L3) -> JMPZ(X, L2) */
+ next = target_block->successors[last_op->opcode == ZEND_JMPNZ];
+ } else {
+ break;
+ }
+ CHECK_LOOP(next);
+ block->successors[0] = next;
+ ++(*opt_count);
+ target_block = get_target_block(cfg, block, 0, opt_count);
+ }
+
+ follow_block = get_follow_block(cfg, block, 1, opt_count);
+ if (target_block == follow_block) {
+ /* L: JMP[N]Z(X, L+1) -> NOP or FREE(X) */
+ if (last_op->op1_type == IS_CV) {
+ last_op->opcode = ZEND_CHECK_VAR;
+ last_op->op2.num = 0;
+ } else if (last_op->op1_type & (IS_VAR|IS_TMP_VAR)) {
+ last_op->opcode = ZEND_FREE;
+ last_op->op2.num = 0;
+ } else {
+ MAKE_NOP(last_op);
+ block->len--;
+ }
+ block->successors_count = 1;
+ ++(*opt_count);
+ } else if (follow_block->len == 1) {
+ target = op_array->opcodes + follow_block->start;
+ if (target->opcode == ZEND_JMP) {
+ if (block->successors[0] == follow_block->successors[0]) {
+ /* JMPZ(X,L1), JMP(L1) -> NOP, JMP(L1) */
+ if (last_op->op1_type == IS_CV) {
+ last_op->opcode = ZEND_CHECK_VAR;
+ last_op->op2.num = 0;
+ } else if (last_op->op1_type & (IS_VAR|IS_TMP_VAR)) {
+ last_op->opcode = ZEND_FREE;
+ last_op->op2.num = 0;
+ } else {
+ MAKE_NOP(last_op);
+ block->len--;
+ }
+ block->successors[0] = follow_block - cfg->blocks;
+ block->successors_count = 1;
+ ++(*opt_count);
+ break;
+ } else if (!(follow_block->flags & (ZEND_BB_TARGET | ZEND_BB_PROTECTED))) {
+ next_block = get_next_block(cfg, follow_block);
+
+ if (target_block == next_block) {
+ /* JMPZ(X,L1) JMP(L2) L1: -> JMPNZ(X,L2) NOP*/
+
+ last_op->opcode = INV_COND(last_op->opcode);
+
+ block->successors[0] = follow_block->successors[0];
+ block->successors[1] = next_block - cfg->blocks;
+
+ follow_block->flags &= ~ZEND_BB_REACHABLE;
+ MAKE_NOP(target);
+ follow_block->len = 0;
+
+ next_block->flags |= ZEND_BB_FOLLOW;
+
+ break;
+ }
+ }
+
+ /* JMPZ(X,L1), JMP(L2) -> JMPZNZ(X,L1,L2) */
+ if (last_op->opcode == ZEND_JMPZ) {
+ block->successors[1] = follow_block->successors[0];
+ } else {
+ block->successors[1] = block->successors[0];
+ block->successors[0] = follow_block->successors[0];
+ }
+ last_op->opcode = ZEND_JMPZNZ;
+ ++(*opt_count);
+ }
+ }
+ break;
+
+ case ZEND_JMPNZ_EX:
+ case ZEND_JMPZ_EX:
+ jmp_hitlist_count = 0;
+
+ target_block = get_target_block(cfg, block, 0, opt_count);
+ while (target_block->len == 1) {
+ target = op_array->opcodes + target_block->start;
+
+ if (target->opcode == ZEND_JMP) {
+ /* T = JMPZ_EX(X, L), L: JMP(L2) -> T = JMPZ(X, L2) */
+ next = target_block->successors[0];
+ } else if (target->opcode == last_op->opcode-3 &&
+ (SAME_VAR(target->op1, last_op->result) ||
+ SAME_VAR(target->op1, last_op->op1))) {
+ /* T = JMPZ_EX(X, L1), L1: JMPZ({X|T}, L2) -> T = JMPZ_EX(X, L2) */
+ next = target_block->successors[0];
+ } else if (target->opcode == last_op->opcode &&
+ target->result.var == last_op->result.var &&
+ (SAME_VAR(target->op1, last_op->result) ||
+ SAME_VAR(target->op1, last_op->op1))) {
+ /* T = JMPZ_EX(X, L1), L1: T = JMPZ_EX({X|T}, L2) -> T = JMPZ_EX(X, L2) */
+ next = target_block->successors[0];
+ } else if (target->opcode == ZEND_JMPZNZ &&
+ (SAME_VAR(target->op1, last_op->result) ||
+ SAME_VAR(target->op1, last_op->op1))) {
+ /* T = JMPZ_EX(X, L), L: JMPZNZ({X|T}, L2, L3) -> T = JMPZ_EX(X, L2) */
+ next = target_block->successors[last_op->opcode == ZEND_JMPNZ_EX];
+ } else if (target->opcode == INV_EX_COND(last_op->opcode) &&
+ (SAME_VAR(target->op1, last_op->result) ||
+ SAME_VAR(target->op1, last_op->op1))) {
+ /* T = JMPZ_EX(X, L1), L1: JMPNZ({X|T1}, L2) -> T = JMPZ_EX(X, L1+1) */
+ next = target_block->successors[1];
+ } else if (target->opcode == INV_EX_COND_EX(last_op->opcode) &&
+ target->result.var == last_op->result.var &&
+ (SAME_VAR(target->op1, last_op->result) ||
+ SAME_VAR(target->op1, last_op->op1))) {
+ /* T = JMPZ_EX(X, L1), L1: T = JMPNZ_EX({X|T}, L2) -> T = JMPZ_EX(X, L1+1) */
+ next = target_block->successors[1];
+ } else if (target->opcode == ZEND_BOOL &&
+ (SAME_VAR(target->op1, last_op->result) ||
+ SAME_VAR(target->op1, last_op->op1))) {
+ /* convert Y = JMPZ_EX(X,L1), L1: Z = BOOL(Y) to
+ Z = JMPZ_EX(X,L1+1) */
+
+ /* NOTE: This optimization pattern is not safe, but works, */
+ /* because result of JMPZ_EX instruction */
+ /* is not used on the following path and */
+ /* should be used once on the branch path. */
+ /* */
+ /* The pattern works well only if jums processed in */
+ /* direct order, otherwise it breaks JMPZ_EX */
+ /* sequences too early. */
+ last_op->result.var = target->result.var;
+ next = target_block->successors[0];
+ } else {
+ break;
+ }
+ CHECK_LOOP(next);
+ block->successors[0] = next;
+ ++(*opt_count);
+ target_block = get_target_block(cfg, block, 0, opt_count);
+ }
+
+ follow_block = get_follow_block(cfg, block, 1, opt_count);
+ if (target_block == follow_block) {
+ /* L: T = JMP[N]Z_EX(X, L+1) -> T = BOOL(X) */
+ last_op->opcode = ZEND_BOOL;
+ last_op->op2.num = 0;
+ block->successors_count = 1;
+ ++(*opt_count);
+ break;
+ }
+ break;
+
+ case ZEND_JMPZNZ: {
+optimize_jmpznz:
+ jmp_hitlist_count = 0;
+ target_block = get_target_block(cfg, block, 0, opt_count);
+ while (target_block->len == 1) {
+ target = op_array->opcodes + target_block->start;
+
+ if (target->opcode == ZEND_JMP) {
+ /* JMPZNZ(X, L1, L2), L1: JMP(L3) -> JMPZNZ(X, L3, L2) */
+ next = target_block->successors[0];
+ } else if ((target->opcode == ZEND_JMPZ || target->opcode == ZEND_JMPZNZ) &&
+ SAME_VAR(target->op1, last_op->op1)) {
+ /* JMPZNZ(X, L1, L2), L1: JMPZ(X, L3) -> JMPZNZ(X, L3, L2) */
+ next = target_block->successors[0];
+ } else if (target->opcode == ZEND_JMPNZ &&
+ SAME_VAR(target->op1, last_op->op1)) {
+ /* JMPZNZ(X, L1, L2), L1: X = JMPNZ(X, L3) -> JMPZNZ(X, L1+1, L2) */
+ next = target_block->successors[1];
+ } else {
+ break;
+ }
+ CHECK_LOOP(next);
+ block->successors[0] = next;
+ ++(*opt_count);
+ target_block = get_target_block(cfg, block, 0, opt_count);
+ }
+
+ jmp_hitlist_count = 0;
+ follow_block = get_target_block(cfg, block, 1, opt_count);
+ while (follow_block->len == 1) {
+ target = op_array->opcodes + follow_block->start;
+
+ if (target->opcode == ZEND_JMP) {
+ /* JMPZNZ(X, L1, L2), L2: JMP(L3) -> JMPZNZ(X, L1, L3) */
+ next = follow_block->successors[0];
+ } else if (target->opcode == ZEND_JMPNZ &&
+ SAME_VAR(target->op1, last_op->op1)) {
+ /* JMPZNZ(X, L1, L2), L2: X = JMPNZ(X, L3) -> JMPZNZ(X, L1, L3) */
+ next = follow_block->successors[0];
+ } else if ((target->opcode == ZEND_JMPZ || target->opcode == ZEND_JMPZNZ) &&
+ SAME_VAR(target->op1, last_op->op1)) {
+ /* JMPZNZ(X, L1, L2), L2: JMPZ(X, L3) -> JMPZNZ(X, L1, L2+1) */
+ next = follow_block->successors[1];
+ } else {
+ break;
+ }
+ CHECK_LOOP(next);
+ block->successors[1] = next;
+ ++(*opt_count);
+ follow_block = get_target_block(cfg, block, 1, opt_count);
+ }
+
+ next_block = get_next_block(cfg, block);
+ if (target_block == follow_block &&
+ !(last_op->op1_type & (IS_VAR|IS_TMP_VAR))) {
+ /* JMPZNZ(?,L,L) -> JMP(L) */
+ last_op->opcode = ZEND_JMP;
+ SET_UNUSED(last_op->op1);
+ SET_UNUSED(last_op->op2);
+ last_op->extended_value = 0;
+ block->successors_count = 1;
+ ++(*opt_count);
+ } else if (target_block == next_block) {
+ /* jumping to next on Z - can follow to it and jump only on NZ */
+ /* JMPZNZ(X,L1,L2) L1: -> JMPNZ(X,L2) */
+ int tmp = block->successors[0];
+ last_op->opcode = ZEND_JMPNZ;
+ block->successors[0] = block->successors[1];
+ block->successors[1] = tmp;
+ ++(*opt_count);
+ } else if (follow_block == next_block) {
+ /* jumping to next on NZ - can follow to it and jump only on Z */
+ /* JMPZNZ(X,L1,L2) L2: -> JMPZ(X,L1) */
+ last_op->opcode = ZEND_JMPZ;
+ ++(*opt_count);
+ }
+ break;
+ }
+ }
+}
+
+/* Global data dependencies */
+
+/* Find a set of variables which are used outside of the block where they are
+ * defined. We won't apply some optimization patterns for such variables. */
+static void zend_t_usage(zend_cfg *cfg, zend_op_array *op_array, zend_bitset used_ext, zend_optimizer_ctx *ctx)
+{
+ int n;
+ zend_basic_block *block, *next_block;
+ uint32_t var_num;
+ uint32_t bitset_len;
+ zend_bitset usage;
+ zend_bitset defined_here;
+ void *checkpoint;
+ zend_op *opline, *end;
+
+
+ if (op_array->T == 0) {
+ /* shortcut - if no Ts, nothing to do */
+ return;
+ }
+
+ checkpoint = zend_arena_checkpoint(ctx->arena);
+ bitset_len = zend_bitset_len(op_array->last_var + op_array->T);
+ defined_here = zend_arena_alloc(&ctx->arena, bitset_len * ZEND_BITSET_ELM_SIZE);
+
+ zend_bitset_clear(defined_here, bitset_len);
+ for (n = 1; n < cfg->blocks_count; n++) {
+ block = cfg->blocks + n;
+
+ if (!(block->flags & ZEND_BB_REACHABLE)) {
+ continue;
+ }
+
+ opline = op_array->opcodes + block->start;
+ end = opline + block->len;
+ if (!(block->flags & ZEND_BB_FOLLOW) ||
+ (block->flags & ZEND_BB_TARGET)) {
+ /* Skip continuation of "extended" BB */
+ zend_bitset_clear(defined_here, bitset_len);
+ }
+
+ while (opline<end) {
+ if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) {
+ var_num = VAR_NUM(opline->op1.var);
+ if (!zend_bitset_in(defined_here, var_num)) {
+ zend_bitset_incl(used_ext, var_num);
+ }
+ }
+ if (opline->op2_type == IS_VAR) {
+ var_num = VAR_NUM(opline->op2.var);
+ if (opline->opcode == ZEND_FE_FETCH_R ||
+ opline->opcode == ZEND_FE_FETCH_RW) {
+ /* these opcode use the op2 as result */
+ zend_bitset_incl(defined_here, var_num);
+ } else if (!zend_bitset_in(defined_here, var_num)) {
+ zend_bitset_incl(used_ext, var_num);
+ }
+ } else if (opline->op2_type == IS_TMP_VAR) {
+ var_num = VAR_NUM(opline->op2.var);
+ if (!zend_bitset_in(defined_here, var_num)) {
+ zend_bitset_incl(used_ext, var_num);
+ }
+ }
+
+ if (opline->result_type == IS_VAR) {
+ var_num = VAR_NUM(opline->result.var);
+ zend_bitset_incl(defined_here, var_num);
+ } else if (opline->result_type == IS_TMP_VAR) {
+ var_num = VAR_NUM(opline->result.var);
+ switch (opline->opcode) {
+ case ZEND_ADD_ARRAY_ELEMENT:
+ case ZEND_ADD_ARRAY_UNPACK:
+ case ZEND_ROPE_ADD:
+ /* these opcodes use the result as argument */
+ if (!zend_bitset_in(defined_here, var_num)) {
+ zend_bitset_incl(used_ext, var_num);
+ }
+ break;
+ default :
+ zend_bitset_incl(defined_here, var_num);
+ }
+ }
+ opline++;
+ }
+ }
+
+ if (ctx->debug_level & ZEND_DUMP_BLOCK_PASS_VARS) {
+ int printed = 0;
+ uint32_t i;
+
+ for (i = op_array->last_var; i< op_array->T; i++) {
+ if (zend_bitset_in(used_ext, i)) {
+ if (!printed) {
+ fprintf(stderr, "NON-LOCAL-VARS: %d", i);
+ printed = 1;
+ } else {
+ fprintf(stderr, ", %d", i);
+ }
+ }
+ }
+ if (printed) {
+ fprintf(stderr, "\n");
+ }
+ }
+
+ usage = defined_here;
+ next_block = NULL;
+ for (n = cfg->blocks_count; n > 0;) {
+ block = cfg->blocks + (--n);
+
+ if (!(block->flags & ZEND_BB_REACHABLE) || block->len == 0) {
+ continue;
+ }
+
+ end = op_array->opcodes + block->start;
+ opline = end + block->len - 1;
+ if (!next_block ||
+ !(next_block->flags & ZEND_BB_FOLLOW) ||
+ (next_block->flags & ZEND_BB_TARGET)) {
+ /* Skip continuation of "extended" BB */
+ zend_bitset_copy(usage, used_ext, bitset_len);
+ } else if (block->successors_count > 1) {
+ zend_bitset_union(usage, used_ext, bitset_len);
+ }
+ next_block = block;
+
+ while (opline >= end) {
+ /* usage checks */
+ if (opline->result_type & (IS_VAR|IS_TMP_VAR)) {
+ if (!zend_bitset_in(usage, VAR_NUM(opline->result.var))) {
+ switch (opline->opcode) {
+ case ZEND_ASSIGN_OP:
+ case ZEND_ASSIGN_DIM_OP:
+ case ZEND_ASSIGN_OBJ_OP:
+ case ZEND_ASSIGN_STATIC_PROP_OP:
+ case ZEND_PRE_INC:
+ case ZEND_PRE_DEC:
+ case ZEND_ASSIGN:
+ case ZEND_ASSIGN_REF:
+ case ZEND_DO_FCALL:
+ case ZEND_DO_ICALL:
+ case ZEND_DO_UCALL:
+ case ZEND_DO_FCALL_BY_NAME:
+ opline->result_type = IS_UNUSED;
+ break;
+ case ZEND_POST_INC:
+ case ZEND_POST_DEC:
+ case ZEND_POST_INC_OBJ:
+ case ZEND_POST_DEC_OBJ:
+ case ZEND_POST_INC_STATIC_PROP:
+ case ZEND_POST_DEC_STATIC_PROP:
+ opline->opcode -= 2;
+ opline->result_type = IS_UNUSED;
+ break;
+ case ZEND_QM_ASSIGN:
+ case ZEND_BOOL:
+ case ZEND_BOOL_NOT:
+ if (opline->op1_type == IS_CV) {
+ opline->opcode = ZEND_CHECK_VAR;
+ SET_UNUSED(opline->result);
+ } else if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
+ opline->opcode = ZEND_FREE;
+ SET_UNUSED(opline->result);
+ } else {
+ if (opline->op1_type == IS_CONST) {
+ literal_dtor(&ZEND_OP1_LITERAL(opline));
+ }
+ MAKE_NOP(opline);
+ }
+ break;
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ opline->opcode -= 3;
+ SET_UNUSED(opline->result);
+ break;
+ case ZEND_ADD_ARRAY_ELEMENT:
+ case ZEND_ADD_ARRAY_UNPACK:
+ case ZEND_ROPE_ADD:
+ zend_bitset_incl(usage, VAR_NUM(opline->result.var));
+ break;
+ }
+ } else {
+ switch (opline->opcode) {
+ case ZEND_ADD_ARRAY_ELEMENT:
+ case ZEND_ADD_ARRAY_UNPACK:
+ case ZEND_ROPE_ADD:
+ break;
+ default:
+ zend_bitset_excl(usage, VAR_NUM(opline->result.var));
+ break;
+ }
+ }
+ }
+
+ if (opline->op2_type == IS_VAR) {
+ switch (opline->opcode) {
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ zend_bitset_excl(usage, VAR_NUM(opline->op2.var));
+ break;
+ default:
+ zend_bitset_incl(usage, VAR_NUM(opline->op2.var));
+ break;
+ }
+ } else if (opline->op2_type == IS_TMP_VAR) {
+ zend_bitset_incl(usage, VAR_NUM(opline->op2.var));
+ }
+
+ if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) {
+ zend_bitset_incl(usage, VAR_NUM(opline->op1.var));
+ }
+
+ opline--;
+ }
+ }
+
+ zend_arena_release(&ctx->arena, checkpoint);
+}
+
+static void zend_merge_blocks(zend_op_array *op_array, zend_cfg *cfg, uint32_t *opt_count)
+{
+ int i;
+ zend_basic_block *b, *bb;
+ zend_basic_block *prev = NULL;
+
+ for (i = 0; i < cfg->blocks_count; i++) {
+ b = cfg->blocks + i;
+ if (b->flags & ZEND_BB_REACHABLE) {
+ if ((b->flags & ZEND_BB_FOLLOW) &&
+ !(b->flags & (ZEND_BB_TARGET | ZEND_BB_PROTECTED)) &&
+ prev && prev->successors_count == 1 && prev->successors[0] == i)
+ {
+ zend_op *last_op = op_array->opcodes + prev->start + prev->len - 1;
+ if (prev->len != 0 && last_op->opcode == ZEND_JMP) {
+ MAKE_NOP(last_op);
+ }
+
+ for (bb = prev + 1; bb != b; bb++) {
+ zend_op *op = op_array->opcodes + bb->start;
+ zend_op *end = op + bb->len;
+ while (op < end) {
+ if (op->op1_type == IS_CONST) {
+ literal_dtor(&ZEND_OP1_LITERAL(op));
+ }
+ if (op->op2_type == IS_CONST) {
+ literal_dtor(&ZEND_OP2_LITERAL(op));
+ }
+ MAKE_NOP(op);
+ op++;
+ }
+ /* make block empty */
+ bb->len = 0;
+ }
+
+ /* re-link */
+ prev->flags |= (b->flags & ZEND_BB_EXIT);
+ prev->len = b->start + b->len - prev->start;
+ prev->successors_count = b->successors_count;
+ if (b->successors != b->successors_storage) {
+ prev->successors = b->successors;
+ b->successors = b->successors_storage;
+ } else {
+ memcpy(prev->successors, b->successors, b->successors_count * sizeof(int));
+ }
+
+ /* unlink & make block empty and unreachable */
+ b->flags = 0;
+ b->len = 0;
+ b->successors_count = 0;
+ ++(*opt_count);
+ } else {
+ prev = b;
+ }
+ }
+ }
+}
+
+#define PASSES 3
+
+void zend_optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx)
+{
+ zend_cfg cfg;
+ zend_basic_block *blocks, *end, *b;
+ int pass;
+ uint32_t bitset_len;
+ zend_bitset usage;
+ void *checkpoint;
+ zend_op **Tsource;
+ uint32_t opt_count;
+ int *jmp_hitlist;
+
+ /* Build CFG */
+ checkpoint = zend_arena_checkpoint(ctx->arena);
+ if (zend_build_cfg(&ctx->arena, op_array, 0, &cfg) != SUCCESS) {
+ zend_arena_release(&ctx->arena, checkpoint);
+ return;
+ }
+
+ if (cfg.blocks_count * (op_array->last_var + op_array->T) > 64 * 1024 * 1024) {
+ zend_arena_release(&ctx->arena, checkpoint);
+ return;
+ }
+
+ if (ctx->debug_level & ZEND_DUMP_BEFORE_BLOCK_PASS) {
+ zend_dump_op_array(op_array, ZEND_DUMP_CFG, "before block pass", &cfg);
+ }
+
+ bitset_len = zend_bitset_len(op_array->last_var + op_array->T);
+ Tsource = zend_arena_calloc(&ctx->arena, op_array->last_var + op_array->T, sizeof(zend_op *));
+ usage = zend_arena_alloc(&ctx->arena, bitset_len * ZEND_BITSET_ELM_SIZE);
+ jmp_hitlist = zend_arena_alloc(&ctx->arena, cfg.blocks_count * sizeof(int));
+
+ blocks = cfg.blocks;
+ end = blocks + cfg.blocks_count;
+ for (pass = 0; pass < PASSES; pass++) {
+ opt_count = 0;
+
+ /* Compute data dependencies */
+ zend_bitset_clear(usage, bitset_len);
+ zend_t_usage(&cfg, op_array, usage, ctx);
+
+ /* optimize each basic block separately */
+ for (b = blocks; b < end; b++) {
+ if (!(b->flags & ZEND_BB_REACHABLE)) {
+ continue;
+ }
+ /* we track data dependencies only inside a single basic block */
+ if (!(b->flags & ZEND_BB_FOLLOW) ||
+ (b->flags & ZEND_BB_TARGET)) {
+ /* Skip continuation of "extended" BB */
+ memset(Tsource, 0, (op_array->last_var + op_array->T) * sizeof(zend_op *));
+ }
+ zend_optimize_block(b, op_array, usage, &cfg, Tsource, &opt_count);
+ }
+
+ /* Eliminate NOPs */
+ for (b = blocks; b < end; b++) {
+ if (b->flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE)) {
+ strip_nops(op_array, b);
+ }
+ }
+
+ opt_count = 0;
+
+ /* Jump optimization for each block */
+ for (b = blocks; b < end; b++) {
+ if (b->flags & ZEND_BB_REACHABLE) {
+ zend_jmp_optimization(b, op_array, &cfg, jmp_hitlist, &opt_count);
+ }
+ }
+
+ /* Eliminate unreachable basic blocks */
+ zend_cfg_remark_reachable_blocks(op_array, &cfg);
+
+ /* Merge Blocks */
+ zend_merge_blocks(op_array, &cfg, &opt_count);
+
+ if (opt_count == 0) {
+ break;
+ }
+ }
+
+ assemble_code_blocks(&cfg, op_array, ctx);
+
+ if (ctx->debug_level & ZEND_DUMP_AFTER_BLOCK_PASS) {
+ zend_dump_op_array(op_array, ZEND_DUMP_CFG | ZEND_DUMP_HIDE_UNREACHABLE, "after block pass", &cfg);
+ }
+
+ /* Destroy CFG */
+ zend_arena_release(&ctx->arena, checkpoint);
+}
diff --git a/Zend/Optimizer/compact_literals.c b/Zend/Optimizer/compact_literals.c
new file mode 100644
index 0000000000..0e1529d2bd
--- /dev/null
+++ b/Zend/Optimizer/compact_literals.c
@@ -0,0 +1,843 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend OPcache |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Dmitry Stogov <dmitry@php.net> |
+ | Xinchen Hui <laruence@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+/* pass 11
+ * - compact literals table
+ */
+
+#include "php.h"
+#include "Optimizer/zend_optimizer.h"
+#include "Optimizer/zend_optimizer_internal.h"
+#include "zend_API.h"
+#include "zend_constants.h"
+#include "zend_execute.h"
+#include "zend_vm.h"
+#include "zend_extensions.h"
+
+#define DEBUG_COMPACT_LITERALS 0
+
+#define LITERAL_VALUE 0x0100
+#define LITERAL_FUNC 0x0200
+#define LITERAL_CLASS 0x0300
+#define LITERAL_CONST 0x0400
+#define LITERAL_CLASS_CONST 0x0500
+#define LITERAL_STATIC_METHOD 0x0600
+#define LITERAL_STATIC_PROPERTY 0x0700
+#define LITERAL_METHOD 0x0800
+#define LITERAL_PROPERTY 0x0900
+#define LITERAL_GLOBAL 0x0A00
+
+#define LITERAL_KIND_MASK 0x0f00
+#define LITERAL_NUM_RELATED_MASK 0x000f
+
+#define LITERAL_NUM_RELATED(info) (info & LITERAL_NUM_RELATED_MASK)
+
+typedef struct _literal_info {
+ uint32_t flags; /* bitmask (see defines above) */
+} literal_info;
+
+#define LITERAL_INFO(n, kind, related) do { \
+ info[n].flags = ((kind) | (related)); \
+ } while (0)
+
+static size_t type_num_classes(const zend_op_array *op_array, uint32_t arg_num)
+{
+ zend_arg_info *arg_info;
+ if (arg_num > 0) {
+ if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
+ return 0;
+ }
+ if (EXPECTED(arg_num <= op_array->num_args)) {
+ arg_info = &op_array->arg_info[arg_num-1];
+ } else if (UNEXPECTED(op_array->fn_flags & ZEND_ACC_VARIADIC)) {
+ arg_info = &op_array->arg_info[op_array->num_args];
+ } else {
+ return 0;
+ }
+ } else {
+ arg_info = op_array->arg_info - 1;
+ }
+
+ if (ZEND_TYPE_HAS_CLASS(arg_info->type)) {
+ if (ZEND_TYPE_HAS_LIST(arg_info->type)) {
+ return ZEND_TYPE_LIST(arg_info->type)->num_types;
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+static uint32_t add_static_slot(HashTable *hash,
+ zend_op_array *op_array,
+ uint32_t op1,
+ uint32_t op2,
+ uint32_t kind,
+ int *cache_size)
+{
+ uint32_t ret;
+ zval *class_name = &op_array->literals[op1];
+ zval *prop_name = &op_array->literals[op2];
+ zval *pos, tmp;
+
+ zend_string *key = zend_create_member_string(Z_STR_P(class_name), Z_STR_P(prop_name));
+ ZSTR_H(key) = zend_string_hash_func(key);
+ ZSTR_H(key) += kind;
+
+ pos = zend_hash_find(hash, key);
+ if (pos) {
+ ret = Z_LVAL_P(pos);
+ } else {
+ ret = *cache_size;
+ *cache_size += (kind == LITERAL_STATIC_PROPERTY ? 3 : 2) * sizeof(void *);
+ ZVAL_LONG(&tmp, ret);
+ zend_hash_add(hash, key, &tmp);
+ }
+ zend_string_release_ex(key, 0);
+ return ret;
+}
+
+void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx *ctx)
+{
+ zend_op *opline, *end;
+ int i, j, n, *map, cache_size;
+ zval zv, *pos;
+ literal_info *info;
+ int l_null = -1;
+ int l_false = -1;
+ int l_true = -1;
+ int l_empty_arr = -1;
+ HashTable hash, double_hash;
+ zend_string *key = NULL;
+ void *checkpoint = zend_arena_checkpoint(ctx->arena);
+ int *const_slot, *class_slot, *func_slot, *bind_var_slot, *property_slot, *method_slot;
+
+ if (op_array->last_literal) {
+ info = (literal_info*)zend_arena_calloc(&ctx->arena, op_array->last_literal, sizeof(literal_info));
+
+ /* Mark literals of specific types */
+ opline = op_array->opcodes;
+ end = opline + op_array->last;
+ while (opline < end) {
+ switch (opline->opcode) {
+ case ZEND_INIT_FCALL:
+ LITERAL_INFO(opline->op2.constant, LITERAL_FUNC, 1);
+ break;
+ case ZEND_INIT_FCALL_BY_NAME:
+ LITERAL_INFO(opline->op2.constant, LITERAL_FUNC, 2);
+ break;
+ case ZEND_INIT_NS_FCALL_BY_NAME:
+ LITERAL_INFO(opline->op2.constant, LITERAL_FUNC, 3);
+ break;
+ case ZEND_INIT_METHOD_CALL:
+ if (opline->op1_type == IS_CONST) {
+ LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 1);
+ }
+ if (opline->op2_type == IS_CONST) {
+ LITERAL_INFO(opline->op2.constant, LITERAL_METHOD, 2);
+ }
+ break;
+ case ZEND_INIT_STATIC_METHOD_CALL:
+ if (opline->op1_type == IS_CONST) {
+ LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 2);
+ }
+ if (opline->op2_type == IS_CONST) {
+ LITERAL_INFO(opline->op2.constant, LITERAL_STATIC_METHOD, 2);
+ }
+ break;
+ case ZEND_CATCH:
+ LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 2);
+ break;
+ case ZEND_DEFINED:
+ LITERAL_INFO(opline->op1.constant, LITERAL_CONST, 1);
+ break;
+ case ZEND_FETCH_CONSTANT:
+ if (opline->op1.num & IS_CONSTANT_UNQUALIFIED_IN_NAMESPACE) {
+ LITERAL_INFO(opline->op2.constant, LITERAL_CONST, 3);
+ } else {
+ LITERAL_INFO(opline->op2.constant, LITERAL_CONST, 2);
+ }
+ break;
+ case ZEND_FETCH_CLASS_CONSTANT:
+ if (opline->op1_type == IS_CONST) {
+ LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 2);
+ }
+ LITERAL_INFO(opline->op2.constant, LITERAL_CLASS_CONST, 1);
+ break;
+ case ZEND_ASSIGN_STATIC_PROP:
+ case ZEND_ASSIGN_STATIC_PROP_REF:
+ case ZEND_FETCH_STATIC_PROP_R:
+ case ZEND_FETCH_STATIC_PROP_W:
+ case ZEND_FETCH_STATIC_PROP_RW:
+ case ZEND_FETCH_STATIC_PROP_IS:
+ case ZEND_FETCH_STATIC_PROP_UNSET:
+ case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
+ case ZEND_UNSET_STATIC_PROP:
+ case ZEND_ISSET_ISEMPTY_STATIC_PROP:
+ case ZEND_PRE_INC_STATIC_PROP:
+ case ZEND_PRE_DEC_STATIC_PROP:
+ case ZEND_POST_INC_STATIC_PROP:
+ case ZEND_POST_DEC_STATIC_PROP:
+ case ZEND_ASSIGN_STATIC_PROP_OP:
+ if (opline->op2_type == IS_CONST) {
+ LITERAL_INFO(opline->op2.constant, LITERAL_CLASS, 2);
+ }
+ if (opline->op1_type == IS_CONST) {
+ LITERAL_INFO(opline->op1.constant, LITERAL_STATIC_PROPERTY, 1);
+ }
+ break;
+ case ZEND_FETCH_CLASS:
+ case ZEND_INSTANCEOF:
+ if (opline->op2_type == IS_CONST) {
+ LITERAL_INFO(opline->op2.constant, LITERAL_CLASS, 2);
+ }
+ break;
+ case ZEND_NEW:
+ if (opline->op1_type == IS_CONST) {
+ LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 2);
+ }
+ break;
+ case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_OBJ_REF:
+ case ZEND_FETCH_OBJ_R:
+ case ZEND_FETCH_OBJ_W:
+ case ZEND_FETCH_OBJ_RW:
+ case ZEND_FETCH_OBJ_IS:
+ case ZEND_FETCH_OBJ_UNSET:
+ case ZEND_FETCH_OBJ_FUNC_ARG:
+ case ZEND_UNSET_OBJ:
+ case ZEND_PRE_INC_OBJ:
+ case ZEND_PRE_DEC_OBJ:
+ case ZEND_POST_INC_OBJ:
+ case ZEND_POST_DEC_OBJ:
+ case ZEND_ISSET_ISEMPTY_PROP_OBJ:
+ case ZEND_ASSIGN_OBJ_OP:
+ if (opline->op1_type == IS_CONST) {
+ LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 1);
+ }
+ if (opline->op2_type == IS_CONST) {
+ LITERAL_INFO(opline->op2.constant, LITERAL_PROPERTY, 1);
+ }
+ break;
+ case ZEND_BIND_GLOBAL:
+ LITERAL_INFO(opline->op2.constant, LITERAL_GLOBAL, 1);
+ break;
+ case ZEND_RECV_INIT:
+ LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 1);
+ break;
+ case ZEND_DECLARE_FUNCTION:
+ LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 2);
+ break;
+ case ZEND_DECLARE_CLASS:
+ case ZEND_DECLARE_CLASS_DELAYED:
+ LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 2);
+ if (opline->op2_type == IS_CONST) {
+ LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 1);
+ }
+ break;
+ case ZEND_ISSET_ISEMPTY_DIM_OBJ:
+ case ZEND_ASSIGN_DIM:
+ case ZEND_UNSET_DIM:
+ case ZEND_FETCH_DIM_R:
+ case ZEND_FETCH_DIM_W:
+ case ZEND_FETCH_DIM_RW:
+ case ZEND_FETCH_DIM_IS:
+ case ZEND_FETCH_DIM_FUNC_ARG:
+ case ZEND_FETCH_DIM_UNSET:
+ case ZEND_FETCH_LIST_R:
+ case ZEND_FETCH_LIST_W:
+ case ZEND_ASSIGN_DIM_OP:
+ if (opline->op1_type == IS_CONST) {
+ LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 1);
+ }
+ if (opline->op2_type == IS_CONST) {
+ if (Z_EXTRA(op_array->literals[opline->op2.constant]) == ZEND_EXTRA_VALUE) {
+ LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 2);
+ } else {
+ LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 1);
+ }
+ }
+ break;
+ default:
+ if (opline->op1_type == IS_CONST) {
+ LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 1);
+ }
+ if (opline->op2_type == IS_CONST) {
+ LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 1);
+ }
+ break;
+ }
+ opline++;
+ }
+
+#if DEBUG_COMPACT_LITERALS
+ {
+ int i, use_copy;
+ fprintf(stderr, "File %s func %s\n", op_array->filename->val,
+ op_array->function_name ? op_array->function_name->val : "main");
+ fprintf(stderr, "Literals table size %d\n", op_array->last_literal);
+
+ for (i = 0; i < op_array->last_literal; i++) {
+ zval zv;
+ ZVAL_COPY_VALUE(&zv, op_array->literals + i);
+ use_copy = zend_make_printable_zval(op_array->literals + i, &zv);
+ fprintf(stderr, "Literal %d, val (%zu):%s\n", i, Z_STRLEN(zv), Z_STRVAL(zv));
+ if (use_copy) {
+ zval_ptr_dtor_nogc(&zv);
+ }
+ }
+ fflush(stderr);
+ }
+#endif
+
+ /* Merge equal constants */
+ j = 0;
+ zend_hash_init(&hash, op_array->last_literal, NULL, NULL, 0);
+ /* Use separate hashtable for doubles stored as string keys, to avoid collisions. */
+ zend_hash_init(&double_hash, 0, NULL, NULL, 0);
+ map = (int*)zend_arena_alloc(&ctx->arena, op_array->last_literal * sizeof(int));
+ memset(map, 0, op_array->last_literal * sizeof(int));
+ for (i = 0; i < op_array->last_literal; i++) {
+ if (!info[i].flags) {
+ /* unset literal */
+ zval_ptr_dtor_nogc(&op_array->literals[i]);
+ continue;
+ }
+ switch (Z_TYPE(op_array->literals[i])) {
+ case IS_NULL:
+ if (l_null < 0) {
+ l_null = j;
+ if (i != j) {
+ op_array->literals[j] = op_array->literals[i];
+ info[j] = info[i];
+ }
+ j++;
+ }
+ map[i] = l_null;
+ break;
+ case IS_FALSE:
+ if (l_false < 0) {
+ l_false = j;
+ if (i != j) {
+ op_array->literals[j] = op_array->literals[i];
+ info[j] = info[i];
+ }
+ j++;
+ }
+ map[i] = l_false;
+ break;
+ case IS_TRUE:
+ if (l_true < 0) {
+ l_true = j;
+ if (i != j) {
+ op_array->literals[j] = op_array->literals[i];
+ info[j] = info[i];
+ }
+ j++;
+ }
+ map[i] = l_true;
+ break;
+ case IS_LONG:
+ if (LITERAL_NUM_RELATED(info[i].flags) == 1) {
+ if ((pos = zend_hash_index_find(&hash, Z_LVAL(op_array->literals[i]))) != NULL) {
+ map[i] = Z_LVAL_P(pos);
+ } else {
+ map[i] = j;
+ ZVAL_LONG(&zv, j);
+ zend_hash_index_add_new(&hash, Z_LVAL(op_array->literals[i]), &zv);
+ if (i != j) {
+ op_array->literals[j] = op_array->literals[i];
+ info[j] = info[i];
+ }
+ j++;
+ }
+ } else {
+ ZEND_ASSERT(LITERAL_NUM_RELATED(info[i].flags) == 2);
+ key = zend_string_init(Z_STRVAL(op_array->literals[i+1]), Z_STRLEN(op_array->literals[i+1]), 0);
+ ZSTR_H(key) = ZSTR_HASH(Z_STR(op_array->literals[i+1])) + 100 +
+ LITERAL_NUM_RELATED(info[i].flags) - 1;
+ if ((pos = zend_hash_find(&hash, key)) != NULL
+ && LITERAL_NUM_RELATED(info[Z_LVAL_P(pos)].flags) == 2) {
+ map[i] = Z_LVAL_P(pos);
+ zval_ptr_dtor_nogc(&op_array->literals[i+1]);
+ } else {
+ map[i] = j;
+ ZVAL_LONG(&zv, j);
+ zend_hash_add_new(&hash, key, &zv);
+ if (i != j) {
+ op_array->literals[j] = op_array->literals[i];
+ info[j] = info[i];
+ op_array->literals[j+1] = op_array->literals[i+1];
+ info[j+1] = info[i+1];
+ }
+ j += 2;
+ }
+ zend_string_release_ex(key, 0);
+ i++;
+ }
+ break;
+ case IS_DOUBLE:
+ if ((pos = zend_hash_str_find(&double_hash, (char*)&Z_DVAL(op_array->literals[i]), sizeof(double))) != NULL) {
+ map[i] = Z_LVAL_P(pos);
+ } else {
+ map[i] = j;
+ ZVAL_LONG(&zv, j);
+ zend_hash_str_add_new(&double_hash, (char*)&Z_DVAL(op_array->literals[i]), sizeof(double), &zv);
+ if (i != j) {
+ op_array->literals[j] = op_array->literals[i];
+ info[j] = info[i];
+ }
+ j++;
+ }
+ break;
+ case IS_STRING: {
+ if (LITERAL_NUM_RELATED(info[i].flags) == 1) {
+ key = zend_string_copy(Z_STR(op_array->literals[i]));
+ } else if ((info[i].flags & LITERAL_KIND_MASK) != LITERAL_VALUE) {
+ key = zend_string_init(Z_STRVAL(op_array->literals[i]), Z_STRLEN(op_array->literals[i]), 0);
+ ZSTR_H(key) = ZSTR_HASH(Z_STR(op_array->literals[i])) +
+ LITERAL_NUM_RELATED(info[i].flags) - 1;
+ } else {
+ /* Don't merge LITERAL_VALUE that has related literals */
+ key = NULL;
+ }
+ if (key && (pos = zend_hash_find(&hash, key)) != NULL &&
+ Z_TYPE(op_array->literals[Z_LVAL_P(pos)]) == IS_STRING &&
+ LITERAL_NUM_RELATED(info[i].flags) == LITERAL_NUM_RELATED(info[Z_LVAL_P(pos)].flags) &&
+ (LITERAL_NUM_RELATED(info[i].flags) != 2 ||
+ ((info[i].flags & LITERAL_KIND_MASK) != LITERAL_VALUE &&
+ (info[Z_LVAL_P(pos)].flags & LITERAL_KIND_MASK) != LITERAL_VALUE))) {
+ zend_string_release_ex(key, 0);
+ map[i] = Z_LVAL_P(pos);
+ zval_ptr_dtor_nogc(&op_array->literals[i]);
+ n = LITERAL_NUM_RELATED(info[i].flags);
+ while (n > 1) {
+ i++;
+ zval_ptr_dtor_nogc(&op_array->literals[i]);
+ n--;
+ }
+ } else {
+ map[i] = j;
+ ZVAL_LONG(&zv, j);
+ if (key) {
+ zend_hash_add_new(&hash, key, &zv);
+ zend_string_release_ex(key, 0);
+ }
+ if (i != j) {
+ op_array->literals[j] = op_array->literals[i];
+ info[j] = info[i];
+ }
+ j++;
+ n = LITERAL_NUM_RELATED(info[i].flags);
+ while (n > 1) {
+ i++;
+ if (i != j) op_array->literals[j] = op_array->literals[i];
+ j++;
+ n--;
+ }
+ }
+ break;
+ }
+ case IS_ARRAY:
+ if (zend_hash_num_elements(Z_ARRVAL(op_array->literals[i])) == 0) {
+ if (l_empty_arr < 0) {
+ l_empty_arr = j;
+ if (i != j) {
+ op_array->literals[j] = op_array->literals[i];
+ info[j] = info[i];
+ }
+ j++;
+ } else {
+ zval_ptr_dtor_nogc(&op_array->literals[i]);
+ }
+ map[i] = l_empty_arr;
+ break;
+ }
+ /* break missing intentionally */
+ default:
+ /* don't merge other types */
+ map[i] = j;
+ if (i != j) {
+ op_array->literals[j] = op_array->literals[i];
+ info[j] = info[i];
+ }
+ j++;
+ break;
+ }
+ }
+
+ /* Only clean "hash", as it will be reused in the loop below. */
+ zend_hash_clean(&hash);
+ zend_hash_destroy(&double_hash);
+ op_array->last_literal = j;
+
+ const_slot = zend_arena_alloc(&ctx->arena, j * 6 * sizeof(int));
+ memset(const_slot, -1, j * 6 * sizeof(int));
+ class_slot = const_slot + j;
+ func_slot = class_slot + j;
+ bind_var_slot = func_slot + j;
+ property_slot = bind_var_slot + j;
+ method_slot = property_slot + j;
+
+ /* Update opcodes to use new literals table */
+ cache_size = zend_op_array_extension_handles * sizeof(void*);
+ opline = op_array->opcodes;
+ end = opline + op_array->last;
+ while (opline < end) {
+ if (opline->op1_type == IS_CONST) {
+ opline->op1.constant = map[opline->op1.constant];
+ }
+ if (opline->op2_type == IS_CONST) {
+ opline->op2.constant = map[opline->op2.constant];
+ }
+ switch (opline->opcode) {
+ case ZEND_RECV_INIT:
+ case ZEND_RECV:
+ case ZEND_RECV_VARIADIC:
+ {
+ size_t num_classes = type_num_classes(op_array, opline->op1.num);
+ if (num_classes) {
+ opline->extended_value = cache_size;
+ cache_size += num_classes * sizeof(void *);
+ }
+ break;
+ }
+ case ZEND_VERIFY_RETURN_TYPE:
+ {
+ size_t num_classes = type_num_classes(op_array, 0);
+ if (num_classes) {
+ opline->op2.num = cache_size;
+ cache_size += num_classes * sizeof(void *);
+ }
+ break;
+ }
+ case ZEND_ASSIGN_STATIC_PROP_OP:
+ if (opline->op1_type == IS_CONST) {
+ // op1 static property
+ if (opline->op2_type == IS_CONST) {
+ (opline+1)->extended_value = add_static_slot(&hash, op_array,
+ opline->op2.constant,
+ opline->op1.constant,
+ LITERAL_STATIC_PROPERTY,
+ &cache_size);
+ } else {
+ (opline+1)->extended_value = cache_size;
+ cache_size += 3 * sizeof(void *);
+ }
+ } else if (opline->op2_type == IS_CONST) {
+ // op2 class
+ if (class_slot[opline->op2.constant] >= 0) {
+ (opline+1)->extended_value = class_slot[opline->op2.constant];
+ } else {
+ (opline+1)->extended_value = cache_size;
+ class_slot[opline->op2.constant] = cache_size;
+ cache_size += sizeof(void *);
+ }
+ }
+ break;
+ case ZEND_ASSIGN_OBJ_OP:
+ if (opline->op2_type == IS_CONST) {
+ // op2 property
+ if (opline->op1_type == IS_UNUSED &&
+ property_slot[opline->op2.constant] >= 0) {
+ (opline+1)->extended_value = property_slot[opline->op2.constant];
+ } else {
+ (opline+1)->extended_value = cache_size;
+ cache_size += 3 * sizeof(void *);
+ if (opline->op1_type == IS_UNUSED) {
+ property_slot[opline->op2.constant] = (opline+1)->extended_value;
+ }
+ }
+ }
+ break;
+ case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_OBJ_REF:
+ case ZEND_FETCH_OBJ_R:
+ case ZEND_FETCH_OBJ_W:
+ case ZEND_FETCH_OBJ_RW:
+ case ZEND_FETCH_OBJ_IS:
+ case ZEND_FETCH_OBJ_UNSET:
+ case ZEND_FETCH_OBJ_FUNC_ARG:
+ case ZEND_UNSET_OBJ:
+ case ZEND_PRE_INC_OBJ:
+ case ZEND_PRE_DEC_OBJ:
+ case ZEND_POST_INC_OBJ:
+ case ZEND_POST_DEC_OBJ:
+ if (opline->op2_type == IS_CONST) {
+ // op2 property
+ if (opline->op1_type == IS_UNUSED &&
+ property_slot[opline->op2.constant] >= 0) {
+ opline->extended_value = property_slot[opline->op2.constant] | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
+ } else {
+ opline->extended_value = cache_size | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
+ cache_size += 3 * sizeof(void *);
+ if (opline->op1_type == IS_UNUSED) {
+ property_slot[opline->op2.constant] = opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS;
+ }
+ }
+ }
+ break;
+ case ZEND_ISSET_ISEMPTY_PROP_OBJ:
+ if (opline->op2_type == IS_CONST) {
+ // op2 property
+ if (opline->op1_type == IS_UNUSED &&
+ property_slot[opline->op2.constant] >= 0) {
+ opline->extended_value = property_slot[opline->op2.constant] | (opline->extended_value & ZEND_ISEMPTY);
+ } else {
+ opline->extended_value = cache_size | (opline->extended_value & ZEND_ISEMPTY);
+ cache_size += 3 * sizeof(void *);
+ if (opline->op1_type == IS_UNUSED) {
+ property_slot[opline->op2.constant] = opline->extended_value & ~ZEND_ISEMPTY;
+ }
+ }
+ }
+ break;
+ case ZEND_INIT_FCALL:
+ case ZEND_INIT_FCALL_BY_NAME:
+ case ZEND_INIT_NS_FCALL_BY_NAME:
+ // op2 func
+ if (func_slot[opline->op2.constant] >= 0) {
+ opline->result.num = func_slot[opline->op2.constant];
+ } else {
+ opline->result.num = cache_size;
+ cache_size += sizeof(void *);
+ func_slot[opline->op2.constant] = opline->result.num;
+ }
+ break;
+ case ZEND_INIT_METHOD_CALL:
+ if (opline->op2_type == IS_CONST) {
+ // op2 method
+ if (opline->op1_type == IS_UNUSED &&
+ method_slot[opline->op2.constant] >= 0) {
+ opline->result.num = method_slot[opline->op2.constant];
+ } else {
+ opline->result.num = cache_size;
+ cache_size += 2 * sizeof(void *);
+ if (opline->op1_type == IS_UNUSED) {
+ method_slot[opline->op2.constant] = opline->result.num;
+ }
+ }
+ }
+ break;
+ case ZEND_INIT_STATIC_METHOD_CALL:
+ if (opline->op2_type == IS_CONST) {
+ // op2 static method
+ if (opline->op1_type == IS_CONST) {
+ opline->result.num = add_static_slot(&hash, op_array,
+ opline->op1.constant,
+ opline->op2.constant,
+ LITERAL_STATIC_METHOD,
+ &cache_size);
+ } else {
+ opline->result.num = cache_size;
+ cache_size += 2 * sizeof(void *);
+ }
+ } else if (opline->op1_type == IS_CONST) {
+ // op1 class
+ if (class_slot[opline->op1.constant] >= 0) {
+ opline->result.num = class_slot[opline->op1.constant];
+ } else {
+ opline->result.num = cache_size;
+ cache_size += sizeof(void *);
+ class_slot[opline->op1.constant] = opline->result.num;
+ }
+ }
+ break;
+ case ZEND_DEFINED:
+ // op1 const
+ if (const_slot[opline->op1.constant] >= 0) {
+ opline->extended_value = const_slot[opline->op1.constant];
+ } else {
+ opline->extended_value = cache_size;
+ cache_size += sizeof(void *);
+ const_slot[opline->op1.constant] = opline->extended_value;
+ }
+ break;
+ case ZEND_FETCH_CONSTANT:
+ // op2 const
+ if (const_slot[opline->op2.constant] >= 0) {
+ opline->extended_value = const_slot[opline->op2.constant];
+ } else {
+ opline->extended_value = cache_size;
+ cache_size += sizeof(void *);
+ const_slot[opline->op2.constant] = opline->extended_value;
+ }
+ break;
+ case ZEND_FETCH_CLASS_CONSTANT:
+ if (opline->op1_type == IS_CONST) {
+ // op1/op2 class_const
+ opline->extended_value = add_static_slot(&hash, op_array,
+ opline->op1.constant,
+ opline->op2.constant,
+ LITERAL_CLASS_CONST,
+ &cache_size);
+ } else {
+ opline->extended_value = cache_size;
+ cache_size += 2 * sizeof(void *);
+ }
+ break;
+ case ZEND_ASSIGN_STATIC_PROP:
+ case ZEND_ASSIGN_STATIC_PROP_REF:
+ case ZEND_FETCH_STATIC_PROP_R:
+ case ZEND_FETCH_STATIC_PROP_W:
+ case ZEND_FETCH_STATIC_PROP_RW:
+ case ZEND_FETCH_STATIC_PROP_IS:
+ case ZEND_FETCH_STATIC_PROP_UNSET:
+ case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
+ case ZEND_UNSET_STATIC_PROP:
+ case ZEND_ISSET_ISEMPTY_STATIC_PROP:
+ case ZEND_PRE_INC_STATIC_PROP:
+ case ZEND_PRE_DEC_STATIC_PROP:
+ case ZEND_POST_INC_STATIC_PROP:
+ case ZEND_POST_DEC_STATIC_PROP:
+ if (opline->op1_type == IS_CONST) {
+ // op1 static property
+ if (opline->op2_type == IS_CONST) {
+ opline->extended_value = add_static_slot(&hash, op_array,
+ opline->op2.constant,
+ opline->op1.constant,
+ LITERAL_STATIC_PROPERTY,
+ &cache_size) | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
+ } else {
+ opline->extended_value = cache_size | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
+ cache_size += 3 * sizeof(void *);
+ }
+ } else if (opline->op2_type == IS_CONST) {
+ // op2 class
+ if (class_slot[opline->op2.constant] >= 0) {
+ opline->extended_value = class_slot[opline->op2.constant] | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
+ } else {
+ opline->extended_value = cache_size | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
+ class_slot[opline->op2.constant] = cache_size;
+ cache_size += sizeof(void *);
+ }
+ }
+ break;
+ case ZEND_FETCH_CLASS:
+ case ZEND_INSTANCEOF:
+ if (opline->op2_type == IS_CONST) {
+ // op2 class
+ if (class_slot[opline->op2.constant] >= 0) {
+ opline->extended_value = class_slot[opline->op2.constant];
+ } else {
+ opline->extended_value = cache_size;
+ cache_size += sizeof(void *);
+ class_slot[opline->op2.constant] = opline->extended_value;
+ }
+ }
+ break;
+ case ZEND_NEW:
+ if (opline->op1_type == IS_CONST) {
+ // op1 class
+ if (class_slot[opline->op1.constant] >= 0) {
+ opline->op2.num = class_slot[opline->op1.constant];
+ } else {
+ opline->op2.num = cache_size;
+ cache_size += sizeof(void *);
+ class_slot[opline->op1.constant] = opline->op2.num;
+ }
+ }
+ break;
+ case ZEND_CATCH:
+ if (opline->op1_type == IS_CONST) {
+ // op1 class
+ if (class_slot[opline->op1.constant] >= 0) {
+ opline->extended_value = class_slot[opline->op1.constant] | (opline->extended_value & ZEND_LAST_CATCH);
+ } else {
+ opline->extended_value = cache_size | (opline->extended_value & ZEND_LAST_CATCH);
+ cache_size += sizeof(void *);
+ class_slot[opline->op1.constant] = opline->extended_value & ~ZEND_LAST_CATCH;
+ }
+ }
+ break;
+ case ZEND_BIND_GLOBAL:
+ // op2 bind var
+ if (bind_var_slot[opline->op2.constant] >= 0) {
+ opline->extended_value = bind_var_slot[opline->op2.constant];
+ } else {
+ opline->extended_value = cache_size;
+ cache_size += sizeof(void *);
+ bind_var_slot[opline->op2.constant] = opline->extended_value;
+ }
+ break;
+ case ZEND_DECLARE_LAMBDA_FUNCTION:
+ case ZEND_DECLARE_ANON_CLASS:
+ case ZEND_DECLARE_CLASS_DELAYED:
+ opline->extended_value = cache_size;
+ cache_size += sizeof(void *);
+ break;
+ 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_SEND_FUNC_ARG:
+ case ZEND_CHECK_FUNC_ARG:
+ if (opline->op2_type == IS_CONST) {
+ opline->result.num = cache_size;
+ cache_size += 2 * sizeof(void *);
+ }
+ break;
+ }
+ opline++;
+ }
+ op_array->cache_size = cache_size;
+ zend_hash_destroy(&hash);
+ zend_arena_release(&ctx->arena, checkpoint);
+
+ if (1) {
+ opline = op_array->opcodes;
+ while (1) {
+ if (opline->opcode == ZEND_RECV_INIT) {
+ zval *val = &op_array->literals[opline->op2.constant];
+
+ if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
+ /* Ensure zval is aligned to 8 bytes */
+ op_array->cache_size = ZEND_MM_ALIGNED_SIZE_EX(op_array->cache_size, 8);
+ Z_CACHE_SLOT_P(val) = op_array->cache_size;
+ op_array->cache_size += sizeof(zval);
+ }
+ } else if (opline->opcode != ZEND_RECV) {
+ break;
+ }
+ opline++;
+ }
+ }
+
+#if DEBUG_COMPACT_LITERALS
+ {
+ int i, use_copy;
+ fprintf(stderr, "Optimized literals table size %d\n", op_array->last_literal);
+
+ for (i = 0; i < op_array->last_literal; i++) {
+ zval zv;
+ ZVAL_COPY_VALUE(&zv, op_array->literals + i);
+ use_copy = zend_make_printable_zval(op_array->literals + i, &zv);
+ fprintf(stderr, "Literal %d, val (%zu):%s\n", i, Z_STRLEN(zv), Z_STRVAL(zv));
+ if (use_copy) {
+ zval_ptr_dtor_nogc(&zv);
+ }
+ }
+ fflush(stderr);
+ }
+#endif
+ }
+}
diff --git a/Zend/Optimizer/compact_vars.c b/Zend/Optimizer/compact_vars.c
new file mode 100644
index 0000000000..e70e189827
--- /dev/null
+++ b/Zend/Optimizer/compact_vars.c
@@ -0,0 +1,123 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, Removing unused variables |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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 "Optimizer/zend_optimizer_internal.h"
+#include "zend_bitset.h"
+
+/* This pass removes all CVs and temporaries that are completely unused. It does *not* merge any CVs or TMPs.
+ * 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_vars_len = zend_bitset_len(op_array->last_var + op_array->T);
+ zend_bitset used_vars = ZEND_BITSET_ALLOCA(used_vars_len, use_heap1);
+ uint32_t *vars_map = do_alloca((op_array->last_var + op_array->T) * sizeof(uint32_t), use_heap2);
+ uint32_t num_cvs, num_tmps;
+
+ /* Determine which CVs are used */
+ zend_bitset_clear(used_vars, used_vars_len);
+ for (i = 0; i < op_array->last; i++) {
+ zend_op *opline = &op_array->opcodes[i];
+ if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ zend_bitset_incl(used_vars, VAR_NUM(opline->op1.var));
+ }
+ if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ zend_bitset_incl(used_vars, VAR_NUM(opline->op2.var));
+ }
+ if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ zend_bitset_incl(used_vars, VAR_NUM(opline->result.var));
+ if (opline->opcode == ZEND_ROPE_INIT) {
+ uint32_t num = ((opline->extended_value * sizeof(zend_string*)) + (sizeof(zval) - 1)) / sizeof(zval);
+ while (num > 1) {
+ num--;
+ zend_bitset_incl(used_vars, VAR_NUM(opline->result.var) + num);
+ }
+ }
+ }
+ }
+
+ num_cvs = 0;
+ for (i = 0; i < op_array->last_var; i++) {
+ if (zend_bitset_in(used_vars, i)) {
+ vars_map[i] = num_cvs++;
+ } else {
+ vars_map[i] = (uint32_t) -1;
+ }
+ }
+
+ num_tmps = 0;
+ for (i = op_array->last_var; i < op_array->last_var + op_array->T; i++) {
+ if (zend_bitset_in(used_vars, i)) {
+ vars_map[i] = num_cvs + num_tmps++;
+ } else {
+ vars_map[i] = (uint32_t) -1;
+ }
+ }
+
+ free_alloca(used_vars, use_heap1);
+ if (num_cvs == op_array->last_var && num_tmps == op_array->T) {
+ free_alloca(vars_map, use_heap2);
+ return;
+ }
+
+ ZEND_ASSERT(num_cvs <= op_array->last_var);
+ ZEND_ASSERT(num_tmps <= op_array->T);
+
+ /* 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|IS_VAR|IS_TMP_VAR)) {
+ opline->op1.var = NUM_VAR(vars_map[VAR_NUM(opline->op1.var)]);
+ }
+ if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ opline->op2.var = NUM_VAR(vars_map[VAR_NUM(opline->op2.var)]);
+ }
+ if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ opline->result.var = NUM_VAR(vars_map[VAR_NUM(opline->result.var)]);
+ }
+ }
+
+ /* Update CV name table */
+ if (num_cvs != op_array->last_var) {
+ if (num_cvs) {
+ zend_string **names = safe_emalloc(sizeof(zend_string *), num_cvs, 0);
+ for (i = 0; i < op_array->last_var; i++) {
+ if (vars_map[i] != (uint32_t) -1) {
+ names[vars_map[i]] = op_array->vars[i];
+ } else {
+ zend_string_release_ex(op_array->vars[i], 0);
+ }
+ }
+ efree(op_array->vars);
+ op_array->vars = names;
+ } else {
+ for (i = 0; i < op_array->last_var; i++) {
+ zend_string_release_ex(op_array->vars[i], 0);
+ }
+ efree(op_array->vars);
+ op_array->vars = NULL;
+ }
+ op_array->last_var = num_cvs;
+ }
+
+ op_array->T = num_tmps;
+
+ free_alloca(vars_map, use_heap2);
+}
diff --git a/Zend/Optimizer/dce.c b/Zend/Optimizer/dce.c
new file mode 100644
index 0000000000..940f1b6ee2
--- /dev/null
+++ b/Zend/Optimizer/dce.c
@@ -0,0 +1,628 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, DCE - Dead Code Elimination |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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> |
+ | Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#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_effects() 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.
+ * * The algorithm is able to eliminate dead modifications of non-escaping arrays
+ * and objects as well as dead arrays and objects allocations.
+ */
+
+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 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 bool may_have_side_effects(
+ zend_op_array *op_array, zend_ssa *ssa,
+ const zend_op *opline, const zend_ssa_op *ssa_op,
+ 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_FE_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_CASE_STRICT:
+ case ZEND_CAST:
+ case ZEND_ROPE_INIT:
+ case ZEND_ROPE_ADD:
+ 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_CV:
+ case ZEND_ISSET_ISEMPTY_VAR:
+ case ZEND_FETCH_IS:
+ case ZEND_IN_ARRAY:
+ case ZEND_FUNC_NUM_ARGS:
+ case ZEND_FUNC_GET_ARGS:
+ case ZEND_ARRAY_KEY_EXISTS:
+ /* No side effects */
+ return 0;
+ case ZEND_ROPE_END:
+ /* TODO: Rope dce optimization, see #76446 */
+ return 1;
+ 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:
+ case ZEND_JMP_NULL:
+ /* 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_MATCH_ERROR:
+ case ZEND_EXT_STMT:
+ case ZEND_EXT_FCALL_BEGIN:
+ case ZEND_EXT_FCALL_END:
+ 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)
+ && ssa->vars[ssa_op->op2_use].escape_state != ESCAPE_STATE_NO_ESCAPE) {
+ /* DCE might shorten lifetime */
+ return 1;
+ }
+ }
+ return 0;
+ }
+ case ZEND_UNSET_VAR:
+ return 1;
+ case ZEND_UNSET_CV:
+ {
+ uint32_t t1 = OP1_INFO();
+ 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_OP:
+ return is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def)
+ || ssa->vars[ssa_op->op1_def].escape_state != ESCAPE_STATE_NO_ESCAPE;
+ case ZEND_ASSIGN_DIM:
+ case ZEND_ASSIGN_OBJ:
+ if (is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def)
+ || ssa->vars[ssa_op->op1_def].escape_state != ESCAPE_STATE_NO_ESCAPE) {
+ return 1;
+ }
+ if (!reorder_dtor_effects) {
+ opline++;
+ ssa_op++;
+ if (opline->op1_type != IS_CONST
+ && (OP1_INFO() & MAY_HAVE_DTOR)) {
+ /* DCE might shorten lifetime */
+ return 1;
+ }
+ }
+ return 0;
+ case ZEND_PRE_INC_OBJ:
+ case ZEND_PRE_DEC_OBJ:
+ case ZEND_POST_INC_OBJ:
+ case ZEND_POST_DEC_OBJ:
+ if (is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def)
+ || ssa->vars[ssa_op->op1_def].escape_state != ESCAPE_STATE_NO_ESCAPE) {
+ return 1;
+ }
+ return 0;
+ case ZEND_BIND_STATIC:
+ if (op_array->static_variables
+ && (opline->extended_value & ZEND_BIND_REF) != 0) {
+ zval *value =
+ (zval*)((char*)op_array->static_variables->arData +
+ (opline->extended_value & ~ZEND_BIND_REF));
+ if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
+ /* AST may contain undefined constants */
+ return 1;
+ }
+ }
+ return 0;
+ case ZEND_CHECK_VAR:
+ return (OP1_INFO() & MAY_BE_UNDEF) != 0;
+ case ZEND_FE_RESET_R:
+ case ZEND_FE_RESET_RW:
+ /* Model as not having side-effects -- let the side-effect be introduced by
+ * FE_FETCH if the array is not known to be non-empty. */
+ return (OP1_INFO() & MAY_BE_ANY) != MAY_BE_ARRAY;
+ default:
+ /* For everything we didn't handle, assume a side-effect */
+ return 1;
+ }
+}
+
+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, zend_ssa *ssa, 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)
+ || (opline->opcode == ZEND_ASSIGN
+ && (ssa->var_info[ssa_op->op1_use].type & MAY_BE_REF) != 0)) {
+ 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)
+ || (opline->opcode == ZEND_FE_FETCH_R
+ && (ssa->var_info[ssa_op->op2_use].type & MAY_BE_REF) != 0)) {
+ 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 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 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_OBJ_REF:
+ case ZEND_ASSIGN_STATIC_PROP:
+ case ZEND_ASSIGN_STATIC_PROP_REF:
+ case ZEND_ASSIGN_OP:
+ case ZEND_ASSIGN_DIM_OP:
+ case ZEND_ASSIGN_OBJ_OP:
+ case ZEND_ASSIGN_STATIC_PROP_OP:
+ case ZEND_PRE_INC:
+ case ZEND_PRE_DEC:
+ case ZEND_PRE_INC_OBJ:
+ case ZEND_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;
+}
+
+static inline bool is_free_of_live_var(context *ctx, zend_op *opline, zend_ssa_op *ssa_op) {
+ switch (opline->opcode) {
+ case ZEND_FREE:
+ /* It is always safe to remove FREEs of non-refcounted values, even if they are live. */
+ if (!(ctx->ssa->var_info[ssa_op->op1_use].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
+ return 0;
+ }
+ /* break missing intentionally */
+ case ZEND_FE_FREE:
+ return !is_var_dead(ctx, ssa_op->op1_use);
+ default:
+ return 0;
+ }
+}
+
+/* Returns whether the instruction has been DCEd */
+static 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 (is_free_of_live_var(ctx, opline, ssa_op)) {
+ 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)) {
+ if (ssa->var_info[ssa_op->op1_use].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)
+ && opline->opcode != ZEND_CASE
+ && opline->opcode != ZEND_CASE_STRICT) {
+ 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 (ssa->var_info[ssa_op->op2_use].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) {
+ 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 = EX_NUM_TO_VAR(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;
+}
+
+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 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, 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. */
+ bool has_varargs = (ssa->cfg.flags & ZEND_FUNC_VARARG) != 0;
+
+ context ctx;
+ ctx.ssa = ssa;
+ ctx.op_array = op_array;
+ ctx.reorder_dtor_effects = reorder_dtor_effects;
+
+ /* We have no dedicated phi vector, so we use the whole ssa var vector instead */
+ ctx.instr_worklist_len = zend_bitset_len(op_array->last);
+ ctx.instr_worklist = alloca(sizeof(zend_ulong) * ctx.instr_worklist_len);
+ memset(ctx.instr_worklist, 0, sizeof(zend_ulong) * ctx.instr_worklist_len);
+ ctx.phi_worklist_len = zend_bitset_len(ssa->vars_count);
+ ctx.phi_worklist = alloca(sizeof(zend_ulong) * ctx.phi_worklist_len);
+ memset(ctx.phi_worklist, 0, sizeof(zend_ulong) * ctx.phi_worklist_len);
+ 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) {
+ int op_data = -1;
+
+ 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 (op_array->opcodes[i].opcode == ZEND_OP_DATA) {
+ op_data = i;
+ continue;
+ }
+
+ 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], ssa, 0);
+ if (op_data >= 0) {
+ add_operands_to_worklists(&ctx, &op_array->opcodes[op_data], &ssa->ops[op_data], ssa, 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], &ssa->ops[i], op_array, ssa)
+ || (has_varargs && may_break_varargs(op_array, ssa, &ssa->ops[i]))) {
+ if (op_array->opcodes[i].opcode == ZEND_NEW
+ && op_array->opcodes[i+1].opcode == ZEND_DO_FCALL
+ && ssa->ops[i].result_def >= 0
+ && ssa->vars[ssa->ops[i].result_def].escape_state == ESCAPE_STATE_NO_ESCAPE) {
+ zend_bitset_incl(ctx.instr_dead, i);
+ zend_bitset_incl(ctx.instr_dead, i+1);
+ } else {
+ add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], ssa, 0);
+ if (op_data >= 0) {
+ add_operands_to_worklists(&ctx, &op_array->opcodes[op_data], &ssa->ops[op_data], ssa, 0);
+ }
+ }
+ } else {
+ zend_bitset_incl(ctx.instr_dead, i);
+ if (op_data >= 0) {
+ zend_bitset_incl(ctx.instr_dead, op_data);
+ }
+ }
+ op_data = -1;
+ }
+ }
+
+ /* 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], ssa, 1);
+ if (i < op_array->last && op_array->opcodes[i+1].opcode == ZEND_OP_DATA) {
+ zend_bitset_excl(ctx.instr_dead, i+1);
+ add_operands_to_worklists(&ctx, &op_array->opcodes[i+1], &ssa->ops[i+1], ssa, 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();
+
+ return removed_ops;
+}
diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c
new file mode 100644
index 0000000000..fe06de276b
--- /dev/null
+++ b/Zend/Optimizer/dfa_pass.c
@@ -0,0 +1,1637 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend OPcache |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#include "php.h"
+#include "Optimizer/zend_optimizer.h"
+#include "Optimizer/zend_optimizer_internal.h"
+#include "zend_API.h"
+#include "zend_constants.h"
+#include "zend_execute.h"
+#include "zend_vm.h"
+#include "zend_bitset.h"
+#include "zend_cfg.h"
+#include "zend_ssa.h"
+#include "zend_func_info.h"
+#include "zend_call_graph.h"
+#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 build_flags;
+
+ if (op_array->last_try_catch) {
+ /* TODO: we can't analyze functions with try/catch/finally ??? */
+ return FAILURE;
+ }
+
+ /* Build SSA */
+ memset(ssa, 0, sizeof(zend_ssa));
+
+ if (zend_build_cfg(&ctx->arena, op_array, ZEND_CFG_NO_ENTRY_PREDECESSORS, &ssa->cfg) != SUCCESS) {
+ return FAILURE;
+ }
+
+ if ((ssa->cfg.flags & ZEND_FUNC_INDIRECT_VAR_ACCESS)) {
+ /* TODO: we can't analyze functions with indirect variable access ??? */
+ return FAILURE;
+ }
+
+ if (zend_cfg_build_predecessors(&ctx->arena, &ssa->cfg) != SUCCESS) {
+ return FAILURE;
+ }
+
+ if (ctx->debug_level & ZEND_DUMP_DFA_CFG) {
+ zend_dump_op_array(op_array, ZEND_DUMP_CFG, "dfa cfg", &ssa->cfg);
+ }
+
+ /* Compute Dominators Tree */
+ if (zend_cfg_compute_dominators_tree(op_array, &ssa->cfg) != SUCCESS) {
+ return FAILURE;
+ }
+
+ /* Identify reducible and irreducible loops */
+ if (zend_cfg_identify_loops(op_array, &ssa->cfg) != SUCCESS) {
+ return FAILURE;
+ }
+
+ if (ctx->debug_level & ZEND_DUMP_DFA_DOMINATORS) {
+ zend_dump_dominators(op_array, &ssa->cfg);
+ }
+
+ build_flags = 0;
+ if (ctx->debug_level & ZEND_DUMP_DFA_LIVENESS) {
+ build_flags |= ZEND_SSA_DEBUG_LIVENESS;
+ }
+ if (ctx->debug_level & ZEND_DUMP_DFA_PHI) {
+ build_flags |= ZEND_SSA_DEBUG_PHI_PLACEMENT;
+ }
+ if (zend_build_ssa(&ctx->arena, ctx->script, op_array, build_flags, ssa) != SUCCESS) {
+ return FAILURE;
+ }
+
+ if (ctx->debug_level & ZEND_DUMP_DFA_SSA) {
+ zend_dump_op_array(op_array, ZEND_DUMP_SSA, "dfa ssa", ssa);
+ }
+
+
+ if (zend_ssa_compute_use_def_chains(&ctx->arena, op_array, ssa) != SUCCESS){
+ return FAILURE;
+ }
+
+ if (zend_ssa_find_false_dependencies(op_array, ssa) != SUCCESS) {
+ return FAILURE;
+ }
+
+ if (zend_ssa_find_sccs(op_array, ssa) != SUCCESS){
+ return FAILURE;
+ }
+
+ if (zend_ssa_inference(&ctx->arena, op_array, ctx->script, ssa, ctx->optimization_level) != SUCCESS) {
+ return FAILURE;
+ }
+
+ if (zend_ssa_escape_analysis(ctx->script, op_array, ssa) != SUCCESS) {
+ return FAILURE;
+ }
+
+ if (ctx->debug_level & ZEND_DUMP_DFA_SSA_VARS) {
+ zend_dump_ssa_variables(op_array, ssa, 0);
+ }
+
+ return SUCCESS;
+}
+
+static void zend_ssa_remove_nops(zend_op_array *op_array, zend_ssa *ssa, zend_optimizer_ctx *ctx)
+{
+ zend_basic_block *blocks = ssa->cfg.blocks;
+ zend_basic_block *blocks_end = blocks + ssa->cfg.blocks_count;
+ zend_basic_block *b;
+ zend_func_info *func_info;
+ int j;
+ uint32_t i = 0;
+ uint32_t target = 0;
+ uint32_t *shiftlist;
+ ALLOCA_FLAG(use_heap);
+
+ shiftlist = (uint32_t *)do_alloca(sizeof(uint32_t) * op_array->last, use_heap);
+ memset(shiftlist, 0, sizeof(uint32_t) * op_array->last);
+ /* remove empty callee_info */
+ func_info = ZEND_FUNC_INFO(op_array);
+ if (func_info) {
+ zend_call_info **call_info = &func_info->callee_info;
+ while ((*call_info)) {
+ if ((*call_info)->caller_init_opline->opcode == ZEND_NOP) {
+ *call_info = (*call_info)->next_callee;
+ } else {
+ call_info = &(*call_info)->next_callee;
+ }
+ }
+ }
+
+ for (b = blocks; b < blocks_end; b++) {
+ if (b->flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE)) {
+ if (b->len) {
+ uint32_t new_start, old_end;
+ while (i < b->start) {
+ shiftlist[i] = i - target;
+ i++;
+ }
+
+ if (b->flags & ZEND_BB_UNREACHABLE_FREE) {
+ /* Only keep the FREE for the loop var */
+ ZEND_ASSERT(op_array->opcodes[b->start].opcode == ZEND_FREE
+ || op_array->opcodes[b->start].opcode == ZEND_FE_FREE);
+ b->len = 1;
+ }
+
+ new_start = target;
+ old_end = b->start + b->len;
+ while (i < old_end) {
+ shiftlist[i] = i - target;
+ if (EXPECTED(op_array->opcodes[i].opcode != ZEND_NOP)) {
+ if (i != target) {
+ op_array->opcodes[target] = op_array->opcodes[i];
+ ssa->ops[target] = ssa->ops[i];
+ ssa->cfg.map[target] = b - blocks;
+ }
+ target++;
+ }
+ i++;
+ }
+ b->start = new_start;
+ if (target != old_end) {
+ zend_op *opline;
+ zend_op *new_opline;
+
+ b->len = target - b->start;
+ opline = op_array->opcodes + old_end - 1;
+ if (opline->opcode == ZEND_NOP) {
+ continue;
+ }
+
+ new_opline = op_array->opcodes + target - 1;
+ zend_optimizer_migrate_jump(op_array, new_opline, opline);
+ }
+ } else {
+ b->start = target;
+ }
+ } else {
+ b->start = target;
+ b->len = 0;
+ }
+ }
+
+ if (target != op_array->last) {
+ /* reset rest opcodes */
+ for (i = target; i < op_array->last; i++) {
+ MAKE_NOP(op_array->opcodes + i);
+ }
+
+ /* update SSA variables */
+ for (j = 0; j < ssa->vars_count; j++) {
+ if (ssa->vars[j].definition >= 0) {
+ ssa->vars[j].definition -= shiftlist[ssa->vars[j].definition];
+ }
+ if (ssa->vars[j].use_chain >= 0) {
+ ssa->vars[j].use_chain -= shiftlist[ssa->vars[j].use_chain];
+ }
+ }
+ for (i = 0; i < op_array->last; i++) {
+ if (ssa->ops[i].op1_use_chain >= 0) {
+ ssa->ops[i].op1_use_chain -= shiftlist[ssa->ops[i].op1_use_chain];
+ }
+ if (ssa->ops[i].op2_use_chain >= 0) {
+ ssa->ops[i].op2_use_chain -= shiftlist[ssa->ops[i].op2_use_chain];
+ }
+ if (ssa->ops[i].res_use_chain >= 0) {
+ ssa->ops[i].res_use_chain -= shiftlist[ssa->ops[i].res_use_chain];
+ }
+ }
+
+ /* update branch targets */
+ for (b = blocks; b < blocks_end; b++) {
+ if ((b->flags & ZEND_BB_REACHABLE) && b->len != 0) {
+ zend_op *opline = op_array->opcodes + b->start + b->len - 1;
+ zend_optimizer_shift_jump(op_array, opline, shiftlist);
+ }
+ }
+
+ /* update try/catch array */
+ for (j = 0; j < op_array->last_try_catch; j++) {
+ op_array->try_catch_array[j].try_op -= shiftlist[op_array->try_catch_array[j].try_op];
+ op_array->try_catch_array[j].catch_op -= shiftlist[op_array->try_catch_array[j].catch_op];
+ if (op_array->try_catch_array[j].finally_op) {
+ op_array->try_catch_array[j].finally_op -= shiftlist[op_array->try_catch_array[j].finally_op];
+ op_array->try_catch_array[j].finally_end -= shiftlist[op_array->try_catch_array[j].finally_end];
+ }
+ }
+
+ /* update early binding list */
+ if (op_array->fn_flags & ZEND_ACC_EARLY_BINDING) {
+ uint32_t *opline_num = &ctx->script->first_early_binding_opline;
+
+ ZEND_ASSERT(op_array == &ctx->script->main_op_array);
+ do {
+ *opline_num -= shiftlist[*opline_num];
+ opline_num = &op_array->opcodes[*opline_num].result.opline_num;
+ } while (*opline_num != (uint32_t)-1);
+ }
+
+ /* update call graph */
+ if (func_info) {
+ zend_call_info *call_info = func_info->callee_info;
+ while (call_info) {
+ call_info->caller_init_opline -=
+ shiftlist[call_info->caller_init_opline - op_array->opcodes];
+ if (call_info->caller_call_opline) {
+ call_info->caller_call_opline -=
+ shiftlist[call_info->caller_call_opline - op_array->opcodes];
+ }
+ call_info = call_info->next_callee;
+ }
+ }
+
+ op_array->last = target;
+ }
+ free_alloca(shiftlist, use_heap);
+}
+
+static bool safe_instanceof(zend_class_entry *ce1, zend_class_entry *ce2) {
+ if (ce1 == ce2) {
+ return 1;
+ }
+ if (!(ce1->ce_flags & ZEND_ACC_LINKED)) {
+ /* This case could be generalized, similarly to unlinked_instanceof */
+ return 0;
+ }
+ return instanceof_function(ce1, ce2);
+}
+
+static inline bool can_elide_return_type_check(
+ zend_op_array *op_array, zend_ssa *ssa, zend_ssa_op *ssa_op) {
+ zend_arg_info *info = &op_array->arg_info[-1];
+ zend_ssa_var_info *use_info = &ssa->var_info[ssa_op->op1_use];
+ zend_ssa_var_info *def_info = &ssa->var_info[ssa_op->op1_def];
+
+ /* TODO: It would be better to rewrite this without using def_info,
+ * which may not be an exact representation of the type. */
+ if (use_info->type & MAY_BE_REF) {
+ return 0;
+ }
+
+ /* A type is possible that is not in the allowed types */
+ if ((use_info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) & ~(def_info->type & MAY_BE_ANY)) {
+ return 0;
+ }
+
+ /* These types are not represented exactly */
+ if (ZEND_TYPE_FULL_MASK(info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE|MAY_BE_STATIC)) {
+ return 0;
+ }
+
+ if (ZEND_TYPE_HAS_CLASS(info->type)) {
+ if (!use_info->ce || !def_info->ce || !safe_instanceof(use_info->ce, def_info->ce)) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static bool opline_supports_assign_contraction(
+ zend_ssa *ssa, zend_op *opline, int src_var, uint32_t cv_var) {
+ if (opline->opcode == ZEND_NEW) {
+ /* see Zend/tests/generators/aborted_yield_during_new.phpt */
+ return 0;
+ }
+
+ if (opline->opcode == ZEND_DO_ICALL || opline->opcode == ZEND_DO_UCALL
+ || opline->opcode == ZEND_DO_FCALL || opline->opcode == ZEND_DO_FCALL_BY_NAME) {
+ /* Function calls may dtor the return value after it has already been written -- allow
+ * direct assignment only for types where a double-dtor does not matter. */
+ uint32_t type = ssa->var_info[src_var].type;
+ uint32_t simple = MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE;
+ return !((type & MAY_BE_ANY) & ~simple);
+ }
+
+ if (opline->opcode == ZEND_POST_INC || opline->opcode == ZEND_POST_DEC) {
+ /* POST_INC/DEC write the result variable before performing the inc/dec. For $i = $i++
+ * eliding the temporary variable would thus yield an incorrect result. */
+ return opline->op1_type != IS_CV || opline->op1.var != cv_var;
+ }
+
+ if (opline->opcode == ZEND_INIT_ARRAY) {
+ /* INIT_ARRAY initializes the result array before reading key/value. */
+ return (opline->op1_type != IS_CV || opline->op1.var != cv_var)
+ && (opline->op2_type != IS_CV || opline->op2.var != cv_var);
+ }
+
+ if (opline->opcode == ZEND_CAST
+ && (opline->extended_value == IS_ARRAY || opline->extended_value == IS_OBJECT)) {
+ /* CAST to array/object may initialize the result to an empty array/object before
+ * reading the expression. */
+ return opline->op1_type != IS_CV || opline->op1.var != cv_var;
+ }
+
+ return 1;
+}
+
+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
+ && call_info->caller_call_opline->opcode == ZEND_DO_ICALL
+ && call_info->callee_func
+ && zend_string_equals_literal(call_info->callee_func->common.function_name, "in_array")
+ && (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;
+ 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 = zend_new_array(zend_hash_num_elements(src));
+ 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++;
+
+ op_num = call_info->caller_call_opline - op_array->opcodes;
+ ssa_op = ssa->ops + op_num;
+ if (ssa_op->result_def >= 0) {
+ int var = ssa_op->result_def;
+ int use = ssa->vars[var].use_chain;
+
+ /* If the result is used only in a JMPZ/JMPNZ, replace result type with
+ * IS_TMP_VAR, which will enable use of smart branches. Don't do this
+ * in other cases, as not all opcodes support both VAR and TMP. */
+ if (ssa->vars[var].phi_use_chain == NULL
+ && ssa->ops[use].op1_use == var
+ && ssa->ops[use].op1_use_chain == -1
+ && (op_array->opcodes[use].opcode == ZEND_JMPZ
+ || op_array->opcodes[use].opcode == ZEND_JMPNZ)) {
+ call_info->caller_call_opline->result_type = IS_TMP_VAR;
+ op_array->opcodes[use].op1_type = IS_TMP_VAR;
+ }
+ }
+ }
+ }
+ }
+ call_info = call_info->next_callee;
+ } while (call_info);
+ }
+
+ return removed_ops;
+}
+
+static zend_always_inline void take_successor_0(zend_ssa *ssa, int block_num, zend_basic_block *block)
+{
+ if (block->successors_count == 2) {
+ if (block->successors[1] != block->successors[0]) {
+ zend_ssa_remove_predecessor(ssa, block_num, block->successors[1]);
+ }
+ block->successors_count = 1;
+ }
+}
+
+static zend_always_inline void take_successor_1(zend_ssa *ssa, int block_num, zend_basic_block *block)
+{
+ if (block->successors_count == 2) {
+ if (block->successors[1] != block->successors[0]) {
+ zend_ssa_remove_predecessor(ssa, block_num, block->successors[0]);
+ block->successors[0] = block->successors[1];
+ }
+ block->successors_count = 1;
+ }
+}
+
+static zend_always_inline void take_successor_ex(zend_ssa *ssa, int block_num, zend_basic_block *block, int target_block)
+{
+ int i;
+
+ for (i = 0; i < block->successors_count; i++) {
+ if (block->successors[i] != target_block) {
+ zend_ssa_remove_predecessor(ssa, block_num, block->successors[i]);
+ }
+ }
+ block->successors[0] = target_block;
+ block->successors_count = 1;
+}
+
+static void compress_block(zend_op_array *op_array, zend_basic_block *block)
+{
+ while (block->len > 0) {
+ zend_op *opline = &op_array->opcodes[block->start + block->len - 1];
+
+ if (opline->opcode == ZEND_NOP) {
+ block->len--;
+ } else {
+ break;
+ }
+ }
+}
+
+static void replace_predecessor(zend_ssa *ssa, int block_id, int old_pred, int new_pred) {
+ zend_basic_block *block = &ssa->cfg.blocks[block_id];
+ int *predecessors = &ssa->cfg.predecessors[block->predecessor_offset];
+ zend_ssa_phi *phi;
+
+ int i;
+ int old_pred_idx = -1;
+ int new_pred_idx = -1;
+ for (i = 0; i < block->predecessors_count; i++) {
+ if (predecessors[i] == old_pred) {
+ old_pred_idx = i;
+ }
+ if (predecessors[i] == new_pred) {
+ new_pred_idx = i;
+ }
+ }
+
+ ZEND_ASSERT(old_pred_idx != -1);
+ if (new_pred_idx == -1) {
+ /* If the new predecessor doesn't exist yet, simply rewire the old one */
+ predecessors[old_pred_idx] = new_pred;
+ } else {
+ /* Otherwise, rewiring the old predecessor would make the new predecessor appear
+ * twice, which violates our CFG invariants. Remove the old predecessor instead. */
+ memmove(
+ predecessors + old_pred_idx,
+ predecessors + old_pred_idx + 1,
+ sizeof(int) * (block->predecessors_count - old_pred_idx - 1)
+ );
+
+ /* Also remove the corresponding phi node entries */
+ for (phi = ssa->blocks[block_id].phis; phi; phi = phi->next) {
+ memmove(
+ phi->sources + old_pred_idx,
+ phi->sources + old_pred_idx + 1,
+ sizeof(int) * (block->predecessors_count - old_pred_idx - 1)
+ );
+ }
+
+ block->predecessors_count--;
+ }
+}
+
+static void zend_ssa_replace_control_link(zend_op_array *op_array, zend_ssa *ssa, int from, int to, int new_to)
+{
+ zend_basic_block *src = &ssa->cfg.blocks[from];
+ zend_basic_block *old = &ssa->cfg.blocks[to];
+ zend_basic_block *dst = &ssa->cfg.blocks[new_to];
+ int i;
+ zend_op *opline;
+
+ for (i = 0; i < src->successors_count; i++) {
+ if (src->successors[i] == to) {
+ src->successors[i] = new_to;
+ }
+ }
+
+ if (src->len > 0) {
+ opline = op_array->opcodes + src->start + src->len - 1;
+ switch (opline->opcode) {
+ case ZEND_JMP:
+ case ZEND_FAST_CALL:
+ ZEND_ASSERT(ZEND_OP1_JMP_ADDR(opline) == op_array->opcodes + old->start);
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op1, op_array->opcodes + dst->start);
+ break;
+ case ZEND_JMPZNZ:
+ if (ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value) == old->start) {
+ opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, dst->start);
+ }
+ /* break missing intentionally */
+ case ZEND_JMPZ:
+ case ZEND_JMPNZ:
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ case ZEND_FE_RESET_R:
+ case ZEND_FE_RESET_RW:
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ case ZEND_ASSERT_CHECK:
+ case ZEND_JMP_NULL:
+ if (ZEND_OP2_JMP_ADDR(opline) == op_array->opcodes + old->start) {
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op2, op_array->opcodes + dst->start);
+ }
+ break;
+ case ZEND_CATCH:
+ if (!(opline->extended_value & ZEND_LAST_CATCH)) {
+ if (ZEND_OP2_JMP_ADDR(opline) == op_array->opcodes + old->start) {
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op2, op_array->opcodes + dst->start);
+ }
+ }
+ break;
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ if (ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value) == old->start) {
+ opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, dst->start);
+ }
+ break;
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ case ZEND_MATCH:
+ {
+ HashTable *jumptable = Z_ARRVAL(ZEND_OP2_LITERAL(opline));
+ zval *zv;
+ ZEND_HASH_FOREACH_VAL(jumptable, zv) {
+ if (ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv)) == old->start) {
+ Z_LVAL_P(zv) = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, dst->start);
+ }
+ } ZEND_HASH_FOREACH_END();
+ if (ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value) == old->start) {
+ opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, dst->start);
+ }
+ break;
+ }
+ }
+ }
+
+ replace_predecessor(ssa, new_to, to, from);
+}
+
+static void zend_ssa_unlink_block(zend_op_array *op_array, zend_ssa *ssa, zend_basic_block *block, int block_num)
+{
+ if (block->predecessors_count == 1 && ssa->blocks[block_num].phis == NULL) {
+ int *predecessors, i;
+
+ ZEND_ASSERT(block->successors_count == 1);
+ predecessors = &ssa->cfg.predecessors[block->predecessor_offset];
+ for (i = 0; i < block->predecessors_count; i++) {
+ zend_ssa_replace_control_link(op_array, ssa, predecessors[i], block_num, block->successors[0]);
+ }
+ zend_ssa_remove_block(op_array, ssa, block_num);
+ }
+}
+
+static int zend_dfa_optimize_jmps(zend_op_array *op_array, zend_ssa *ssa)
+{
+ int removed_ops = 0;
+ int block_num = 0;
+
+ for (block_num = 1; block_num < ssa->cfg.blocks_count; block_num++) {
+ zend_basic_block *block = &ssa->cfg.blocks[block_num];
+
+ if (!(block->flags & ZEND_BB_REACHABLE)) {
+ continue;
+ }
+ compress_block(op_array, block);
+ if (block->len == 0) {
+ zend_ssa_unlink_block(op_array, ssa, block, block_num);
+ }
+ }
+
+ block_num = 0;
+ while (block_num < ssa->cfg.blocks_count
+ && !(ssa->cfg.blocks[block_num].flags & ZEND_BB_REACHABLE)) {
+ block_num++;
+ }
+ while (block_num < ssa->cfg.blocks_count) {
+ int next_block_num = block_num + 1;
+ zend_basic_block *block = &ssa->cfg.blocks[block_num];
+ uint32_t op_num;
+ zend_op *opline;
+ zend_ssa_op *ssa_op;
+
+ while (next_block_num < ssa->cfg.blocks_count
+ && !(ssa->cfg.blocks[next_block_num].flags & ZEND_BB_REACHABLE)) {
+ next_block_num++;
+ }
+
+ if (block->len) {
+ op_num = block->start + block->len - 1;
+ opline = op_array->opcodes + op_num;
+ ssa_op = ssa->ops + op_num;
+
+ switch (opline->opcode) {
+ case ZEND_JMP:
+optimize_jmp:
+ if (block->successors[0] == next_block_num) {
+ MAKE_NOP(opline);
+ removed_ops++;
+ goto optimize_nop;
+ }
+ break;
+ case ZEND_JMPZ:
+optimize_jmpz:
+ if (opline->op1_type == IS_CONST) {
+ if (zend_is_true(CT_CONSTANT_EX(op_array, opline->op1.constant))) {
+ MAKE_NOP(opline);
+ removed_ops++;
+ take_successor_1(ssa, block_num, block);
+ goto optimize_nop;
+ } else {
+ opline->opcode = ZEND_JMP;
+ COPY_NODE(opline->op1, opline->op2);
+ take_successor_0(ssa, block_num, block);
+ goto optimize_jmp;
+ }
+ } else {
+ if (block->successors[0] == next_block_num) {
+ take_successor_0(ssa, block_num, block);
+ if (opline->op1_type == IS_CV && (OP1_INFO() & MAY_BE_UNDEF)) {
+ opline->opcode = ZEND_CHECK_VAR;
+ opline->op2.num = 0;
+ } else if (opline->op1_type == IS_CV || !(OP1_INFO() & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
+ zend_ssa_remove_instr(ssa, opline, ssa_op);
+ removed_ops++;
+ goto optimize_nop;
+ } else {
+ opline->opcode = ZEND_FREE;
+ opline->op2.num = 0;
+ }
+ }
+ }
+ break;
+ case ZEND_JMPNZ:
+optimize_jmpnz:
+ if (opline->op1_type == IS_CONST) {
+ if (zend_is_true(CT_CONSTANT_EX(op_array, opline->op1.constant))) {
+ opline->opcode = ZEND_JMP;
+ COPY_NODE(opline->op1, opline->op2);
+ take_successor_0(ssa, block_num, block);
+ goto optimize_jmp;
+ } else {
+ MAKE_NOP(opline);
+ removed_ops++;
+ take_successor_1(ssa, block_num, block);
+ goto optimize_nop;
+ }
+ } else if (block->successors_count == 2) {
+ if (block->successors[0] == next_block_num) {
+ take_successor_0(ssa, block_num, block);
+ if (opline->op1_type == IS_CV && (OP1_INFO() & MAY_BE_UNDEF)) {
+ opline->opcode = ZEND_CHECK_VAR;
+ opline->op2.num = 0;
+ } else if (opline->op1_type == IS_CV || !(OP1_INFO() & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
+ zend_ssa_remove_instr(ssa, opline, ssa_op);
+ removed_ops++;
+ goto optimize_nop;
+ } else {
+ opline->opcode = ZEND_FREE;
+ opline->op2.num = 0;
+ }
+ }
+ }
+ break;
+ case ZEND_JMPZNZ:
+ if (opline->op1_type == IS_CONST) {
+ if (zend_is_true(CT_CONSTANT_EX(op_array, opline->op1.constant))) {
+ zend_op *target_opline = ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value);
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target_opline);
+ take_successor_1(ssa, block_num, block);
+ } else {
+ zend_op *target_opline = ZEND_OP2_JMP_ADDR(opline);
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target_opline);
+ take_successor_0(ssa, block_num, block);
+ }
+ opline->op1_type = IS_UNUSED;
+ opline->extended_value = 0;
+ opline->opcode = ZEND_JMP;
+ goto optimize_jmp;
+ } else if (block->successors_count == 2) {
+ if (block->successors[0] == block->successors[1]) {
+ take_successor_0(ssa, block_num, block);
+ if (block->successors[0] == next_block_num) {
+ if (opline->op1_type == IS_CV && (OP1_INFO() & MAY_BE_UNDEF)) {
+ opline->opcode = ZEND_CHECK_VAR;
+ opline->op2.num = 0;
+ } else if (opline->op1_type == IS_CV || !(OP1_INFO() & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
+ zend_ssa_remove_instr(ssa, opline, ssa_op);
+ removed_ops++;
+ goto optimize_nop;
+ } else {
+ opline->opcode = ZEND_FREE;
+ opline->op2.num = 0;
+ }
+ } else if ((opline->op1_type == IS_CV && !(OP1_INFO() & MAY_BE_UNDEF)) || !(OP1_INFO() & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
+ ZEND_ASSERT(ssa_op->op1_use >= 0);
+ zend_ssa_unlink_use_chain(ssa, op_num, ssa_op->op1_use);
+ ssa_op->op1_use = -1;
+ ssa_op->op1_use_chain = -1;
+ opline->opcode = ZEND_JMP;
+ opline->op1_type = IS_UNUSED;
+ opline->op1.num = opline->op2.num;
+ goto optimize_jmp;
+ }
+ }
+ }
+ break;
+ case ZEND_JMPZ_EX:
+ 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);
+ goto optimize_jmpz;
+ } else if (opline->op1_type == IS_CONST) {
+ if (zend_is_true(CT_CONSTANT_EX(op_array, opline->op1.constant))) {
+ opline->opcode = ZEND_QM_ASSIGN;
+ take_successor_1(ssa, block_num, block);
+ }
+ }
+ break;
+ case ZEND_JMPNZ_EX:
+ 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);
+ goto optimize_jmpnz;
+ } else if (opline->op1_type == IS_CONST) {
+ if (!zend_is_true(CT_CONSTANT_EX(op_array, opline->op1.constant))) {
+ opline->opcode = ZEND_QM_ASSIGN;
+ take_successor_1(ssa, block_num, block);
+ }
+ }
+ break;
+ case ZEND_JMP_SET:
+ 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);
+ goto optimize_jmpnz;
+ } else if (opline->op1_type == IS_CONST) {
+ if (!zend_is_true(CT_CONSTANT_EX(op_array, opline->op1.constant))) {
+ MAKE_NOP(opline);
+ removed_ops++;
+ take_successor_1(ssa, block_num, block);
+ zend_ssa_remove_result_def(ssa, ssa_op);
+ goto optimize_nop;
+ }
+ }
+ break;
+ case ZEND_COALESCE:
+ {
+ zend_ssa_var *var = &ssa->vars[ssa_op->result_def];
+ if (opline->op1_type == IS_CONST
+ && var->use_chain < 0 && var->phi_use_chain == NULL) {
+ if (Z_TYPE_P(CT_CONSTANT_EX(op_array, opline->op1.constant)) == IS_NULL) {
+ zend_ssa_remove_result_def(ssa, ssa_op);
+ MAKE_NOP(opline);
+ removed_ops++;
+ take_successor_1(ssa, block_num, block);
+ goto optimize_nop;
+ } else {
+ opline->opcode = ZEND_JMP;
+ opline->result_type = IS_UNUSED;
+ zend_ssa_remove_result_def(ssa, ssa_op);
+ COPY_NODE(opline->op1, opline->op2);
+ take_successor_0(ssa, block_num, block);
+ goto optimize_jmp;
+ }
+ }
+ break;
+ }
+ case ZEND_JMP_NULL:
+ {
+ zend_ssa_var *var = &ssa->vars[ssa_op->result_def];
+ if (opline->op1_type == IS_CONST
+ && var->use_chain < 0 && var->phi_use_chain == NULL) {
+ if (Z_TYPE_P(CT_CONSTANT_EX(op_array, opline->op1.constant)) == IS_NULL) {
+ opline->opcode = ZEND_JMP;
+ opline->result_type = IS_UNUSED;
+ zend_ssa_remove_result_def(ssa, ssa_op);
+ COPY_NODE(opline->op1, opline->op2);
+ take_successor_0(ssa, block_num, block);
+ goto optimize_jmp;
+ } else {
+ zend_ssa_remove_result_def(ssa, ssa_op);
+ MAKE_NOP(opline);
+ removed_ops++;
+ take_successor_1(ssa, block_num, block);
+ goto optimize_nop;
+ }
+ }
+ break;
+ }
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ case ZEND_MATCH:
+ if (opline->op1_type == IS_CONST) {
+ zval *zv = CT_CONSTANT_EX(op_array, opline->op1.constant);
+ zend_uchar type = Z_TYPE_P(zv);
+ bool correct_type =
+ (opline->opcode == ZEND_SWITCH_LONG && type == IS_LONG)
+ || (opline->opcode == ZEND_SWITCH_STRING && type == IS_STRING)
+ || (opline->opcode == ZEND_MATCH && (type == IS_LONG || type == IS_STRING));
+
+ if (!correct_type) {
+ removed_ops++;
+ MAKE_NOP(opline);
+ opline->extended_value = 0;
+ take_successor_ex(ssa, block_num, block, block->successors[block->successors_count - 1]);
+ goto optimize_nop;
+ } else {
+ HashTable *jmptable = Z_ARRVAL_P(CT_CONSTANT_EX(op_array, opline->op2.constant));
+ zval *jmp_zv = type == IS_LONG
+ ? zend_hash_index_find(jmptable, Z_LVAL_P(zv))
+ : zend_hash_find(jmptable, Z_STR_P(zv));
+
+ uint32_t target;
+ if (jmp_zv) {
+ target = ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(jmp_zv));
+ } else {
+ target = ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value);
+ }
+ opline->opcode = ZEND_JMP;
+ opline->extended_value = 0;
+ SET_UNUSED(opline->op1);
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op1, op_array->opcodes + target);
+ SET_UNUSED(opline->op2);
+ take_successor_ex(ssa, block_num, block, ssa->cfg.map[target]);
+ goto optimize_jmp;
+ }
+ }
+ break;
+ case ZEND_NOP:
+optimize_nop:
+ compress_block(op_array, block);
+ if (block->len == 0) {
+ if (block_num > 0) {
+ zend_ssa_unlink_block(op_array, ssa, block, block_num);
+ /* backtrack to previous basic block */
+ do {
+ block_num--;
+ } while (block_num >= 0
+ && !(ssa->cfg.blocks[block_num].flags & ZEND_BB_REACHABLE));
+ if (block_num >= 0) {
+ continue;
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ block_num = next_block_num;
+ }
+
+ return removed_ops;
+}
+
+static int zend_dfa_try_to_replace_result(zend_op_array *op_array, zend_ssa *ssa, int def, int cv_var)
+{
+ int result_var = ssa->ops[def].result_def;
+ int cv = EX_NUM_TO_VAR(ssa->vars[cv_var].var);
+
+ if (result_var >= 0
+ && !(ssa->var_info[cv_var].type & MAY_BE_REF)
+ && ssa->vars[cv_var].alias == NO_ALIAS
+ && ssa->vars[result_var].phi_use_chain == NULL
+ && ssa->vars[result_var].sym_use_chain == NULL) {
+ int use = ssa->vars[result_var].use_chain;
+
+ if (use >= 0
+ && zend_ssa_next_use(ssa->ops, result_var, use) < 0
+ && op_array->opcodes[use].opcode != ZEND_FREE
+ && op_array->opcodes[use].opcode != ZEND_SEND_VAL
+ && op_array->opcodes[use].opcode != ZEND_SEND_VAL_EX
+ && op_array->opcodes[use].opcode != ZEND_VERIFY_RETURN_TYPE) {
+ if (use > def) {
+ int i = use;
+ const zend_op *opline = &op_array->opcodes[use];
+
+ while (i > def) {
+ if ((opline->op1_type == IS_CV && opline->op1.var == cv)
+ || (opline->op2_type == IS_CV && opline->op2.var == cv)
+ || (opline->result_type == IS_CV && opline->result.var == cv)) {
+ return 0;
+ }
+ opline--;
+ i--;
+ }
+
+ /* Update opcodes and reconstruct SSA */
+ ssa->vars[result_var].definition = -1;
+ ssa->vars[result_var].use_chain = -1;
+ ssa->ops[def].result_def = -1;
+
+ op_array->opcodes[def].result_type = IS_UNUSED;
+ op_array->opcodes[def].result.var = 0;
+
+ if (ssa->ops[use].op1_use == result_var) {
+ ssa->ops[use].op1_use = cv_var;
+ ssa->ops[use].op1_use_chain = ssa->vars[cv_var].use_chain;
+ ssa->vars[cv_var].use_chain = use;
+
+ op_array->opcodes[use].op1_type = IS_CV;
+ op_array->opcodes[use].op1.var = cv;
+ } else if (ssa->ops[use].op2_use == result_var) {
+ ssa->ops[use].op2_use = cv_var;
+ ssa->ops[use].op2_use_chain = ssa->vars[cv_var].use_chain;
+ ssa->vars[cv_var].use_chain = use;
+
+ op_array->opcodes[use].op2_type = IS_CV;
+ op_array->opcodes[use].op2.var = cv;
+ } else if (ssa->ops[use].result_use == result_var) {
+ ssa->ops[use].result_use = cv_var;
+ ssa->ops[use].res_use_chain = ssa->vars[cv_var].use_chain;
+ ssa->vars[cv_var].use_chain = use;
+
+ op_array->opcodes[use].result_type = IS_CV;
+ op_array->opcodes[use].result.var = cv;
+ }
+
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+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);
+ }
+
+ if (ssa->var_info) {
+ int op_1;
+ int v;
+ int remove_nops = 0;
+ zend_op *opline;
+ zend_ssa_op *ssa_op;
+ zval tmp;
+
+#if ZEND_DEBUG_DFA
+ ssa_verify_integrity(op_array, ssa, "before dfa");
+#endif
+
+ if (ZEND_OPTIMIZER_PASS_8 & ctx->optimization_level) {
+ if (sccp_optimize_op_array(ctx, op_array, ssa, call_map)) {
+ remove_nops = 1;
+ }
+
+ if (zend_dfa_optimize_jmps(op_array, ssa)) {
+ 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 (zend_dfa_optimize_jmps(op_array, ssa)) {
+ 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;
+
+ if (op_1 < 0) {
+ continue;
+ }
+
+ opline = op_array->opcodes + op_1;
+ ssa_op = &ssa->ops[op_1];
+
+ /* Convert LONG constants to DOUBLE */
+ if (ssa->var_info[v].use_as_double) {
+ if (opline->opcode == ZEND_ASSIGN
+ && opline->op2_type == IS_CONST
+ && ssa->ops[op_1].op1_def == v
+ && !RETURN_VALUE_USED(opline)
+ ) {
+
+// op_1: ASSIGN ? -> #v [use_as_double], long(?) => ASSIGN ? -> #v, double(?)
+
+ zval *zv = CT_CONSTANT_EX(op_array, opline->op2.constant);
+ ZEND_ASSERT(Z_TYPE_INFO_P(zv) == IS_LONG);
+ ZVAL_DOUBLE(&tmp, zval_get_double(zv));
+ opline->op2.constant = zend_optimizer_add_literal(op_array, &tmp);
+
+ } else if (opline->opcode == ZEND_QM_ASSIGN
+ && opline->op1_type == IS_CONST
+ ) {
+
+// op_1: QM_ASSIGN #v [use_as_double], long(?) => QM_ASSIGN #v, double(?)
+
+ zval *zv = CT_CONSTANT_EX(op_array, opline->op1.constant);
+ ZEND_ASSERT(Z_TYPE_INFO_P(zv) == IS_LONG);
+ ZVAL_DOUBLE(&tmp, zval_get_double(zv));
+ opline->op1.constant = zend_optimizer_add_literal(op_array, &tmp);
+ }
+
+ } else {
+ if (opline->opcode == ZEND_ADD
+ || opline->opcode == ZEND_SUB
+ || opline->opcode == ZEND_MUL
+ || opline->opcode == ZEND_IS_EQUAL
+ || opline->opcode == ZEND_IS_NOT_EQUAL
+ || opline->opcode == ZEND_IS_SMALLER
+ || opline->opcode == ZEND_IS_SMALLER_OR_EQUAL
+ ) {
+
+ if (opline->op1_type == IS_CONST && opline->op2_type != IS_CONST) {
+ zval *zv = CT_CONSTANT_EX(op_array, opline->op1.constant);
+
+ if ((OP2_INFO() & MAY_BE_ANY) == MAY_BE_DOUBLE
+ && Z_TYPE_INFO_P(zv) == IS_LONG) {
+
+// op_1: #v.? = ADD long(?), #?.? [double] => #v.? = ADD double(?), #?.? [double]
+
+ ZVAL_DOUBLE(&tmp, zval_get_double(zv));
+ opline->op1.constant = zend_optimizer_add_literal(op_array, &tmp);
+ zv = CT_CONSTANT_EX(op_array, opline->op1.constant);
+ }
+ if (opline->opcode == ZEND_ADD) {
+ zv = CT_CONSTANT_EX(op_array, opline->op1.constant);
+
+ if (((OP2_INFO() & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG
+ && Z_TYPE_INFO_P(zv) == IS_LONG
+ && Z_LVAL_P(zv) == 0)
+ || ((OP2_INFO() & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_DOUBLE
+ && Z_TYPE_INFO_P(zv) == IS_DOUBLE
+ && Z_DVAL_P(zv) == 0.0)) {
+
+// op_1: #v.? = ADD 0, #?.? [double,long] => #v.? = QM_ASSIGN #?.?
+
+ opline->opcode = ZEND_QM_ASSIGN;
+ opline->op1_type = opline->op2_type;
+ opline->op1.var = opline->op2.var;
+ opline->op2_type = IS_UNUSED;
+ opline->op2.num = 0;
+ 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;
+ }
+ }
+ } else if (opline->op1_type != IS_CONST && opline->op2_type == IS_CONST) {
+ zval *zv = CT_CONSTANT_EX(op_array, opline->op2.constant);
+
+ if ((OP1_INFO() & MAY_BE_ANY) == MAY_BE_DOUBLE
+ && Z_TYPE_INFO_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) == IS_LONG) {
+
+// op_1: #v.? = ADD #?.? [double], long(?) => #v.? = ADD #?.? [double], double(?)
+
+ ZVAL_DOUBLE(&tmp, zval_get_double(zv));
+ opline->op2.constant = zend_optimizer_add_literal(op_array, &tmp);
+ zv = CT_CONSTANT_EX(op_array, opline->op2.constant);
+ }
+ if (opline->opcode == ZEND_ADD || opline->opcode == ZEND_SUB) {
+ if (((OP1_INFO() & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG
+ && Z_TYPE_INFO_P(zv) == IS_LONG
+ && Z_LVAL_P(zv) == 0)
+ || ((OP1_INFO() & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_DOUBLE
+ && Z_TYPE_INFO_P(zv) == IS_DOUBLE
+ && Z_DVAL_P(zv) == 0.0)) {
+
+// op_1: #v.? = ADD #?.? [double,long], 0 => #v.? = QM_ASSIGN #?.?
+
+ opline->opcode = ZEND_QM_ASSIGN;
+ opline->op2_type = IS_UNUSED;
+ opline->op2.num = 0;
+ }
+ }
+ }
+ } else if (opline->opcode == ZEND_CONCAT) {
+ if (!(OP1_INFO() & MAY_BE_OBJECT)
+ && !(OP2_INFO() & MAY_BE_OBJECT)) {
+ opline->opcode = ZEND_FAST_CONCAT;
+ }
+ } else if (opline->opcode == ZEND_VERIFY_RETURN_TYPE
+ && opline->op1_type != IS_CONST
+ && ssa->ops[op_1].op1_def == v
+ && ssa->ops[op_1].op1_use >= 0
+ && ssa->ops[op_1].op1_use_chain == -1
+ && ssa->vars[v].use_chain >= 0
+ && can_elide_return_type_check(op_array, ssa, &ssa->ops[op_1])) {
+
+// op_1: VERIFY_RETURN_TYPE #orig_var.? [T] -> #v.? [T] => NOP
+
+ int orig_var = ssa->ops[op_1].op1_use;
+ if (zend_ssa_unlink_use_chain(ssa, op_1, orig_var)) {
+
+ int ret = ssa->vars[v].use_chain;
+
+ ssa->ops[ret].op1_use = orig_var;
+ ssa->ops[ret].op1_use_chain = ssa->vars[orig_var].use_chain;
+ ssa->vars[orig_var].use_chain = ret;
+
+ ssa->vars[v].definition = -1;
+ ssa->vars[v].use_chain = -1;
+
+ ssa->ops[op_1].op1_def = -1;
+ ssa->ops[op_1].op1_use = -1;
+
+ MAKE_NOP(opline);
+ remove_nops = 1;
+ }
+ }
+ }
+
+ if (opline->opcode == ZEND_QM_ASSIGN
+ && ssa->ops[op_1].result_def == v
+ && opline->op1_type & (IS_TMP_VAR|IS_VAR)
+ && !(ssa->var_info[v].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))
+ ) {
+
+ int src_var = ssa->ops[op_1].op1_use;
+
+ if (src_var >= 0
+ && !(ssa->var_info[src_var].type & MAY_BE_REF)
+ && ssa->vars[src_var].definition >= 0
+ && ssa->ops[ssa->vars[src_var].definition].result_def == src_var
+ && ssa->ops[ssa->vars[src_var].definition].result_use < 0
+ && ssa->vars[src_var].use_chain == op_1
+ && ssa->ops[op_1].op1_use_chain < 0
+ && !ssa->vars[src_var].phi_use_chain
+ && !ssa->vars[src_var].sym_use_chain
+ && opline_supports_assign_contraction(
+ ssa, &op_array->opcodes[ssa->vars[src_var].definition],
+ src_var, opline->result.var)
+ ) {
+
+ int orig_var = ssa->ops[op_1].result_use;
+ int op_2 = ssa->vars[src_var].definition;
+
+// op_2: #src_var.T = OP ... => #v.CV = OP ...
+// op_1: QM_ASSIGN #src_var.T #orig_var.CV [undef,scalar] -> #v.CV, NOP
+
+ if (orig_var < 0 || zend_ssa_unlink_use_chain(ssa, op_1, orig_var)) {
+ /* Reconstruct SSA */
+ ssa->vars[v].definition = op_2;
+ ssa->ops[op_2].result_def = v;
+
+ ssa->vars[src_var].definition = -1;
+ ssa->vars[src_var].use_chain = -1;
+
+ ssa->ops[op_1].op1_use = -1;
+ ssa->ops[op_1].op1_def = -1;
+ ssa->ops[op_1].op1_use_chain = -1;
+ ssa->ops[op_1].result_use = -1;
+ ssa->ops[op_1].result_def = -1;
+ ssa->ops[op_1].res_use_chain = -1;
+
+ /* Update opcodes */
+ op_array->opcodes[op_2].result_type = opline->result_type;
+ op_array->opcodes[op_2].result.var = opline->result.var;
+
+ MAKE_NOP(opline);
+ remove_nops = 1;
+
+ if (op_array->opcodes[op_2].opcode == ZEND_SUB
+ && op_array->opcodes[op_2].op1_type == op_array->opcodes[op_2].result_type
+ && op_array->opcodes[op_2].op1.var == op_array->opcodes[op_2].result.var
+ && op_array->opcodes[op_2].op2_type == IS_CONST
+ && Z_TYPE_P(CT_CONSTANT_EX(op_array, op_array->opcodes[op_2].op2.constant)) == IS_LONG
+ && Z_LVAL_P(CT_CONSTANT_EX(op_array, op_array->opcodes[op_2].op2.constant)) == 1
+ && ssa->ops[op_2].op1_use >= 0
+ && !(ssa->var_info[ssa->ops[op_2].op1_use].type & (MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
+
+ op_array->opcodes[op_2].opcode = ZEND_PRE_DEC;
+ SET_UNUSED(op_array->opcodes[op_2].op2);
+ SET_UNUSED(op_array->opcodes[op_2].result);
+
+ ssa->ops[op_2].result_def = -1;
+ ssa->ops[op_2].op1_def = v;
+
+ } else if (op_array->opcodes[op_2].opcode == ZEND_ADD
+ && op_array->opcodes[op_2].op1_type == op_array->opcodes[op_2].result_type
+ && op_array->opcodes[op_2].op1.var == op_array->opcodes[op_2].result.var
+ && op_array->opcodes[op_2].op2_type == IS_CONST
+ && Z_TYPE_P(CT_CONSTANT_EX(op_array, op_array->opcodes[op_2].op2.constant)) == IS_LONG
+ && Z_LVAL_P(CT_CONSTANT_EX(op_array, op_array->opcodes[op_2].op2.constant)) == 1
+ && ssa->ops[op_2].op1_use >= 0
+ && !(ssa->var_info[ssa->ops[op_2].op1_use].type & (MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
+
+ op_array->opcodes[op_2].opcode = ZEND_PRE_INC;
+ SET_UNUSED(op_array->opcodes[op_2].op2);
+ SET_UNUSED(op_array->opcodes[op_2].result);
+
+ ssa->ops[op_2].result_def = -1;
+ ssa->ops[op_2].op1_def = v;
+
+ } else if (op_array->opcodes[op_2].opcode == ZEND_ADD
+ && op_array->opcodes[op_2].op2_type == op_array->opcodes[op_2].result_type
+ && op_array->opcodes[op_2].op2.var == op_array->opcodes[op_2].result.var
+ && op_array->opcodes[op_2].op1_type == IS_CONST
+ && Z_TYPE_P(CT_CONSTANT_EX(op_array, op_array->opcodes[op_2].op1.constant)) == IS_LONG
+ && Z_LVAL_P(CT_CONSTANT_EX(op_array, op_array->opcodes[op_2].op1.constant)) == 1
+ && ssa->ops[op_2].op2_use >= 0
+ && !(ssa->var_info[ssa->ops[op_2].op2_use].type & (MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
+
+ op_array->opcodes[op_2].opcode = ZEND_PRE_INC;
+ op_array->opcodes[op_2].op1_type = op_array->opcodes[op_2].op2_type;
+ op_array->opcodes[op_2].op1.var = op_array->opcodes[op_2].op2.var;
+ SET_UNUSED(op_array->opcodes[op_2].op2);
+ SET_UNUSED(op_array->opcodes[op_2].result);
+
+ ssa->ops[op_2].result_def = -1;
+ ssa->ops[op_2].op1_def = v;
+ ssa->ops[op_2].op1_use = ssa->ops[op_2].op2_use;
+ ssa->ops[op_2].op1_use_chain = ssa->ops[op_2].op2_use_chain;
+ ssa->ops[op_2].op2_use = -1;
+ ssa->ops[op_2].op2_use_chain = -1;
+ }
+ }
+ }
+ }
+
+ if (ssa->vars[v].var >= op_array->last_var) {
+ /* skip TMP and VAR */
+ continue;
+ }
+
+ if (ssa->ops[op_1].op1_def == v
+ && RETURN_VALUE_USED(opline)) {
+ if (opline->opcode == ZEND_ASSIGN
+ || opline->opcode == ZEND_ASSIGN_OP
+ || opline->opcode == ZEND_PRE_INC
+ || opline->opcode == ZEND_PRE_DEC) {
+ zend_dfa_try_to_replace_result(op_array, ssa, op_1, v);
+ } else if (opline->opcode == ZEND_POST_INC) {
+ int result_var = ssa->ops[op_1].result_def;
+
+ if (result_var >= 0
+ && (ssa->var_info[result_var].type & ((MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF) - (MAY_BE_LONG|MAY_BE_DOUBLE))) == 0) {
+ int use = ssa->vars[result_var].use_chain;
+
+ if (op_array->opcodes[use].opcode == ZEND_IS_SMALLER
+ && ssa->ops[use].op1_use == result_var
+ && zend_dfa_try_to_replace_result(op_array, ssa, op_1, v)) {
+ opline->opcode = ZEND_PRE_INC;
+ op_array->opcodes[use].opcode = ZEND_IS_SMALLER_OR_EQUAL;
+ }
+ }
+ } else if (opline->opcode == ZEND_POST_DEC) {
+ int result_var = ssa->ops[op_1].result_def;
+
+ if (result_var >= 0
+ && (ssa->var_info[result_var].type & ((MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF) - (MAY_BE_LONG|MAY_BE_DOUBLE))) == 0) {
+ int use = ssa->vars[result_var].use_chain;
+
+ if (op_array->opcodes[use].opcode == ZEND_IS_SMALLER
+ && ssa->ops[use].op2_use == result_var
+ && zend_dfa_try_to_replace_result(op_array, ssa, op_1, v)) {
+ opline->opcode = ZEND_PRE_DEC;
+ op_array->opcodes[use].opcode = ZEND_IS_SMALLER_OR_EQUAL;
+ }
+ }
+ }
+ }
+
+ if (opline->opcode == ZEND_ASSIGN
+ && ssa->ops[op_1].op1_def == v
+ && !RETURN_VALUE_USED(opline)
+ ) {
+ int orig_var = ssa->ops[op_1].op1_use;
+
+ if (orig_var >= 0
+ && !(ssa->var_info[orig_var].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))
+ ) {
+ int src_var = ssa->ops[op_1].op2_use;
+
+ if ((opline->op2_type & (IS_TMP_VAR|IS_VAR))
+ && src_var >= 0
+ && !(ssa->var_info[src_var].type & MAY_BE_REF)
+ && ssa->vars[src_var].definition >= 0
+ && ssa->ops[ssa->vars[src_var].definition].result_def == src_var
+ && ssa->ops[ssa->vars[src_var].definition].result_use < 0
+ && ssa->vars[src_var].use_chain == op_1
+ && ssa->ops[op_1].op2_use_chain < 0
+ && !ssa->vars[src_var].phi_use_chain
+ && !ssa->vars[src_var].sym_use_chain
+ && opline_supports_assign_contraction(
+ ssa, &op_array->opcodes[ssa->vars[src_var].definition],
+ src_var, opline->op1.var)
+ ) {
+
+ int op_2 = ssa->vars[src_var].definition;
+
+// op_2: #src_var.T = OP ... => #v.CV = OP ...
+// op_1: ASSIGN #orig_var.CV [undef,scalar] -> #v.CV, #src_var.T NOP
+
+ if (zend_ssa_unlink_use_chain(ssa, op_1, orig_var)) {
+ /* Reconstruct SSA */
+ ssa->vars[v].definition = op_2;
+ ssa->ops[op_2].result_def = v;
+
+ ssa->vars[src_var].definition = -1;
+ ssa->vars[src_var].use_chain = -1;
+
+ ssa->ops[op_1].op1_use = -1;
+ ssa->ops[op_1].op2_use = -1;
+ ssa->ops[op_1].op1_def = -1;
+ ssa->ops[op_1].op1_use_chain = -1;
+
+ /* Update opcodes */
+ op_array->opcodes[op_2].result_type = opline->op1_type;
+ op_array->opcodes[op_2].result.var = opline->op1.var;
+
+ MAKE_NOP(opline);
+ remove_nops = 1;
+
+ if (op_array->opcodes[op_2].opcode == ZEND_SUB
+ && op_array->opcodes[op_2].op1_type == op_array->opcodes[op_2].result_type
+ && op_array->opcodes[op_2].op1.var == op_array->opcodes[op_2].result.var
+ && op_array->opcodes[op_2].op2_type == IS_CONST
+ && Z_TYPE_P(CT_CONSTANT_EX(op_array, op_array->opcodes[op_2].op2.constant)) == IS_LONG
+ && Z_LVAL_P(CT_CONSTANT_EX(op_array, op_array->opcodes[op_2].op2.constant)) == 1
+ && ssa->ops[op_2].op1_use >= 0
+ && !(ssa->var_info[ssa->ops[op_2].op1_use].type & (MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
+
+ op_array->opcodes[op_2].opcode = ZEND_PRE_DEC;
+ SET_UNUSED(op_array->opcodes[op_2].op2);
+ SET_UNUSED(op_array->opcodes[op_2].result);
+
+ ssa->ops[op_2].result_def = -1;
+ ssa->ops[op_2].op1_def = v;
+
+ } else if (op_array->opcodes[op_2].opcode == ZEND_ADD
+ && op_array->opcodes[op_2].op1_type == op_array->opcodes[op_2].result_type
+ && op_array->opcodes[op_2].op1.var == op_array->opcodes[op_2].result.var
+ && op_array->opcodes[op_2].op2_type == IS_CONST
+ && Z_TYPE_P(CT_CONSTANT_EX(op_array, op_array->opcodes[op_2].op2.constant)) == IS_LONG
+ && Z_LVAL_P(CT_CONSTANT_EX(op_array, op_array->opcodes[op_2].op2.constant)) == 1
+ && ssa->ops[op_2].op1_use >= 0
+ && !(ssa->var_info[ssa->ops[op_2].op1_use].type & (MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
+
+ op_array->opcodes[op_2].opcode = ZEND_PRE_INC;
+ SET_UNUSED(op_array->opcodes[op_2].op2);
+ SET_UNUSED(op_array->opcodes[op_2].result);
+
+ ssa->ops[op_2].result_def = -1;
+ ssa->ops[op_2].op1_def = v;
+
+ } else if (op_array->opcodes[op_2].opcode == ZEND_ADD
+ && op_array->opcodes[op_2].op2_type == op_array->opcodes[op_2].result_type
+ && op_array->opcodes[op_2].op2.var == op_array->opcodes[op_2].result.var
+ && op_array->opcodes[op_2].op1_type == IS_CONST
+ && Z_TYPE_P(CT_CONSTANT_EX(op_array, op_array->opcodes[op_2].op1.constant)) == IS_LONG
+ && Z_LVAL_P(CT_CONSTANT_EX(op_array, op_array->opcodes[op_2].op1.constant)) == 1
+ && ssa->ops[op_2].op2_use >= 0
+ && !(ssa->var_info[ssa->ops[op_2].op2_use].type & (MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
+
+ op_array->opcodes[op_2].opcode = ZEND_PRE_INC;
+ op_array->opcodes[op_2].op1_type = op_array->opcodes[op_2].op2_type;
+ op_array->opcodes[op_2].op1.var = op_array->opcodes[op_2].op2.var;
+ SET_UNUSED(op_array->opcodes[op_2].op2);
+ SET_UNUSED(op_array->opcodes[op_2].result);
+
+ ssa->ops[op_2].result_def = -1;
+ ssa->ops[op_2].op1_def = v;
+ ssa->ops[op_2].op1_use = ssa->ops[op_2].op2_use;
+ ssa->ops[op_2].op1_use_chain = ssa->ops[op_2].op2_use_chain;
+ ssa->ops[op_2].op2_use = -1;
+ ssa->ops[op_2].op2_use_chain = -1;
+ }
+ }
+ } else if (opline->op2_type == IS_CONST
+ || ((opline->op2_type & (IS_TMP_VAR|IS_VAR|IS_CV))
+ && ssa->ops[op_1].op2_use >= 0
+ && ssa->ops[op_1].op2_def < 0)
+ ) {
+
+// op_1: ASSIGN #orig_var.CV [undef,scalar] -> #v.CV, CONST|TMPVAR => QM_ASSIGN v.CV, CONST|TMPVAR
+
+ 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;
+ }
+ }
+
+ } else if (opline->opcode == ZEND_ASSIGN_OP
+ && opline->extended_value == ZEND_ADD
+ && ssa->ops[op_1].op1_def == v
+ && opline->op2_type == IS_CONST
+ && Z_TYPE_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) == IS_LONG
+ && Z_LVAL_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) == 1
+ && ssa->ops[op_1].op1_use >= 0
+ && !(ssa->var_info[ssa->ops[op_1].op1_use].type & (MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
+
+// op_1: ASSIGN_ADD #?.CV [undef,null,int,foat] ->#v.CV, int(1) => PRE_INC #?.CV ->#v.CV
+
+ opline->opcode = ZEND_PRE_INC;
+ opline->extended_value = 0;
+ SET_UNUSED(opline->op2);
+
+ } else if (opline->opcode == ZEND_ASSIGN_OP
+ && opline->extended_value == ZEND_SUB
+ && ssa->ops[op_1].op1_def == v
+ && opline->op2_type == IS_CONST
+ && Z_TYPE_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) == IS_LONG
+ && Z_LVAL_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) == 1
+ && ssa->ops[op_1].op1_use >= 0
+ && !(ssa->var_info[ssa->ops[op_1].op1_use].type & (MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
+
+// op_1: ASSIGN_SUB #?.CV [undef,null,int,foat] -> #v.CV, int(1) => PRE_DEC #?.CV ->#v.CV
+
+ opline->opcode = ZEND_PRE_DEC;
+ opline->extended_value = 0;
+ SET_UNUSED(opline->op2);
+
+ } else if (ssa->ops[op_1].op1_def == v
+ && !RETURN_VALUE_USED(opline)
+ && ssa->ops[op_1].op1_use >= 0
+ && !(ssa->var_info[ssa->ops[op_1].op1_use].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))
+ && opline->opcode == ZEND_ASSIGN_OP
+ && opline->extended_value != ZEND_CONCAT) {
+
+// op_1: ASSIGN_OP #orig_var.CV [undef,null,bool,int,double] -> #v.CV, ? => #v.CV = ADD #orig_var.CV, ?
+
+ /* Reconstruct SSA */
+ ssa->ops[op_1].result_def = ssa->ops[op_1].op1_def;
+ ssa->ops[op_1].op1_def = -1;
+
+ /* Update opcode */
+ opline->opcode = opline->extended_value;
+ opline->extended_value = 0;
+ opline->result_type = opline->op1_type;
+ opline->result.var = opline->op1.var;
+
+ }
+ }
+
+#if ZEND_DEBUG_DFA
+ ssa_verify_integrity(op_array, ssa, "after dfa");
+#endif
+
+ if (remove_nops) {
+ zend_ssa_remove_nops(op_array, ssa, ctx);
+#if ZEND_DEBUG_DFA
+ ssa_verify_integrity(op_array, ssa, "after nop");
+#endif
+ }
+ }
+
+ if (ctx->debug_level & ZEND_DUMP_AFTER_DFA_PASS) {
+ zend_dump_op_array(op_array, ZEND_DUMP_SSA, "after dfa pass", ssa);
+ }
+}
+
+void zend_optimize_dfa(zend_op_array *op_array, zend_optimizer_ctx *ctx)
+{
+ void *checkpoint = zend_arena_checkpoint(ctx->arena);
+ zend_ssa ssa;
+
+ if (zend_dfa_analyze_op_array(op_array, ctx, &ssa) != SUCCESS) {
+ zend_arena_release(&ctx->arena, checkpoint);
+ return;
+ }
+
+ zend_dfa_optimize_op_array(op_array, ctx, &ssa, NULL);
+
+ /* Destroy SSA */
+ zend_arena_release(&ctx->arena, checkpoint);
+}
diff --git a/Zend/Optimizer/escape_analysis.c b/Zend/Optimizer/escape_analysis.c
new file mode 100644
index 0000000000..c0d5081c1f
--- /dev/null
+++ b/Zend/Optimizer/escape_analysis.c
@@ -0,0 +1,539 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend OPcache, Escape Analysis |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#include "php.h"
+#include "Optimizer/zend_optimizer.h"
+#include "Optimizer/zend_optimizer_internal.h"
+#include "zend_bitset.h"
+#include "zend_cfg.h"
+#include "zend_ssa.h"
+#include "zend_inference.h"
+#include "zend_dump.h"
+
+/*
+ * T. Kotzmann and H. Mossenbock. Escape analysis in the context of dynamic
+ * compilation and deoptimization. In Proceedings of the International
+ * Conference on Virtual Execution Environments, pages 111-120, Chicago,
+ * June 2005
+ */
+
+static zend_always_inline void union_find_init(int *parent, int *size, int count) /* {{{ */
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ parent[i] = i;
+ size[i] = 1;
+ }
+}
+/* }}} */
+
+static zend_always_inline int union_find_root(int *parent, int i) /* {{{ */
+{
+ int p = parent[i];
+
+ while (i != p) {
+ p = parent[p];
+ parent[i] = p;
+ i = p;
+ p = parent[i];
+ }
+ return i;
+}
+/* }}} */
+
+static zend_always_inline void union_find_unite(int *parent, int *size, int i, int j) /* {{{ */
+{
+ int r1 = union_find_root(parent, i);
+ int r2 = union_find_root(parent, j);
+
+ if (r1 != r2) {
+ if (size[r1] < size[r2]) {
+ parent[r1] = r2;
+ size[r2] += size[r1];
+ } else {
+ parent[r2] = r1;
+ size[r1] += size[r2];
+ }
+ }
+}
+/* }}} */
+
+static int zend_build_equi_escape_sets(int *parent, zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
+{
+ zend_ssa_var *ssa_vars = ssa->vars;
+ int ssa_vars_count = ssa->vars_count;
+ zend_ssa_phi *p;
+ int i, j;
+ int *size;
+ ALLOCA_FLAG(use_heap)
+
+ size = do_alloca(sizeof(int) * ssa_vars_count, use_heap);
+ if (!size) {
+ return FAILURE;
+ }
+ union_find_init(parent, size, ssa_vars_count);
+
+ for (i = 0; i < ssa_vars_count; i++) {
+ if (ssa_vars[i].definition_phi) {
+ p = ssa_vars[i].definition_phi;
+ if (p->pi >= 0) {
+ union_find_unite(parent, size, i, p->sources[0]);
+ } else {
+ for (j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) {
+ union_find_unite(parent, size, i, p->sources[j]);
+ }
+ }
+ } else if (ssa_vars[i].definition >= 0) {
+ int def = ssa_vars[i].definition;
+ zend_ssa_op *op = ssa->ops + def;
+ zend_op *opline = op_array->opcodes + def;
+
+ if (op->op1_def >= 0) {
+ if (op->op1_use >= 0) {
+ if (opline->opcode != ZEND_ASSIGN) {
+ union_find_unite(parent, size, op->op1_def, op->op1_use);
+ }
+ }
+ if (opline->opcode == ZEND_ASSIGN && op->op2_use >= 0) {
+ union_find_unite(parent, size, op->op1_def, op->op2_use);
+ }
+ }
+ if (op->op2_def >= 0) {
+ if (op->op2_use >= 0) {
+ union_find_unite(parent, size, op->op2_def, op->op2_use);
+ }
+ }
+ if (op->result_def >= 0) {
+ if (op->result_use >= 0) {
+ if (opline->opcode != ZEND_QM_ASSIGN) {
+ union_find_unite(parent, size, op->result_def, op->result_use);
+ }
+ }
+ if (opline->opcode == ZEND_QM_ASSIGN && op->op1_use >= 0) {
+ union_find_unite(parent, size, op->result_def, op->op1_use);
+ }
+ if (opline->opcode == ZEND_ASSIGN && op->op2_use >= 0) {
+ union_find_unite(parent, size, op->result_def, op->op2_use);
+ }
+ if (opline->opcode == ZEND_ASSIGN && op->op1_def >= 0) {
+ union_find_unite(parent, size, op->result_def, op->op1_def);
+ }
+ }
+ }
+ }
+
+ for (i = 0; i < ssa_vars_count; i++) {
+ parent[i] = union_find_root(parent, i);
+ }
+
+ free_alloca(size, use_heap);
+
+ return SUCCESS;
+}
+/* }}} */
+
+static inline zend_class_entry *get_class_entry(const zend_script *script, zend_string *lcname) /* {{{ */
+{
+ zend_class_entry *ce = script ? zend_hash_find_ptr(&script->class_table, lcname) : NULL;
+ if (ce) {
+ return ce;
+ }
+
+ ce = zend_hash_find_ptr(CG(class_table), lcname);
+ if (ce && ce->type == ZEND_INTERNAL_CLASS) {
+ return ce;
+ }
+
+ return NULL;
+}
+/* }}} */
+
+static int is_allocation_def(zend_op_array *op_array, zend_ssa *ssa, int def, int var, const zend_script *script) /* {{{ */
+{
+ zend_ssa_op *ssa_op = ssa->ops + def;
+ zend_op *opline = op_array->opcodes + def;
+
+ if (ssa_op->result_def == var) {
+ switch (opline->opcode) {
+ case ZEND_INIT_ARRAY:
+ return 1;
+ case ZEND_NEW:
+ /* objects with destructors should escape */
+ if (opline->op1_type == IS_CONST) {
+ zend_class_entry *ce = get_class_entry(script, Z_STR_P(CRT_CONSTANT(opline->op1)+1));
+ uint32_t forbidden_flags =
+ /* These flags will always cause an exception */
+ ZEND_ACC_IMPLICIT_ABSTRACT_CLASS | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS
+ | ZEND_ACC_INTERFACE | ZEND_ACC_TRAIT;
+ if (ce && !ce->parent && !ce->create_object && !ce->constructor &&
+ !ce->destructor && !ce->__get && !ce->__set &&
+ !(ce->ce_flags & forbidden_flags) &&
+ (ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
+ return 1;
+ }
+ }
+ break;
+ case ZEND_QM_ASSIGN:
+ if (opline->op1_type == IS_CONST
+ && Z_TYPE_P(CRT_CONSTANT(opline->op1)) == IS_ARRAY) {
+ return 1;
+ }
+ if (opline->op1_type == IS_CV && (OP1_INFO() & MAY_BE_ARRAY)) {
+ return 1;
+ }
+ break;
+ case ZEND_ASSIGN:
+ if (opline->op1_type == IS_CV && (OP1_INFO() & MAY_BE_ARRAY)) {
+ return 1;
+ }
+ break;
+ }
+ } else if (ssa_op->op1_def == var) {
+ switch (opline->opcode) {
+ case ZEND_ASSIGN:
+ if (opline->op2_type == IS_CONST
+ && Z_TYPE_P(CRT_CONSTANT(opline->op2)) == IS_ARRAY) {
+ return 1;
+ }
+ if (opline->op2_type == IS_CV && (OP2_INFO() & MAY_BE_ARRAY)) {
+ return 1;
+ }
+ break;
+ case ZEND_ASSIGN_DIM:
+ if (OP1_INFO() & (MAY_BE_UNDEF | MAY_BE_NULL | MAY_BE_FALSE)) {
+ /* implicit object/array allocation */
+ return 1;
+ }
+ break;
+ }
+ }
+
+ return 0;
+}
+/* }}} */
+
+static int is_local_def(zend_op_array *op_array, zend_ssa *ssa, int def, int var, const zend_script *script) /* {{{ */
+{
+ zend_ssa_op *op = ssa->ops + def;
+ zend_op *opline = op_array->opcodes + def;
+
+ if (op->result_def == var) {
+ switch (opline->opcode) {
+ case ZEND_INIT_ARRAY:
+ case ZEND_ADD_ARRAY_ELEMENT:
+ case ZEND_QM_ASSIGN:
+ case ZEND_ASSIGN:
+ return 1;
+ case ZEND_NEW:
+ /* objects with destructors should escape */
+ if (opline->op1_type == IS_CONST) {
+ zend_class_entry *ce = get_class_entry(script, Z_STR_P(CRT_CONSTANT(opline->op1)+1));
+ if (ce && !ce->create_object && !ce->constructor &&
+ !ce->destructor && !ce->__get && !ce->__set && !ce->parent) {
+ return 1;
+ }
+ }
+ break;
+ }
+ } else if (op->op1_def == var) {
+ switch (opline->opcode) {
+ case ZEND_ASSIGN:
+ case ZEND_ASSIGN_DIM:
+ case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_OBJ_REF:
+ case ZEND_ASSIGN_DIM_OP:
+ case ZEND_ASSIGN_OBJ_OP:
+ case ZEND_PRE_INC_OBJ:
+ case ZEND_PRE_DEC_OBJ:
+ case ZEND_POST_INC_OBJ:
+ case ZEND_POST_DEC_OBJ:
+ return 1;
+ }
+ }
+
+ return 0;
+}
+/* }}} */
+
+static int is_escape_use(zend_op_array *op_array, zend_ssa *ssa, int use, int var) /* {{{ */
+{
+ zend_ssa_op *ssa_op = ssa->ops + use;
+ zend_op *opline = op_array->opcodes + use;
+
+ if (ssa_op->op1_use == var) {
+ switch (opline->opcode) {
+ case ZEND_ASSIGN:
+ /* no_val */
+ break;
+ case ZEND_QM_ASSIGN:
+ if (opline->op1_type == IS_CV) {
+ if (OP1_INFO() & MAY_BE_OBJECT) {
+ /* object aliasing */
+ return 1;
+ }
+ }
+ break;
+ case ZEND_ISSET_ISEMPTY_DIM_OBJ:
+ case ZEND_ISSET_ISEMPTY_PROP_OBJ:
+ case ZEND_FETCH_DIM_R:
+ case ZEND_FETCH_OBJ_R:
+ case ZEND_FETCH_DIM_IS:
+ case ZEND_FETCH_OBJ_IS:
+ break;
+ case ZEND_ASSIGN_OP:
+ return 1;
+ case ZEND_ASSIGN_DIM_OP:
+ case ZEND_ASSIGN_OBJ_OP:
+ case ZEND_ASSIGN_STATIC_PROP_OP:
+ case ZEND_ASSIGN_DIM:
+ case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_OBJ_REF:
+ break;
+ case ZEND_PRE_INC_OBJ:
+ case ZEND_PRE_DEC_OBJ:
+ case ZEND_POST_INC_OBJ:
+ case ZEND_POST_DEC_OBJ:
+ break;
+ case ZEND_INIT_ARRAY:
+ case ZEND_ADD_ARRAY_ELEMENT:
+ if (opline->extended_value & ZEND_ARRAY_ELEMENT_REF) {
+ return 1;
+ }
+ if (OP1_INFO() & MAY_BE_OBJECT) {
+ /* object aliasing */
+ return 1;
+ }
+ /* reference dependencies processed separately */
+ break;
+ case ZEND_OP_DATA:
+ if ((opline-1)->opcode != ZEND_ASSIGN_DIM
+ && (opline-1)->opcode != ZEND_ASSIGN_OBJ) {
+ return 1;
+ }
+ if (OP1_INFO() & MAY_BE_OBJECT) {
+ /* object aliasing */
+ return 1;
+ }
+ opline--;
+ ssa_op--;
+ if (opline->op1_type != IS_CV
+ || (OP1_INFO() & MAY_BE_REF)
+ || (ssa_op->op1_def >= 0 && ssa->vars[ssa_op->op1_def].alias)) {
+ /* assignment into escaping structure */
+ return 1;
+ }
+ /* reference dependencies processed separately */
+ break;
+ default:
+ return 1;
+ }
+ }
+
+ if (ssa_op->op2_use == var) {
+ switch (opline->opcode) {
+ case ZEND_ASSIGN:
+ if (opline->op1_type != IS_CV
+ || (OP1_INFO() & MAY_BE_REF)
+ || (ssa_op->op1_def >= 0 && ssa->vars[ssa_op->op1_def].alias)) {
+ /* assignment into escaping variable */
+ return 1;
+ }
+ if (opline->op2_type == IS_CV || opline->result_type != IS_UNUSED) {
+ if (OP2_INFO() & MAY_BE_OBJECT) {
+ /* object aliasing */
+ return 1;
+ }
+ }
+ break;
+ default:
+ return 1;
+ }
+ }
+
+ if (ssa_op->result_use == var) {
+ switch (opline->opcode) {
+ case ZEND_ASSIGN:
+ case ZEND_QM_ASSIGN:
+ case ZEND_INIT_ARRAY:
+ case ZEND_ADD_ARRAY_ELEMENT:
+ break;
+ default:
+ return 1;
+ }
+ }
+
+ return 0;
+}
+/* }}} */
+
+int zend_ssa_escape_analysis(const zend_script *script, zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
+{
+ zend_ssa_var *ssa_vars = ssa->vars;
+ int ssa_vars_count = ssa->vars_count;
+ int i, root, use;
+ int *ees;
+ bool has_allocations;
+ int num_non_escaped;
+ ALLOCA_FLAG(use_heap)
+
+ if (!ssa_vars) {
+ return SUCCESS;
+ }
+
+ has_allocations = 0;
+ for (i = op_array->last_var; i < ssa_vars_count; i++) {
+ if (ssa_vars[i].definition >= 0
+ && (ssa->var_info[i].type & (MAY_BE_ARRAY|MAY_BE_OBJECT))
+ && is_allocation_def(op_array, ssa, ssa_vars[i].definition, i, script)) {
+ has_allocations = 1;
+ break;
+ }
+ }
+ if (!has_allocations) {
+ return SUCCESS;
+ }
+
+
+ /* 1. Build EES (Equi-Escape Sets) */
+ ees = do_alloca(sizeof(int) * ssa_vars_count, use_heap);
+ if (!ees) {
+ return FAILURE;
+ }
+
+ if (zend_build_equi_escape_sets(ees, op_array, ssa) != SUCCESS) {
+ return FAILURE;
+ }
+
+ /* 2. Identify Allocations */
+ num_non_escaped = 0;
+ for (i = op_array->last_var; i < ssa_vars_count; i++) {
+ root = ees[i];
+ if (ssa_vars[root].escape_state > ESCAPE_STATE_NO_ESCAPE) {
+ /* already escape. skip */
+ } else if (ssa_vars[i].alias && (ssa->var_info[i].type & MAY_BE_REF)) {
+ if (ssa_vars[root].escape_state == ESCAPE_STATE_NO_ESCAPE) {
+ num_non_escaped--;
+ }
+ ssa_vars[root].escape_state = ESCAPE_STATE_GLOBAL_ESCAPE;
+ } else if (ssa_vars[i].definition >= 0
+ && (ssa->var_info[i].type & (MAY_BE_ARRAY|MAY_BE_OBJECT))) {
+ if (!is_local_def(op_array, ssa, ssa_vars[i].definition, i, script)) {
+ if (ssa_vars[root].escape_state == ESCAPE_STATE_NO_ESCAPE) {
+ num_non_escaped--;
+ }
+ ssa_vars[root].escape_state = ESCAPE_STATE_GLOBAL_ESCAPE;
+ } else if (ssa_vars[root].escape_state == ESCAPE_STATE_UNKNOWN
+ && is_allocation_def(op_array, ssa, ssa_vars[i].definition, i, script)) {
+ ssa_vars[root].escape_state = ESCAPE_STATE_NO_ESCAPE;
+ num_non_escaped++;
+ }
+ }
+ }
+
+ /* 3. Mark escaped EES */
+ if (num_non_escaped) {
+ for (i = 0; i < ssa_vars_count; i++) {
+ if (ssa_vars[i].use_chain >= 0) {
+ root = ees[i];
+ if (ssa_vars[root].escape_state == ESCAPE_STATE_NO_ESCAPE) {
+ FOREACH_USE(ssa_vars + i, use) {
+ if (is_escape_use(op_array, ssa, use, i)) {
+ ssa_vars[root].escape_state = ESCAPE_STATE_GLOBAL_ESCAPE;
+ num_non_escaped--;
+ if (num_non_escaped == 0) {
+ i = ssa_vars_count;
+ }
+ break;
+ }
+ } FOREACH_USE_END();
+ }
+ }
+ }
+ }
+
+ /* 4. Process referential dependencies */
+ if (num_non_escaped) {
+ bool changed;
+
+ do {
+ changed = 0;
+ for (i = 0; i < ssa_vars_count; i++) {
+ if (ssa_vars[i].use_chain >= 0) {
+ root = ees[i];
+ if (ssa_vars[root].escape_state == ESCAPE_STATE_NO_ESCAPE) {
+ FOREACH_USE(ssa_vars + i, use) {
+ zend_ssa_op *op = ssa->ops + use;
+ zend_op *opline = op_array->opcodes + use;
+ int enclosing_root;
+
+ if (opline->opcode == ZEND_OP_DATA &&
+ ((opline-1)->opcode == ZEND_ASSIGN_DIM ||
+ (opline-1)->opcode == ZEND_ASSIGN_OBJ ||
+ (opline-1)->opcode == ZEND_ASSIGN_OBJ_REF) &&
+ op->op1_use == i &&
+ (op-1)->op1_use >= 0) {
+ enclosing_root = ees[(op-1)->op1_use];
+ } else if ((opline->opcode == ZEND_INIT_ARRAY ||
+ opline->opcode == ZEND_ADD_ARRAY_ELEMENT) &&
+ op->op1_use == i &&
+ op->result_def >= 0) {
+ enclosing_root = ees[op->result_def];
+ } else {
+ continue;
+ }
+
+ if (ssa_vars[enclosing_root].escape_state == ESCAPE_STATE_UNKNOWN ||
+ ssa_vars[enclosing_root].escape_state > ssa_vars[root].escape_state) {
+ if (ssa_vars[enclosing_root].escape_state == ESCAPE_STATE_UNKNOWN) {
+ ssa_vars[root].escape_state = ESCAPE_STATE_GLOBAL_ESCAPE;
+ } else {
+ ssa_vars[root].escape_state = ssa_vars[enclosing_root].escape_state;
+ }
+ if (ssa_vars[root].escape_state == ESCAPE_STATE_GLOBAL_ESCAPE) {
+ num_non_escaped--;
+ if (num_non_escaped == 0) {
+ changed = 0;
+ } else {
+ changed = 1;
+ }
+ break;
+ } else {
+ changed = 1;
+ }
+ }
+ } FOREACH_USE_END();
+ }
+ }
+ }
+ } while (changed);
+ }
+
+ /* 5. Propagate values of escape sets to variables */
+ for (i = 0; i < ssa_vars_count; i++) {
+ root = ees[i];
+ if (i != root) {
+ ssa_vars[i].escape_state = ssa_vars[root].escape_state;
+ }
+ }
+
+ free_alloca(ees, use_heap);
+
+ return SUCCESS;
+}
+/* }}} */
diff --git a/Zend/Optimizer/nop_removal.c b/Zend/Optimizer/nop_removal.c
new file mode 100644
index 0000000000..32d2f10bf4
--- /dev/null
+++ b/Zend/Optimizer/nop_removal.c
@@ -0,0 +1,106 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend OPcache |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Andi Gutmans <andi@php.net> |
+ | Zeev Suraski <zeev@php.net> |
+ | Stanislav Malyshev <stas@zend.com> |
+ | Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+/* pass 10:
+ * - remove NOPs
+ */
+
+#include "php.h"
+#include "Optimizer/zend_optimizer.h"
+#include "Optimizer/zend_optimizer_internal.h"
+#include "zend_API.h"
+#include "zend_constants.h"
+#include "zend_execute.h"
+#include "zend_vm.h"
+
+void zend_optimizer_nop_removal(zend_op_array *op_array, zend_optimizer_ctx *ctx)
+{
+ zend_op *end, *opline;
+ uint32_t new_count, i, shift;
+ int j;
+ uint32_t *shiftlist;
+ ALLOCA_FLAG(use_heap);
+
+ shiftlist = (uint32_t *)do_alloca(sizeof(uint32_t) * op_array->last, use_heap);
+ i = new_count = shift = 0;
+ end = op_array->opcodes + op_array->last;
+ for (opline = op_array->opcodes; opline < end; opline++) {
+
+ /* Kill JMP-over-NOP-s */
+ if (opline->opcode == ZEND_JMP && ZEND_OP1_JMP_ADDR(opline) > op_array->opcodes + i) {
+ /* check if there are only NOPs under the branch */
+ zend_op *target = ZEND_OP1_JMP_ADDR(opline) - 1;
+
+ while (target->opcode == ZEND_NOP) {
+ target--;
+ }
+ if (target == opline) {
+ /* only NOPs */
+ opline->opcode = ZEND_NOP;
+ }
+ }
+
+ shiftlist[i++] = shift;
+ if (opline->opcode == ZEND_NOP) {
+ shift++;
+ } else {
+ if (shift) {
+ zend_op *new_opline = op_array->opcodes + new_count;
+
+ *new_opline = *opline;
+ zend_optimizer_migrate_jump(op_array, new_opline, opline);
+ }
+ new_count++;
+ }
+ }
+
+ if (shift) {
+ op_array->last = new_count;
+ end = op_array->opcodes + op_array->last;
+
+ /* update JMPs */
+ for (opline = op_array->opcodes; opline<end; opline++) {
+ zend_optimizer_shift_jump(op_array, opline, shiftlist);
+ }
+
+ /* update try/catch array */
+ for (j = 0; j < op_array->last_try_catch; j++) {
+ op_array->try_catch_array[j].try_op -= shiftlist[op_array->try_catch_array[j].try_op];
+ op_array->try_catch_array[j].catch_op -= shiftlist[op_array->try_catch_array[j].catch_op];
+ if (op_array->try_catch_array[j].finally_op) {
+ op_array->try_catch_array[j].finally_op -= shiftlist[op_array->try_catch_array[j].finally_op];
+ op_array->try_catch_array[j].finally_end -= shiftlist[op_array->try_catch_array[j].finally_end];
+ }
+ }
+
+ /* update early binding list */
+ if (op_array->fn_flags & ZEND_ACC_EARLY_BINDING) {
+ uint32_t *opline_num = &ctx->script->first_early_binding_opline;
+
+ ZEND_ASSERT(op_array == &ctx->script->main_op_array);
+ do {
+ *opline_num -= shiftlist[*opline_num];
+ opline_num = &op_array->opcodes[*opline_num].result.opline_num;
+ } while (*opline_num != (uint32_t)-1);
+ }
+ }
+ free_alloca(shiftlist, use_heap);
+}
diff --git a/Zend/Optimizer/optimize_func_calls.c b/Zend/Optimizer/optimize_func_calls.c
new file mode 100644
index 0000000000..319b17438d
--- /dev/null
+++ b/Zend/Optimizer/optimize_func_calls.c
@@ -0,0 +1,337 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend OPcache |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Dmitry Stogov <dmitry@php.net> |
+ | Xinchen Hui <laruence@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+/* pass 4
+ * - optimize INIT_FCALL_BY_NAME to DO_FCALL
+ */
+
+#include "php.h"
+#include "Optimizer/zend_optimizer.h"
+#include "Optimizer/zend_optimizer_internal.h"
+#include "zend_API.h"
+#include "zend_constants.h"
+#include "zend_execute.h"
+#include "zend_vm.h"
+
+#define ZEND_OP1_IS_CONST_STRING(opline) \
+ (opline->op1_type == IS_CONST && \
+ Z_TYPE(op_array->literals[(opline)->op1.constant]) == IS_STRING)
+#define ZEND_OP2_IS_CONST_STRING(opline) \
+ (opline->op2_type == IS_CONST && \
+ Z_TYPE(op_array->literals[(opline)->op2.constant]) == IS_STRING)
+
+typedef struct _optimizer_call_info {
+ zend_function *func;
+ zend_op *opline;
+ bool is_prototype;
+ bool try_inline;
+ uint32_t func_arg_num;
+} optimizer_call_info;
+
+static void zend_delete_call_instructions(zend_op *opline)
+{
+ int call = 0;
+
+ while (1) {
+ switch (opline->opcode) {
+ case ZEND_INIT_FCALL_BY_NAME:
+ case ZEND_INIT_NS_FCALL_BY_NAME:
+ case ZEND_INIT_STATIC_METHOD_CALL:
+ case ZEND_INIT_METHOD_CALL:
+ case ZEND_INIT_FCALL:
+ if (call == 0) {
+ MAKE_NOP(opline);
+ return;
+ }
+ /* break missing intentionally */
+ case ZEND_NEW:
+ case ZEND_INIT_DYNAMIC_CALL:
+ case ZEND_INIT_USER_CALL:
+ call--;
+ break;
+ case ZEND_DO_FCALL:
+ case ZEND_DO_ICALL:
+ case ZEND_DO_UCALL:
+ case ZEND_DO_FCALL_BY_NAME:
+ call++;
+ break;
+ case ZEND_SEND_VAL:
+ case ZEND_SEND_VAR:
+ if (call == 0) {
+ if (opline->op1_type == IS_CONST) {
+ MAKE_NOP(opline);
+ } else if (opline->op1_type == IS_CV) {
+ opline->opcode = ZEND_CHECK_VAR;
+ opline->extended_value = 0;
+ opline->result.var = 0;
+ } else {
+ opline->opcode = ZEND_FREE;
+ opline->extended_value = 0;
+ opline->result.var = 0;
+ }
+ }
+ break;
+ }
+ opline--;
+ }
+}
+
+static void zend_try_inline_call(zend_op_array *op_array, zend_op *fcall, zend_op *opline, zend_function *func)
+{
+ if (func->type == ZEND_USER_FUNCTION
+ && !(func->op_array.fn_flags & (ZEND_ACC_ABSTRACT|ZEND_ACC_HAS_TYPE_HINTS))
+ /* TODO: function copied from trait may be inconsistent ??? */
+ && !(func->op_array.fn_flags & (ZEND_ACC_TRAIT_CLONE))
+ && fcall->extended_value >= func->op_array.required_num_args
+ && func->op_array.opcodes[func->op_array.num_args].opcode == ZEND_RETURN) {
+
+ zend_op *ret_opline = func->op_array.opcodes + func->op_array.num_args;
+
+ if (ret_opline->op1_type == IS_CONST) {
+ uint32_t i, num_args = func->op_array.num_args;
+ num_args += (func->op_array.fn_flags & ZEND_ACC_VARIADIC) != 0;
+
+ if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL
+ && !(func->op_array.fn_flags & ZEND_ACC_STATIC)) {
+ /* Don't inline static call to instance method. */
+ return;
+ }
+
+ for (i = 0; i < num_args; i++) {
+ /* Don't inline functions with by-reference arguments. This would require
+ * correct handling of INDIRECT arguments. */
+ if (ZEND_ARG_SEND_MODE(&func->op_array.arg_info[i])) {
+ return;
+ }
+ }
+
+ if (fcall->extended_value < func->op_array.num_args) {
+ /* don't inline functions with named constants in default arguments */
+ i = fcall->extended_value;
+
+ do {
+ if (Z_TYPE_P(CRT_CONSTANT_EX(&func->op_array, &func->op_array.opcodes[i], func->op_array.opcodes[i].op2)) == IS_CONSTANT_AST) {
+ return;
+ }
+ i++;
+ } while (i < func->op_array.num_args);
+ }
+
+ if (RETURN_VALUE_USED(opline)) {
+ zval zv;
+
+ ZVAL_COPY(&zv, CRT_CONSTANT_EX(&func->op_array, ret_opline, ret_opline->op1));
+ opline->opcode = ZEND_QM_ASSIGN;
+ opline->op1_type = IS_CONST;
+ opline->op1.constant = zend_optimizer_add_literal(op_array, &zv);
+ SET_UNUSED(opline->op2);
+ } else {
+ MAKE_NOP(opline);
+ }
+
+ zend_delete_call_instructions(opline-1);
+ }
+ }
+}
+
+void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
+{
+ zend_op *opline = op_array->opcodes;
+ zend_op *end = opline + op_array->last;
+ int call = 0;
+ void *checkpoint;
+ optimizer_call_info *call_stack;
+
+ if (op_array->last < 2) {
+ return;
+ }
+
+ checkpoint = zend_arena_checkpoint(ctx->arena);
+ call_stack = zend_arena_calloc(&ctx->arena, op_array->last / 2, sizeof(optimizer_call_info));
+ while (opline < end) {
+ switch (opline->opcode) {
+ case ZEND_INIT_FCALL_BY_NAME:
+ case ZEND_INIT_NS_FCALL_BY_NAME:
+ case ZEND_INIT_STATIC_METHOD_CALL:
+ case ZEND_INIT_METHOD_CALL:
+ case ZEND_INIT_FCALL:
+ case ZEND_NEW:
+ /* The argument passing optimizations are valid for prototypes as well,
+ * as inheritance cannot change between ref <-> non-ref arguments. */
+ call_stack[call].func = zend_optimizer_get_called_func(
+ ctx->script, op_array, opline, &call_stack[call].is_prototype);
+ call_stack[call].try_inline =
+ !call_stack[call].is_prototype && opline->opcode != ZEND_NEW;
+ /* break missing intentionally */
+ case ZEND_INIT_DYNAMIC_CALL:
+ case ZEND_INIT_USER_CALL:
+ call_stack[call].opline = opline;
+ call_stack[call].func_arg_num = (uint32_t)-1;
+ call++;
+ break;
+ case ZEND_DO_FCALL:
+ case ZEND_DO_ICALL:
+ case ZEND_DO_UCALL:
+ case ZEND_DO_FCALL_BY_NAME:
+ call--;
+ if (call_stack[call].func && call_stack[call].opline) {
+ zend_op *fcall = call_stack[call].opline;
+
+ if (fcall->opcode == ZEND_INIT_FCALL) {
+ /* nothing to do */
+ } else if (fcall->opcode == ZEND_INIT_FCALL_BY_NAME) {
+ fcall->opcode = ZEND_INIT_FCALL;
+ fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func);
+ literal_dtor(&ZEND_OP2_LITERAL(fcall));
+ fcall->op2.constant = fcall->op2.constant + 1;
+ opline->opcode = zend_get_call_op(fcall, call_stack[call].func);
+ } else if (fcall->opcode == ZEND_INIT_NS_FCALL_BY_NAME) {
+ fcall->opcode = ZEND_INIT_FCALL;
+ fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func);
+ literal_dtor(&op_array->literals[fcall->op2.constant]);
+ literal_dtor(&op_array->literals[fcall->op2.constant + 2]);
+ fcall->op2.constant = fcall->op2.constant + 1;
+ opline->opcode = zend_get_call_op(fcall, call_stack[call].func);
+ } else if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL
+ || fcall->opcode == ZEND_INIT_METHOD_CALL
+ || fcall->opcode == ZEND_NEW) {
+ /* We don't have specialized opcodes for this, do nothing */
+ } else {
+ ZEND_UNREACHABLE();
+ }
+
+ if ((ZEND_OPTIMIZER_PASS_16 & ctx->optimization_level)
+ && call_stack[call].try_inline) {
+ zend_try_inline_call(op_array, fcall, opline, call_stack[call].func);
+ }
+ }
+ call_stack[call].func = NULL;
+ call_stack[call].opline = NULL;
+ call_stack[call].try_inline = 0;
+ call_stack[call].func_arg_num = (uint32_t)-1;
+ break;
+ case ZEND_FETCH_FUNC_ARG:
+ case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
+ case ZEND_FETCH_OBJ_FUNC_ARG:
+ case ZEND_FETCH_DIM_FUNC_ARG:
+ if (call_stack[call - 1].func
+ && call_stack[call - 1].func_arg_num != (uint32_t)-1) {
+ if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, call_stack[call - 1].func_arg_num)) {
+ if (opline->opcode != ZEND_FETCH_STATIC_PROP_FUNC_ARG) {
+ opline->opcode -= 9;
+ } else {
+ opline->opcode = ZEND_FETCH_STATIC_PROP_W;
+ }
+ } else {
+ if (opline->opcode == ZEND_FETCH_DIM_FUNC_ARG
+ && opline->op2_type == IS_UNUSED) {
+ /* FETCH_DIM_FUNC_ARG supports UNUSED op2, while FETCH_DIM_R does not.
+ * Performing the replacement would create an invalid opcode. */
+ call_stack[call - 1].try_inline = 0;
+ break;
+ }
+
+ if (opline->opcode != ZEND_FETCH_STATIC_PROP_FUNC_ARG) {
+ opline->opcode -= 12;
+ } else {
+ opline->opcode = ZEND_FETCH_STATIC_PROP_R;
+ }
+ }
+ }
+ break;
+ case ZEND_SEND_VAL_EX:
+ if (call_stack[call - 1].func) {
+ if (opline->op2_type == IS_CONST) {
+ call_stack[call - 1].try_inline = 0;
+ break;
+ }
+
+ if (ARG_MUST_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
+ /* We won't convert it into_DO_FCALL to emit error at run-time */
+ call_stack[call - 1].opline = NULL;
+ } else {
+ opline->opcode = ZEND_SEND_VAL;
+ }
+ }
+ break;
+ case ZEND_CHECK_FUNC_ARG:
+ if (call_stack[call - 1].func) {
+ if (opline->op2_type == IS_CONST) {
+ call_stack[call - 1].try_inline = 0;
+ call_stack[call - 1].func_arg_num = (uint32_t)-1;
+ break;
+ }
+
+ call_stack[call - 1].func_arg_num = opline->op2.num;
+ MAKE_NOP(opline);
+ }
+ break;
+ case ZEND_SEND_VAR_EX:
+ case ZEND_SEND_FUNC_ARG:
+ if (call_stack[call - 1].func) {
+ if (opline->op2_type == IS_CONST) {
+ call_stack[call - 1].try_inline = 0;
+ break;
+ }
+
+ call_stack[call - 1].func_arg_num = (uint32_t)-1;
+ if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
+ opline->opcode = ZEND_SEND_REF;
+ } else {
+ opline->opcode = ZEND_SEND_VAR;
+ }
+ }
+ break;
+ case ZEND_SEND_VAR_NO_REF_EX:
+ if (call_stack[call - 1].func) {
+ if (opline->op2_type == IS_CONST) {
+ call_stack[call - 1].try_inline = 0;
+ break;
+ }
+
+ if (ARG_MUST_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
+ opline->opcode = ZEND_SEND_VAR_NO_REF;
+ } else if (ARG_MAY_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
+ opline->opcode = ZEND_SEND_VAL;
+ } else {
+ opline->opcode = ZEND_SEND_VAR;
+ }
+ }
+ break;
+ case ZEND_SEND_VAL:
+ case ZEND_SEND_VAR:
+ case ZEND_SEND_REF:
+ if (opline->op2_type == IS_CONST) {
+ call_stack[call - 1].try_inline = 0;
+ break;
+ }
+ break;
+ case ZEND_SEND_UNPACK:
+ case ZEND_SEND_USER:
+ case ZEND_SEND_ARRAY:
+ call_stack[call - 1].try_inline = 0;
+ break;
+ default:
+ break;
+ }
+ opline++;
+ }
+
+ zend_arena_release(&ctx->arena, checkpoint);
+}
diff --git a/Zend/Optimizer/optimize_temp_vars_5.c b/Zend/Optimizer/optimize_temp_vars_5.c
new file mode 100644
index 0000000000..6f7400159d
--- /dev/null
+++ b/Zend/Optimizer/optimize_temp_vars_5.c
@@ -0,0 +1,187 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend OPcache |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Andi Gutmans <andi@php.net> |
+ | Zeev Suraski <zeev@php.net> |
+ | Stanislav Malyshev <stas@zend.com> |
+ | Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#include "php.h"
+#include "Optimizer/zend_optimizer.h"
+#include "Optimizer/zend_optimizer_internal.h"
+#include "zend_API.h"
+#include "zend_constants.h"
+#include "zend_execute.h"
+#include "zend_vm.h"
+#include "zend_bitset.h"
+
+#define GET_AVAILABLE_T() \
+ for (i = 0; i < T; i++) { \
+ if (!zend_bitset_in(taken_T, i)) { \
+ break; \
+ } \
+ } \
+ zend_bitset_incl(taken_T, i); \
+ if (i > max) { \
+ max = i; \
+ }
+
+void zend_optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *ctx)
+{
+ int T = op_array->T;
+ int offset = op_array->last_var;
+ uint32_t bitset_len;
+ zend_bitset taken_T; /* T index in use */
+ zend_op **start_of_T; /* opline where T is first used */
+ zend_bitset valid_T; /* Is the map_T valid */
+ int *map_T; /* Map's the T to its new index */
+ zend_op *opline, *end;
+ int currT;
+ int i;
+ int max = -1;
+ void *checkpoint = zend_arena_checkpoint(ctx->arena);
+
+ bitset_len = zend_bitset_len(T);
+ taken_T = (zend_bitset) zend_arena_alloc(&ctx->arena, bitset_len * ZEND_BITSET_ELM_SIZE);
+ start_of_T = (zend_op **) zend_arena_alloc(&ctx->arena, T * sizeof(zend_op *));
+ valid_T = (zend_bitset) zend_arena_alloc(&ctx->arena, bitset_len * ZEND_BITSET_ELM_SIZE);
+ map_T = (int *) zend_arena_alloc(&ctx->arena, T * sizeof(int));
+
+ end = op_array->opcodes;
+ opline = &op_array->opcodes[op_array->last - 1];
+
+ /* Find T definition points */
+ while (opline >= end) {
+ if (opline->result_type & (IS_VAR | IS_TMP_VAR)) {
+ start_of_T[VAR_NUM(opline->result.var) - offset] = opline;
+ }
+ opline--;
+ }
+
+ zend_bitset_clear(valid_T, bitset_len);
+ zend_bitset_clear(taken_T, bitset_len);
+
+ end = op_array->opcodes;
+ opline = &op_array->opcodes[op_array->last - 1];
+
+ while (opline >= end) {
+ if ((opline->op1_type & (IS_VAR | IS_TMP_VAR))) {
+ currT = VAR_NUM(opline->op1.var) - offset;
+ if (opline->opcode == ZEND_ROPE_END) {
+ int num = (((opline->extended_value + 1) * sizeof(zend_string*)) + (sizeof(zval) - 1)) / sizeof(zval);
+ int var;
+
+ var = max;
+ while (var >= 0 && !zend_bitset_in(taken_T, var)) {
+ var--;
+ }
+ max = MAX(max, var + num);
+ var = var + 1;
+ map_T[currT] = var;
+ zend_bitset_incl(valid_T, currT);
+ zend_bitset_incl(taken_T, var);
+ opline->op1.var = NUM_VAR(var + offset);
+ while (num > 1) {
+ num--;
+ zend_bitset_incl(taken_T, var + num);
+ }
+ } else {
+ if (!zend_bitset_in(valid_T, currT)) {
+ int use_new_var = 0;
+
+ /* Code in "finally" blocks may modify temporary variables.
+ * We allocate new temporaries for values that need to
+ * relive FAST_CALLs.
+ */
+ if ((op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) &&
+ (opline->opcode == ZEND_RETURN ||
+ opline->opcode == ZEND_GENERATOR_RETURN ||
+ opline->opcode == ZEND_RETURN_BY_REF ||
+ opline->opcode == ZEND_FREE ||
+ opline->opcode == ZEND_FE_FREE)) {
+ zend_op *curr = opline;
+
+ while (--curr >= end) {
+ if (curr->opcode == ZEND_FAST_CALL) {
+ use_new_var = 1;
+ break;
+ } else if (curr->opcode != ZEND_FREE &&
+ curr->opcode != ZEND_FE_FREE &&
+ curr->opcode != ZEND_VERIFY_RETURN_TYPE &&
+ curr->opcode != ZEND_DISCARD_EXCEPTION) {
+ break;
+ }
+ }
+ }
+ if (use_new_var) {
+ i = ++max;
+ zend_bitset_incl(taken_T, i);
+ } else {
+ GET_AVAILABLE_T();
+ }
+ map_T[currT] = i;
+ zend_bitset_incl(valid_T, currT);
+ }
+ opline->op1.var = NUM_VAR(map_T[currT] + offset);
+ }
+ }
+
+ if ((opline->op2_type & (IS_VAR | IS_TMP_VAR))) {
+ currT = VAR_NUM(opline->op2.var) - offset;
+ if (!zend_bitset_in(valid_T, currT)) {
+ GET_AVAILABLE_T();
+ map_T[currT] = i;
+ zend_bitset_incl(valid_T, currT);
+ }
+ opline->op2.var = NUM_VAR(map_T[currT] + offset);
+ }
+
+ if (opline->result_type & (IS_VAR | IS_TMP_VAR)) {
+ currT = VAR_NUM(opline->result.var) - offset;
+ if (zend_bitset_in(valid_T, currT)) {
+ if (start_of_T[currT] == opline) {
+ /* ZEND_FAST_CALL can not share temporary var with others
+ * since the fast_var could also be set by ZEND_HANDLE_EXCEPTION
+ * which could be ahead of it */
+ if (opline->opcode != ZEND_FAST_CALL) {
+ zend_bitset_excl(taken_T, map_T[currT]);
+ }
+ }
+ opline->result.var = NUM_VAR(map_T[currT] + offset);
+ if (opline->opcode == ZEND_ROPE_INIT) {
+ if (start_of_T[currT] == opline) {
+ uint32_t num = ((opline->extended_value * sizeof(zend_string*)) + (sizeof(zval) - 1)) / sizeof(zval);
+ while (num > 1) {
+ num--;
+ zend_bitset_excl(taken_T, map_T[currT]+num);
+ }
+ }
+ }
+ } else {
+ /* Code which gets here is using a wrongly built opcode such as RECV() */
+ GET_AVAILABLE_T();
+ map_T[currT] = i;
+ zend_bitset_incl(valid_T, currT);
+ opline->result.var = NUM_VAR(i + offset);
+ }
+ }
+
+ opline--;
+ }
+
+ zend_arena_release(&ctx->arena, checkpoint);
+ op_array->T = max + 1;
+}
diff --git a/Zend/Optimizer/pass1.c b/Zend/Optimizer/pass1.c
new file mode 100644
index 0000000000..86774afef4
--- /dev/null
+++ b/Zend/Optimizer/pass1.c
@@ -0,0 +1,685 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend OPcache |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Andi Gutmans <andi@php.net> |
+ | Zeev Suraski <zeev@php.net> |
+ | Stanislav Malyshev <stas@zend.com> |
+ | Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+/* pass 1 (Simple local optimizations)
+ * - persistent constant substitution (true, false, null, etc)
+ * - constant casting (ADD expects numbers, CONCAT strings, etc)
+ * - constant expression evaluation
+ * - optimize constant conditional JMPs
+ * - pre-evaluate constant function calls
+ * - eliminate FETCH $GLOBALS followed by FETCH_DIM/UNSET_DIM/ISSET_ISEMPTY_DIM
+ */
+
+#include "php.h"
+#include "Optimizer/zend_optimizer.h"
+#include "Optimizer/zend_optimizer_internal.h"
+#include "zend_API.h"
+#include "zend_constants.h"
+#include "zend_execute.h"
+#include "zend_vm.h"
+
+void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
+{
+ zend_op *opline = op_array->opcodes;
+ zend_op *end = opline + op_array->last;
+ bool collect_constants = (ZEND_OPTIMIZER_PASS_15 & ctx->optimization_level)?
+ (op_array == &ctx->script->main_op_array) : 0;
+
+ while (opline < end) {
+ switch (opline->opcode) {
+ case ZEND_CONCAT:
+ case ZEND_FAST_CONCAT:
+ if (opline->op1_type == IS_CONST) {
+ if (Z_TYPE(ZEND_OP1_LITERAL(opline)) != IS_STRING) {
+ convert_to_string(&ZEND_OP1_LITERAL(opline));
+ }
+ }
+ if (opline->op2_type == IS_CONST) {
+ if (Z_TYPE(ZEND_OP2_LITERAL(opline)) != IS_STRING) {
+ convert_to_string(&ZEND_OP2_LITERAL(opline));
+ }
+ if (opline->op1_type == IS_CONST) {
+ goto constant_binary_op;
+ }
+ }
+ break;
+
+ case ZEND_ADD:
+ case ZEND_SUB:
+ case ZEND_MUL:
+ case ZEND_DIV:
+ case ZEND_POW:
+ case ZEND_MOD:
+ case ZEND_SL:
+ case ZEND_SR:
+ case ZEND_BW_OR:
+ case ZEND_BW_AND:
+ case ZEND_BW_XOR:
+ 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_BOOL_XOR:
+ case ZEND_SPACESHIP:
+ case ZEND_CASE:
+ case ZEND_CASE_STRICT:
+ if (opline->op1_type == IS_CONST &&
+ opline->op2_type == IS_CONST) {
+ /* binary operation with constant operands */
+ zval result;
+
+constant_binary_op:
+ if (zend_optimizer_eval_binary_op(&result, opline->opcode, &ZEND_OP1_LITERAL(opline), &ZEND_OP2_LITERAL(opline)) == SUCCESS) {
+ literal_dtor(&ZEND_OP1_LITERAL(opline));
+ literal_dtor(&ZEND_OP2_LITERAL(opline));
+ if (zend_optimizer_replace_by_const(op_array, opline + 1, IS_TMP_VAR, opline->result.var, &result)) {
+ MAKE_NOP(opline);
+ } else {
+ opline->opcode = ZEND_QM_ASSIGN;
+ SET_UNUSED(opline->op2);
+ zend_optimizer_update_op1_const(op_array, opline, &result);
+ }
+ }
+ }
+ break;
+
+ case ZEND_ASSIGN_OP:
+ if (opline->op2_type == IS_CONST) {
+ if (opline->extended_value == ZEND_ADD
+ || opline->extended_value == ZEND_SUB
+ || opline->extended_value == ZEND_MUL
+ || opline->extended_value == ZEND_DIV
+ || opline->extended_value == ZEND_POW) {
+ if (Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING) {
+ /* don't optimise if it should produce a runtime numeric string error */
+ if (is_numeric_string(Z_STRVAL(ZEND_OP2_LITERAL(opline)), Z_STRLEN(ZEND_OP2_LITERAL(opline)), NULL, NULL, 0)) {
+ convert_scalar_to_number(&ZEND_OP2_LITERAL(opline));
+ }
+ }
+ } else if (opline->extended_value == ZEND_MOD
+ || opline->extended_value == ZEND_SL
+ || opline->extended_value == ZEND_SR) {
+ if (Z_TYPE(ZEND_OP2_LITERAL(opline)) != IS_LONG) {
+ /* don't optimise if it should produce a runtime numeric string error */
+ if (!(Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING
+ && !is_numeric_string(Z_STRVAL(ZEND_OP2_LITERAL(opline)), Z_STRLEN(ZEND_OP2_LITERAL(opline)), NULL, NULL, 0))) {
+ convert_to_long(&ZEND_OP2_LITERAL(opline));
+ }
+ }
+ } else if (opline->extended_value == ZEND_CONCAT) {
+ if (Z_TYPE(ZEND_OP2_LITERAL(opline)) != IS_STRING) {
+ convert_to_string(&ZEND_OP2_LITERAL(opline));
+ }
+ }
+ }
+ break;
+
+ case ZEND_CAST:
+ if (opline->op1_type == IS_CONST) {
+ /* cast of constant operand */
+ zval result;
+
+ if (zend_optimizer_eval_cast(&result, opline->extended_value, &ZEND_OP1_LITERAL(opline)) == SUCCESS) {
+ literal_dtor(&ZEND_OP1_LITERAL(opline));
+ if (zend_optimizer_replace_by_const(op_array, opline + 1, opline->result_type, opline->result.var, &result)) {
+ MAKE_NOP(opline);
+ } else {
+ opline->opcode = ZEND_QM_ASSIGN;
+ opline->extended_value = 0;
+ zend_optimizer_update_op1_const(op_array, opline, &result);
+ }
+ break;
+ }
+ }
+ break;
+
+ case ZEND_BW_NOT:
+ case ZEND_BOOL_NOT:
+ if (opline->op1_type == IS_CONST) {
+ /* unary operation on constant operand */
+ zval result;
+
+ if (zend_optimizer_eval_unary_op(&result, opline->opcode, &ZEND_OP1_LITERAL(opline)) == SUCCESS) {
+ literal_dtor(&ZEND_OP1_LITERAL(opline));
+ if (zend_optimizer_replace_by_const(op_array, opline + 1, IS_TMP_VAR, opline->result.var, &result)) {
+ MAKE_NOP(opline);
+ } else {
+ opline->opcode = ZEND_QM_ASSIGN;
+ zend_optimizer_update_op1_const(op_array, opline, &result);
+ }
+ }
+ }
+ break;
+
+ case ZEND_FETCH_CONSTANT:
+ if (opline->op2_type == IS_CONST &&
+ Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING &&
+ Z_STRLEN(ZEND_OP2_LITERAL(opline)) == sizeof("__COMPILER_HALT_OFFSET__") - 1 &&
+ memcmp(Z_STRVAL(ZEND_OP2_LITERAL(opline)), "__COMPILER_HALT_OFFSET__", sizeof("__COMPILER_HALT_OFFSET__") - 1) == 0) {
+ /* substitute __COMPILER_HALT_OFFSET__ constant */
+ zend_execute_data *orig_execute_data = EG(current_execute_data);
+ zend_execute_data fake_execute_data;
+ zval *offset;
+
+ memset(&fake_execute_data, 0, sizeof(zend_execute_data));
+ fake_execute_data.func = (zend_function*)op_array;
+ EG(current_execute_data) = &fake_execute_data;
+ if ((offset = zend_get_constant_str("__COMPILER_HALT_OFFSET__", sizeof("__COMPILER_HALT_OFFSET__") - 1)) != NULL) {
+
+ literal_dtor(&ZEND_OP2_LITERAL(opline));
+ if (zend_optimizer_replace_by_const(op_array, opline, IS_TMP_VAR, opline->result.var, offset)) {
+ MAKE_NOP(opline);
+ } else {
+ opline->opcode = ZEND_QM_ASSIGN;
+ opline->extended_value = 0;
+ SET_UNUSED(opline->op2);
+ zend_optimizer_update_op1_const(op_array, opline, offset);
+ }
+ }
+ EG(current_execute_data) = orig_execute_data;
+ break;
+ }
+
+ if (opline->op2_type == IS_CONST &&
+ Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING) {
+ /* substitute persistent constants */
+ zval c;
+
+ if (!zend_optimizer_get_persistent_constant(Z_STR(ZEND_OP2_LITERAL(opline)), &c, 1)) {
+ if (!ctx->constants || !zend_optimizer_get_collected_constant(ctx->constants, &ZEND_OP2_LITERAL(opline), &c)) {
+ break;
+ }
+ }
+ if (Z_TYPE(c) == IS_CONSTANT_AST) {
+ break;
+ }
+ literal_dtor(&ZEND_OP2_LITERAL(opline));
+ if (zend_optimizer_replace_by_const(op_array, opline, IS_TMP_VAR, opline->result.var, &c)) {
+ MAKE_NOP(opline);
+ } else {
+ opline->opcode = ZEND_QM_ASSIGN;
+ opline->extended_value = 0;
+ SET_UNUSED(opline->op2);
+ zend_optimizer_update_op1_const(op_array, opline, &c);
+ }
+ }
+ break;
+
+ case ZEND_FETCH_CLASS_CONSTANT:
+ if (opline->op2_type == IS_CONST &&
+ Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING) {
+
+ zend_class_entry *ce = NULL;
+
+ if (opline->op1_type == IS_CONST &&
+ Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_STRING) {
+ /* for A::B */
+ if (op_array->scope &&
+ !strncasecmp(Z_STRVAL(ZEND_OP1_LITERAL(opline)),
+ ZSTR_VAL(op_array->scope->name), Z_STRLEN(ZEND_OP1_LITERAL(opline)) + 1)) {
+ ce = op_array->scope;
+ } else {
+ if ((ce = zend_hash_find_ptr(EG(class_table),
+ Z_STR(op_array->literals[opline->op1.constant + 1]))) == NULL ||
+ (ce->type == ZEND_INTERNAL_CLASS &&
+ ce->info.internal.module->type != MODULE_PERSISTENT) ||
+ (ce->type == ZEND_USER_CLASS &&
+ ce->info.user.filename != op_array->filename)) {
+ break;
+ }
+ }
+ } else if (op_array->scope &&
+ opline->op1_type == IS_UNUSED &&
+ (opline->op1.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_SELF) {
+ /* for self::B */
+ ce = op_array->scope;
+ } else if (op_array->scope &&
+ opline->op1_type == IS_VAR &&
+ (opline - 1)->opcode == ZEND_FETCH_CLASS &&
+ ((opline - 1)->op2_type == IS_UNUSED &&
+ ((opline - 1)->op1.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_SELF) &&
+ (opline - 1)->result.var == opline->op1.var) {
+ /* for self::B */
+ ce = op_array->scope;
+ }
+
+ if (ce) {
+ zend_class_constant *cc;
+ zval *c, t;
+
+ if ((cc = zend_hash_find_ptr(&ce->constants_table,
+ Z_STR(ZEND_OP2_LITERAL(opline)))) != NULL &&
+ (Z_ACCESS_FLAGS(cc->value) & ZEND_ACC_PPP_MASK) == ZEND_ACC_PUBLIC) {
+ c = &cc->value;
+ if (Z_TYPE_P(c) == IS_CONSTANT_AST) {
+ zend_ast *ast = Z_ASTVAL_P(c);
+ if (ast->kind != ZEND_AST_CONSTANT
+ || !zend_optimizer_get_persistent_constant(zend_ast_get_constant_name(ast), &t, 1)
+ || Z_TYPE(t) == IS_CONSTANT_AST) {
+ break;
+ }
+ } else {
+ ZVAL_COPY_OR_DUP(&t, c);
+ }
+
+ if (opline->op1_type == IS_CONST) {
+ literal_dtor(&ZEND_OP1_LITERAL(opline));
+ } else if (opline->op1_type == IS_VAR) {
+ MAKE_NOP((opline - 1));
+ }
+ literal_dtor(&ZEND_OP2_LITERAL(opline));
+
+ if (zend_optimizer_replace_by_const(op_array, opline, IS_TMP_VAR, opline->result.var, &t)) {
+ MAKE_NOP(opline);
+ } else {
+ opline->opcode = ZEND_QM_ASSIGN;
+ opline->extended_value = 0;
+ SET_UNUSED(opline->op2);
+ zend_optimizer_update_op1_const(op_array, opline, &t);
+ }
+ }
+ }
+ }
+ break;
+
+ case ZEND_DO_ICALL: {
+ zend_op *send1_opline = opline - 1;
+ zend_op *send2_opline = NULL;
+ zend_op *init_opline = NULL;
+
+ while (send1_opline->opcode == ZEND_NOP) {
+ send1_opline--;
+ }
+ if (send1_opline->opcode != ZEND_SEND_VAL ||
+ send1_opline->op1_type != IS_CONST) {
+ /* don't colllect constants after unknown function call */
+ collect_constants = 0;
+ break;
+ }
+ if (send1_opline->op2.num == 2) {
+ send2_opline = send1_opline;
+ send1_opline--;
+ while (send1_opline->opcode == ZEND_NOP) {
+ send1_opline--;
+ }
+ if (send1_opline->opcode != ZEND_SEND_VAL ||
+ send1_opline->op1_type != IS_CONST) {
+ /* don't colllect constants after unknown function call */
+ collect_constants = 0;
+ break;
+ }
+ }
+ init_opline = send1_opline - 1;
+ while (init_opline->opcode == ZEND_NOP) {
+ init_opline--;
+ }
+ if (init_opline->opcode != ZEND_INIT_FCALL ||
+ init_opline->op2_type != IS_CONST ||
+ Z_TYPE(ZEND_OP2_LITERAL(init_opline)) != IS_STRING) {
+ /* don't colllect constants after unknown function call */
+ collect_constants = 0;
+ break;
+ }
+
+ /* define("name", scalar); */
+ if (Z_STRLEN(ZEND_OP2_LITERAL(init_opline)) == sizeof("define")-1 &&
+ zend_binary_strcasecmp(Z_STRVAL(ZEND_OP2_LITERAL(init_opline)), Z_STRLEN(ZEND_OP2_LITERAL(init_opline)), "define", sizeof("define")-1) == 0) {
+
+ if (Z_TYPE(ZEND_OP1_LITERAL(send1_opline)) == IS_STRING &&
+ send2_opline &&
+ Z_TYPE(ZEND_OP1_LITERAL(send2_opline)) <= IS_STRING) {
+
+ if (collect_constants) {
+ zend_optimizer_collect_constant(ctx, &ZEND_OP1_LITERAL(send1_opline), &ZEND_OP1_LITERAL(send2_opline));
+ }
+
+ if (RESULT_UNUSED(opline) &&
+ !zend_memnstr(Z_STRVAL(ZEND_OP1_LITERAL(send1_opline)), "::", sizeof("::") - 1, Z_STRVAL(ZEND_OP1_LITERAL(send1_opline)) + Z_STRLEN(ZEND_OP1_LITERAL(send1_opline)))) {
+
+ opline->opcode = ZEND_DECLARE_CONST;
+ opline->op1_type = IS_CONST;
+ opline->op2_type = IS_CONST;
+ opline->result_type = IS_UNUSED;
+ opline->op1.constant = send1_opline->op1.constant;
+ opline->op2.constant = send2_opline->op1.constant;
+ opline->result.num = 0;
+
+ literal_dtor(&ZEND_OP2_LITERAL(init_opline));
+ MAKE_NOP(init_opline);
+ MAKE_NOP(send1_opline);
+ MAKE_NOP(send2_opline);
+ }
+ break;
+ }
+ }
+
+ /* pre-evaluate constant functions:
+ constant(x)
+ function_exists(x)
+ is_callable(x)
+ extension_loaded(x)
+ */
+ if (!send2_opline &&
+ Z_TYPE(ZEND_OP1_LITERAL(send1_opline)) == IS_STRING) {
+ if ((Z_STRLEN(ZEND_OP2_LITERAL(init_opline)) == sizeof("function_exists")-1 &&
+ !memcmp(Z_STRVAL(ZEND_OP2_LITERAL(init_opline)),
+ "function_exists", sizeof("function_exists")-1)) ||
+ (Z_STRLEN(ZEND_OP2_LITERAL(init_opline)) == sizeof("is_callable")-1 &&
+ !memcmp(Z_STRVAL(ZEND_OP2_LITERAL(init_opline)),
+ "is_callable", sizeof("is_callable")))) {
+ zend_internal_function *func;
+ zend_string *lc_name = zend_string_tolower(
+ Z_STR(ZEND_OP1_LITERAL(send1_opline)));
+
+ if ((func = zend_hash_find_ptr(EG(function_table), lc_name)) != NULL
+ && func->type == ZEND_INTERNAL_FUNCTION
+ && func->module->type == MODULE_PERSISTENT
+#ifdef ZEND_WIN32
+ && func->module->handle == NULL
+#endif
+ ) {
+ zval t;
+ ZVAL_TRUE(&t);
+ literal_dtor(&ZEND_OP2_LITERAL(init_opline));
+ MAKE_NOP(init_opline);
+ literal_dtor(&ZEND_OP1_LITERAL(send1_opline));
+ MAKE_NOP(send1_opline);
+ if (zend_optimizer_replace_by_const(op_array, opline + 1, IS_VAR, opline->result.var, &t)) {
+ MAKE_NOP(opline);
+ } else {
+ opline->opcode = ZEND_QM_ASSIGN;
+ opline->extended_value = 0;
+ SET_UNUSED(opline->op2);
+ zend_optimizer_update_op1_const(op_array, opline, &t);
+ }
+ }
+ zend_string_release_ex(lc_name, 0);
+ break;
+ } else if (Z_STRLEN(ZEND_OP2_LITERAL(init_opline)) == sizeof("extension_loaded")-1 &&
+ !memcmp(Z_STRVAL(ZEND_OP2_LITERAL(init_opline)),
+ "extension_loaded", sizeof("extension_loaded")-1)) {
+ zval t;
+ zend_string *lc_name = zend_string_tolower(
+ Z_STR(ZEND_OP1_LITERAL(send1_opline)));
+ zend_module_entry *m = zend_hash_find_ptr(&module_registry,
+ lc_name);
+
+ zend_string_release_ex(lc_name, 0);
+ if (!m) {
+ if (PG(enable_dl)) {
+ break;
+ } else {
+ ZVAL_FALSE(&t);
+ }
+ } else {
+ if (m->type == MODULE_PERSISTENT
+#ifdef ZEND_WIN32
+ && m->handle == NULL
+#endif
+ ) {
+ ZVAL_TRUE(&t);
+ } else {
+ break;
+ }
+ }
+
+ literal_dtor(&ZEND_OP2_LITERAL(init_opline));
+ MAKE_NOP(init_opline);
+ literal_dtor(&ZEND_OP1_LITERAL(send1_opline));
+ MAKE_NOP(send1_opline);
+ if (zend_optimizer_replace_by_const(op_array, opline + 1, IS_VAR, opline->result.var, &t)) {
+ MAKE_NOP(opline);
+ } else {
+ opline->opcode = ZEND_QM_ASSIGN;
+ opline->extended_value = 0;
+ SET_UNUSED(opline->op2);
+ zend_optimizer_update_op1_const(op_array, opline, &t);
+ }
+ break;
+ } else if (Z_STRLEN(ZEND_OP2_LITERAL(init_opline)) == sizeof("constant")-1 &&
+ !memcmp(Z_STRVAL(ZEND_OP2_LITERAL(init_opline)),
+ "constant", sizeof("constant")-1)) {
+ zval t;
+
+ if (zend_optimizer_get_persistent_constant(Z_STR(ZEND_OP1_LITERAL(send1_opline)), &t, 1)) {
+ literal_dtor(&ZEND_OP2_LITERAL(init_opline));
+ MAKE_NOP(init_opline);
+ literal_dtor(&ZEND_OP1_LITERAL(send1_opline));
+ MAKE_NOP(send1_opline);
+ if (zend_optimizer_replace_by_const(op_array, opline + 1, IS_VAR, opline->result.var, &t)) {
+ MAKE_NOP(opline);
+ } else {
+ opline->opcode = ZEND_QM_ASSIGN;
+ opline->extended_value = 0;
+ SET_UNUSED(opline->op2);
+ zend_optimizer_update_op1_const(op_array, opline, &t);
+ }
+ }
+ break;
+ /* dirname(IS_CONST/IS_STRING) -> IS_CONST/IS_STRING */
+ } else if (Z_STRLEN(ZEND_OP2_LITERAL(init_opline)) == sizeof("dirname")-1 &&
+ !memcmp(Z_STRVAL(ZEND_OP2_LITERAL(init_opline)),
+ "dirname", sizeof("dirname") - 1) &&
+ IS_ABSOLUTE_PATH(Z_STRVAL(ZEND_OP1_LITERAL(send1_opline)), Z_STRLEN(ZEND_OP1_LITERAL(send1_opline)))) {
+ zend_string *dirname = zend_string_init(Z_STRVAL(ZEND_OP1_LITERAL(send1_opline)), Z_STRLEN(ZEND_OP1_LITERAL(send1_opline)), 0);
+ ZSTR_LEN(dirname) = zend_dirname(ZSTR_VAL(dirname), ZSTR_LEN(dirname));
+ if (IS_ABSOLUTE_PATH(ZSTR_VAL(dirname), ZSTR_LEN(dirname))) {
+ zval t;
+
+ ZVAL_STR(&t, dirname);
+ literal_dtor(&ZEND_OP2_LITERAL(init_opline));
+ MAKE_NOP(init_opline);
+ literal_dtor(&ZEND_OP1_LITERAL(send1_opline));
+ MAKE_NOP(send1_opline);
+ if (zend_optimizer_replace_by_const(op_array, opline + 1, IS_VAR, opline->result.var, &t)) {
+ MAKE_NOP(opline);
+ } else {
+ opline->opcode = ZEND_QM_ASSIGN;
+ opline->extended_value = 0;
+ SET_UNUSED(opline->op2);
+ zend_optimizer_update_op1_const(op_array, opline, &t);
+ }
+ } else {
+ zend_string_release_ex(dirname, 0);
+ }
+ break;
+ }
+ }
+ /* don't colllect constants after any other function call */
+ collect_constants = 0;
+ break;
+ }
+ case ZEND_STRLEN:
+ if (opline->op1_type == IS_CONST) {
+ zval t;
+
+ if (zend_optimizer_eval_strlen(&t, &ZEND_OP1_LITERAL(opline)) == SUCCESS) {
+ literal_dtor(&ZEND_OP1_LITERAL(opline));
+ if (zend_optimizer_replace_by_const(op_array, opline + 1, IS_TMP_VAR, opline->result.var, &t)) {
+ MAKE_NOP(opline);
+ } else {
+ opline->opcode = ZEND_QM_ASSIGN;
+ zend_optimizer_update_op1_const(op_array, opline, &t);
+ }
+ }
+ }
+ break;
+ case ZEND_DEFINED:
+ {
+ zval c;
+ if (!zend_optimizer_get_persistent_constant(Z_STR(ZEND_OP1_LITERAL(opline)), &c, 0)) {
+ break;
+ }
+ ZVAL_TRUE(&c);
+ literal_dtor(&ZEND_OP1_LITERAL(opline));
+ if (zend_optimizer_replace_by_const(op_array, opline, IS_TMP_VAR, opline->result.var, &c)) {
+ MAKE_NOP(opline);
+ } else {
+ opline->opcode = ZEND_QM_ASSIGN;
+ zend_optimizer_update_op1_const(op_array, opline, &c);
+ }
+ }
+ break;
+ case ZEND_DECLARE_CONST:
+ if (collect_constants &&
+ Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_STRING &&
+ Z_TYPE(ZEND_OP2_LITERAL(opline)) <= IS_STRING) {
+ zend_optimizer_collect_constant(ctx, &ZEND_OP1_LITERAL(opline), &ZEND_OP2_LITERAL(opline));
+ }
+ break;
+#if 0
+ /* see ext/opcache/tests/bug78961.phpt */
+// case ZEND_FETCH_R:
+ case ZEND_FETCH_W:
+// case ZEND_FETCH_RW:
+ case ZEND_FETCH_IS:
+// case ZEND_FETCH_FUNC_ARG:
+ case ZEND_FETCH_UNSET:
+ /* convert FETCH $GLOBALS (global), FETCH_DIM $x into FETCH $x (global) */
+ if ((opline->extended_value & ZEND_FETCH_GLOBAL) != 0 &&
+ opline->op1_type == IS_CONST &&
+ Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_STRING &&
+ zend_string_equals_literal(Z_STR(ZEND_OP1_LITERAL(opline)), "GLOBALS") &&
+ ((opline + 1)->opcode == opline->opcode + 1 ||
+ ((opline + 1)->opcode == ZEND_UNSET_DIM &&
+ opline->opcode == ZEND_FETCH_UNSET) ||
+ ((opline + 1)->opcode == ZEND_ISSET_ISEMPTY_DIM_OBJ &&
+ opline->opcode == ZEND_FETCH_IS)) &&
+ (opline + 1)->op1_type == opline->result_type &&
+ (opline + 1)->op1.var == opline->result.var &&
+ ((opline + 1)->op2_type != IS_CONST ||
+ Z_TYPE(ZEND_OP2_LITERAL(opline + 1)) < IS_ARRAY)) {
+
+ if ((opline + 1)->opcode == ZEND_UNSET_DIM) {
+ (opline + 1)->opcode = ZEND_UNSET_VAR;
+ (opline + 1)->extended_value = ZEND_FETCH_GLOBAL;
+ } else if ((opline + 1)->opcode == ZEND_ISSET_ISEMPTY_DIM_OBJ) {
+ (opline + 1)->opcode = ZEND_ISSET_ISEMPTY_VAR;
+ (opline + 1)->extended_value |= ZEND_FETCH_GLOBAL;
+ } else {
+ (opline + 1)->opcode = opline->opcode;
+ (opline + 1)->extended_value = ZEND_FETCH_GLOBAL;
+ }
+ (opline + 1)->op1_type = (opline + 1)->op2_type;
+ (opline + 1)->op1 = (opline + 1)->op2;
+ if ((opline + 1)->op1_type == IS_CONST &&
+ Z_TYPE(ZEND_OP1_LITERAL(opline + 1)) != IS_STRING) {
+
+ convert_to_string(&ZEND_OP1_LITERAL(opline + 1));
+ zend_string_hash_val(Z_STR(ZEND_OP1_LITERAL(opline + 1)));
+ }
+ SET_UNUSED((opline + 1)->op2);
+ MAKE_NOP(opline);
+ }
+ break;
+#endif
+
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ /* convert Ti = JMPZ_EX(C, L) => Ti = QM_ASSIGN(C)
+ in case we know it wouldn't jump */
+ if (opline->op1_type == IS_CONST) {
+ if (zend_is_true(&ZEND_OP1_LITERAL(opline))) {
+ if (opline->opcode == ZEND_JMPZ_EX) {
+ opline->opcode = ZEND_QM_ASSIGN;
+ zval_ptr_dtor_nogc(&ZEND_OP1_LITERAL(opline));
+ ZVAL_TRUE(&ZEND_OP1_LITERAL(opline));
+ opline->op2.num = 0;
+ break;
+ }
+ } else {
+ if (opline->opcode == ZEND_JMPNZ_EX) {
+ opline->opcode = ZEND_QM_ASSIGN;
+ zval_ptr_dtor_nogc(&ZEND_OP1_LITERAL(opline));
+ ZVAL_FALSE(&ZEND_OP1_LITERAL(opline));
+ opline->op2.num = 0;
+ break;
+ }
+ }
+ }
+ collect_constants = 0;
+ break;
+
+ case ZEND_JMPZ:
+ case ZEND_JMPNZ:
+ if (opline->op1_type == IS_CONST) {
+ int should_jmp = zend_is_true(&ZEND_OP1_LITERAL(opline));
+
+ if (opline->opcode == ZEND_JMPZ) {
+ should_jmp = !should_jmp;
+ }
+ literal_dtor(&ZEND_OP1_LITERAL(opline));
+ opline->op1_type = IS_UNUSED;
+ if (should_jmp) {
+ opline->opcode = ZEND_JMP;
+ COPY_NODE(opline->op1, opline->op2);
+ opline->op2.num = 0;
+ } else {
+ MAKE_NOP(opline);
+ break;
+ }
+ }
+ collect_constants = 0;
+ break;
+
+ case ZEND_JMPZNZ:
+ if (opline->op1_type == IS_CONST) {
+ zend_op *target_opline;
+
+ if (zend_is_true(&ZEND_OP1_LITERAL(opline))) {
+ target_opline = ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value); /* JMPNZ */
+ } else {
+ target_opline = ZEND_OP2_JMP_ADDR(opline); /* JMPZ */
+ }
+ literal_dtor(&ZEND_OP1_LITERAL(opline));
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target_opline);
+ opline->op1_type = IS_UNUSED;
+ opline->opcode = ZEND_JMP;
+ }
+ collect_constants = 0;
+ break;
+
+ case ZEND_RETURN:
+ case ZEND_RETURN_BY_REF:
+ case ZEND_GENERATOR_RETURN:
+ case ZEND_EXIT:
+ case ZEND_THROW:
+ case ZEND_MATCH_ERROR:
+ case ZEND_CATCH:
+ case ZEND_FAST_CALL:
+ case ZEND_FAST_RET:
+ case ZEND_JMP:
+ case ZEND_FE_RESET_R:
+ case ZEND_FE_RESET_RW:
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ case ZEND_ASSERT_CHECK:
+ case ZEND_JMP_NULL:
+ collect_constants = 0;
+ break;
+ }
+ opline++;
+ }
+}
diff --git a/Zend/Optimizer/pass3.c b/Zend/Optimizer/pass3.c
new file mode 100644
index 0000000000..f98c41848c
--- /dev/null
+++ b/Zend/Optimizer/pass3.c
@@ -0,0 +1,356 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend OPcache |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Andi Gutmans <andi@php.net> |
+ | Zeev Suraski <zeev@php.net> |
+ | Stanislav Malyshev <stas@zend.com> |
+ | Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+/* pass 3: (Jump optimization)
+ * - optimize series of JMPs
+ */
+
+#include "php.h"
+#include "Optimizer/zend_optimizer.h"
+#include "Optimizer/zend_optimizer_internal.h"
+#include "zend_API.h"
+#include "zend_constants.h"
+#include "zend_execute.h"
+#include "zend_vm.h"
+
+/* we use "jmp_hitlist" to avoid infinity loops during jmp optimization */
+static zend_always_inline int in_hitlist(zend_op *target, zend_op **jmp_hitlist, int jmp_hitlist_count)
+{
+ int i;
+
+ for (i = 0; i < jmp_hitlist_count; i++) {
+ if (jmp_hitlist[i] == target) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+#define CHECK_LOOP(target) \
+ if (EXPECTED(!in_hitlist(target, jmp_hitlist, jmp_hitlist_count))) { \
+ jmp_hitlist[jmp_hitlist_count++] = target; \
+ } else { \
+ break; \
+ }
+
+void zend_optimizer_pass3(zend_op_array *op_array, zend_optimizer_ctx *ctx)
+{
+ zend_op *opline;
+ zend_op *end;
+ zend_op *target;
+ zend_op **jmp_hitlist;
+ int jmp_hitlist_count;
+ ALLOCA_FLAG(use_heap);
+
+ jmp_hitlist = (zend_op**)do_alloca(sizeof(zend_op*)*op_array->last, use_heap);
+ opline = op_array->opcodes;
+ end = opline + op_array->last;
+
+ while (opline < end) {
+
+ switch (opline->opcode) {
+ case ZEND_JMP:
+ jmp_hitlist_count = 0;
+
+ target = ZEND_OP1_JMP_ADDR(opline);
+ while (1) {
+ if (target->opcode == ZEND_JMP) {
+ /* convert JMP L1 ... L1: JMP L2 to JMP L2 .. L1: JMP L2 */
+ target = ZEND_OP1_JMP_ADDR(target);
+ CHECK_LOOP(target);
+ } else if (target->opcode == ZEND_NOP) {
+ target = target + 1;
+ } else {
+ break;
+ }
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target);
+ }
+
+ if (target == opline + 1) {
+ /* convert L: JMP L+1 to NOP */
+ MAKE_NOP(opline);
+ } else if (target->opcode == ZEND_JMPZNZ) {
+ /* JMP L, L: JMPZNZ L1,L2 -> JMPZNZ L1,L2 */
+ *opline = *target;
+ if (opline->op1_type == IS_CONST) {
+ zval zv;
+ ZVAL_COPY(&zv, &ZEND_OP1_LITERAL(opline));
+ opline->op1.constant = zend_optimizer_add_literal(op_array, &zv);
+ }
+ goto optimize_jmpznz;
+ } else if ((target->opcode == ZEND_RETURN ||
+ target->opcode == ZEND_RETURN_BY_REF ||
+ target->opcode == ZEND_GENERATOR_RETURN ||
+ target->opcode == ZEND_EXIT) &&
+ !(op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK)) {
+ /* JMP L, L: RETURN to immediate RETURN */
+ *opline = *target;
+ if (opline->op1_type == IS_CONST) {
+ zval zv;
+ ZVAL_COPY(&zv, &ZEND_OP1_LITERAL(opline));
+ opline->op1.constant = zend_optimizer_add_literal(op_array, &zv);
+ }
+ } else if (opline > op_array->opcodes &&
+ ((opline-1)->opcode == ZEND_JMPZ ||
+ (opline-1)->opcode == ZEND_JMPNZ)) {
+ if (ZEND_OP2_JMP_ADDR(opline-1) == target) {
+ /* JMPZ(X,L1), JMP(L1) -> NOP, JMP(L1) */
+ if ((opline-1)->op1_type == IS_CV) {
+ (opline-1)->opcode = ZEND_CHECK_VAR;
+ (opline-1)->op2.num = 0;
+ } else if ((opline-1)->op1_type & (IS_TMP_VAR|IS_VAR)) {
+ (opline-1)->opcode = ZEND_FREE;
+ (opline-1)->op2.num = 0;
+ } else {
+ MAKE_NOP(opline-1);
+ }
+ } else {
+ /* JMPZ(X,L1), JMP(L2) -> JMPZNZ(X,L1,L2) */
+ if ((opline-1)->opcode == ZEND_JMPZ) {
+ (opline-1)->extended_value = ZEND_OPLINE_TO_OFFSET((opline-1), target);
+ } else {
+ (opline-1)->extended_value = ZEND_OPLINE_TO_OFFSET((opline-1), ZEND_OP2_JMP_ADDR(opline-1));
+ ZEND_SET_OP_JMP_ADDR((opline-1), (opline-1)->op2, target);
+ }
+ (opline-1)->opcode = ZEND_JMPZNZ;
+ }
+ }
+ break;
+
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ jmp_hitlist_count = 0;
+
+ target = ZEND_OP2_JMP_ADDR(opline);
+ while (1) {
+ if (target->opcode == ZEND_JMP) {
+ target = ZEND_OP1_JMP_ADDR(target);
+ CHECK_LOOP(target);
+ } else if (target->opcode == ZEND_NOP) {
+ target = target + 1;
+ } else {
+ break;
+ }
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op2, target);
+ }
+ break;
+
+ case ZEND_JMPZ:
+ case ZEND_JMPNZ:
+ jmp_hitlist_count = 0;
+
+ target = ZEND_OP2_JMP_ADDR(opline);
+ while (1) {
+ if (target->opcode == ZEND_JMP) {
+ /* plain JMP */
+ /* JMPZ(X,L1), L1: JMP(L2) => JMPZ(X,L2), L1: JMP(L2) */
+ target = ZEND_OP1_JMP_ADDR(target);
+ CHECK_LOOP(target);
+ } else if (target->opcode == opline->opcode &&
+ SAME_VAR(opline->op1, target->op1)) {
+ /* same opcode and same var as this opcode */
+ /* JMPZ(X,L1), L1: JMPZ(X,L2) => JMPZ(X,L2), L1: JMPZ(X,L2) */
+ target = ZEND_OP2_JMP_ADDR(target);
+ CHECK_LOOP(target);
+ } else if (target->opcode == INV_COND(opline->opcode) &&
+ SAME_VAR(opline->op1, target->op1)) {
+ /* convert JMPZ(X,L1), L1: JMPNZ(X,L2) to
+ JMPZ(X,L1+1) */
+ target = target + 1;
+ } else if (target->opcode == ZEND_JMPZNZ &&
+ SAME_VAR(opline->op1, target->op1)) {
+ target = (opline->opcode == ZEND_JMPZ) ?
+ ZEND_OP2_JMP_ADDR(target) :
+ ZEND_OFFSET_TO_OPLINE(target, target->extended_value);
+ CHECK_LOOP(target);
+ } else if (target->opcode == ZEND_NOP) {
+ target = target + 1;
+ } else {
+ break;
+ }
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op2, target);
+ }
+
+ /* convert L: JMPZ L+1 to NOP */
+ if (target == opline + 1) {
+ if (opline->op1_type == IS_CV) {
+ opline->opcode = ZEND_CHECK_VAR;
+ opline->op2.num = 0;
+ } else if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
+ opline->opcode = ZEND_FREE;
+ opline->op2.num = 0;
+ } else {
+ MAKE_NOP(opline);
+ }
+ }
+ break;
+
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ jmp_hitlist_count = 0;
+
+ target = ZEND_OP2_JMP_ADDR(opline);
+ while (1) {
+ if (target->opcode == ZEND_JMP) {
+ /* plain JMP */
+ /* JMPZ_EX(X,L1), L1: JMP(L2) => JMPZ_EX(X,L2), L1: JMP(L2) */
+ target = ZEND_OP1_JMP_ADDR(target);
+ CHECK_LOOP(target);
+ } else if (target->opcode == opline->opcode-3 &&
+ (SAME_VAR(target->op1, opline->result) ||
+ SAME_VAR(target->op1, opline->op1))) {
+ /* convert T=JMPZ_EX(X,L1), L1: JMPZ(T,L2) to
+ JMPZ_EX(X,L2) */
+ target = ZEND_OP2_JMP_ADDR(target);
+ CHECK_LOOP(target);
+ } else if (target->opcode == opline->opcode &&
+ target->result.var == opline->result.var &&
+ (SAME_VAR(target->op1, opline->result) ||
+ SAME_VAR(target->op1, opline->op1))) {
+ /* convert T=JMPZ_EX(X,L1), L1: T=JMPZ_EX(T,L2) to
+ JMPZ_EX(X,L2) */
+ target = ZEND_OP2_JMP_ADDR(target);
+ CHECK_LOOP(target);
+ } else if (target->opcode == ZEND_JMPZNZ &&
+ (SAME_VAR(target->op1, opline->result) ||
+ SAME_VAR(target->op1, opline->op1))) {
+ /* Check for JMPZNZ with same cond variable */
+ target = (opline->opcode == ZEND_JMPZ_EX) ?
+ ZEND_OP2_JMP_ADDR(target) :
+ ZEND_OFFSET_TO_OPLINE(target, target->extended_value);
+ CHECK_LOOP(target);
+ } else if (target->opcode == INV_EX_COND(opline->opcode) &&
+ (SAME_VAR(target->op1, opline->result) ||
+ SAME_VAR(target->op1, opline->op1))) {
+ /* convert T=JMPZ_EX(X,L1), L1: JMPNZ(T,L2) to
+ JMPZ_EX(X,L1+1) */
+ target = target + 1;
+ } else if (target->opcode == INV_EX_COND_EX(opline->opcode) &&
+ target->result.var == opline->result.var &&
+ (SAME_VAR(target->op1, opline->result) ||
+ SAME_VAR(target->op1, opline->op1))) {
+ /* convert T=JMPZ_EX(X,L1), L1: T=JMPNZ_EX(T,L2) to
+ JMPZ_EX(X,L1+1) */
+ target = target + 1;
+ } else if (target->opcode == ZEND_BOOL &&
+ (SAME_VAR(target->op1, opline->result) ||
+ SAME_VAR(target->op1, opline->op1))) {
+ /* convert Y = JMPZ_EX(X,L1), L1: Z = BOOL(Y) to
+ Z = JMPZ_EX(X,L1+1) */
+
+ /* NOTE: This optimization pattern is not safe, but works, */
+ /* because result of JMPZ_EX instruction */
+ /* is not used on the following path and */
+ /* should be used once on the branch path. */
+ /* */
+ /* The pattern works well only if jums processed in */
+ /* direct order, otherwise it breaks JMPZ_EX */
+ /* sequences too early. */
+ opline->result.var = target->result.var;
+ target = target + 1;
+ CHECK_LOOP(target);
+ } else if (target->opcode == ZEND_NOP) {
+ target = target + 1;
+ } else {
+ break;
+ }
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op2, target);
+ }
+
+ /* convert L: T = JMPZ_EX X,L+1 to T = BOOL(X) */
+ if (target == opline + 1) {
+ opline->opcode = ZEND_BOOL;
+ opline->op2.num = 0;
+ }
+ break;
+
+ case ZEND_JMPZNZ:
+optimize_jmpznz:
+ jmp_hitlist_count = 0;
+ target = ZEND_OP2_JMP_ADDR(opline);
+ while (1) {
+ if (target->opcode == ZEND_JMP) {
+ /* JMPZNZ(X,L1,L2), L1: JMP(L3) => JMPZNZ(X,L3,L2), L1: JMP(L3) */
+ target = ZEND_OP1_JMP_ADDR(target);
+ CHECK_LOOP(target);
+ } else if ((target->opcode == ZEND_JMPZ || target->opcode == ZEND_JMPZNZ) &&
+ SAME_VAR(target->op1, opline->op1)) {
+ /* JMPZNZ(X, L1, L2), L1: JMPZ(X, L3) -> JMPZNZ(X, L3, L2) */
+ target = ZEND_OP2_JMP_ADDR(target);
+ CHECK_LOOP(target);
+ } else if (target->opcode == ZEND_JMPNZ &&
+ SAME_VAR(target->op1, opline->op1)) {
+ /* JMPZNZ(X, L1, L2), L1: X = JMPNZ(X, L3) -> JMPZNZ(X, L1+1, L2) */
+ target = target + 1;
+ } else if (target->opcode == ZEND_NOP) {
+ target = target + 1;
+ } else {
+ break;
+ }
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op2, target);
+ }
+
+ jmp_hitlist_count = 0;
+ target = ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value);
+ while (1) {
+ if (target->opcode == ZEND_JMP) {
+ /* JMPZNZ(X,L1,L2), L2: JMP(L3) => JMPZNZ(X,L1,L3), L2: JMP(L3) */
+ target = ZEND_OP1_JMP_ADDR(target);
+ CHECK_LOOP(target);
+ } else if (target->opcode == ZEND_JMPNZ &&
+ SAME_VAR(target->op1, opline->op1)) {
+ /* JMPZNZ(X, L1, L2), L1: X = JMPNZ(X, L3) -> JMPZNZ(X, L1+1, L2) */
+ target = ZEND_OP2_JMP_ADDR(target);
+ CHECK_LOOP(target);
+ } else if (target->opcode == ZEND_JMPZ &&
+ SAME_VAR(target->op1, opline->op1)) {
+ /* JMPZNZ(X, L1, L2), L1: JMPZ(X, L3) -> JMPZNZ(X, L3, L2) */
+ target = target + 1;
+ } else if (target->opcode == ZEND_JMPZNZ &&
+ SAME_VAR(target->op1, opline->op1)) {
+ /* JMPZNZ(X, L1, L2), L1: JMPZ(X, L3) -> JMPZNZ(X, L3, L2) */
+ target = ZEND_OFFSET_TO_OPLINE(target, target->extended_value);
+ CHECK_LOOP(target);
+ } else if (target->opcode == ZEND_NOP) {
+ target = target + 1;
+ } else {
+ break;
+ }
+ opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, target);
+ }
+
+ if (ZEND_OP2_JMP_ADDR(opline) == target &&
+ !(opline->op1_type & (IS_VAR|IS_TMP_VAR))) {
+ /* JMPZNZ(?,L,L) -> JMP(L) */
+ opline->opcode = ZEND_JMP;
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target);
+ SET_UNUSED(opline->op1);
+ SET_UNUSED(opline->op2);
+ opline->extended_value = 0;
+ }
+ /* Don't convert JMPZNZ back to JMPZ/JMPNZ, because the
+ following JMP is not removed yet. */
+ break;
+ }
+ opline++;
+ }
+ free_alloca(jmp_hitlist, use_heap);
+}
diff --git a/Zend/Optimizer/sccp.c b/Zend/Optimizer/sccp.c
new file mode 100644
index 0000000000..e097f654c8
--- /dev/null
+++ b/Zend/Optimizer/sccp.c
@@ -0,0 +1,2528 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, SCCP - Sparse Conditional Constant Propagation |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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> |
+ | Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#include "php.h"
+#include "zend_type_info.h"
+#include "Optimizer/zend_optimizer_internal.h"
+#include "Optimizer/zend_call_graph.h"
+#include "Optimizer/zend_inference.h"
+#include "Optimizer/scdf.h"
+#include "Optimizer/zend_dump.h"
+#include "ext/standard/php_string.h"
+#include "zend_exceptions.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).
+ *
+ * The original SCCP algorithm is extended with ability to propagate constant array
+ * elements and object properties. The extension is based on a variation of Array
+ * SSA form and its application to Spare Constant Propagation, described at
+ * "Array SSA Form" by Vivek Sarkar, Kathleen Knobe and Stephen Fink in chapter
+ * 16 of the SSA book.
+ */
+
+#define SCP_DEBUG 0
+
+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 PARTIAL_ARRAY ((zend_uchar)-3)
+#define PARTIAL_OBJECT ((zend_uchar)-4)
+#define IS_TOP(zv) (Z_TYPE_P(zv) == TOP)
+#define IS_BOT(zv) (Z_TYPE_P(zv) == BOT)
+#define IS_PARTIAL_ARRAY(zv) (Z_TYPE_P(zv) == PARTIAL_ARRAY)
+#define IS_PARTIAL_OBJECT(zv) (Z_TYPE_P(zv) == PARTIAL_OBJECT)
+
+#define MAKE_PARTIAL_ARRAY(zv) (Z_TYPE_INFO_P(zv) = PARTIAL_ARRAY | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT))
+#define MAKE_PARTIAL_OBJECT(zv) (Z_TYPE_INFO_P(zv) = PARTIAL_OBJECT | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT))
+
+#define MAKE_TOP(zv) (Z_TYPE_INFO_P(zv) = TOP)
+#define MAKE_BOT(zv) (Z_TYPE_INFO_P(zv) = BOT)
+
+static void scp_dump_value(zval *zv) {
+ if (IS_TOP(zv)) {
+ fprintf(stderr, " top");
+ } else if (IS_BOT(zv)) {
+ fprintf(stderr, " bot");
+ } else if (Z_TYPE_P(zv) == IS_ARRAY || IS_PARTIAL_ARRAY(zv)) {
+ fprintf(stderr, " %s[", IS_PARTIAL_ARRAY(zv) ? "partial " : "");
+ zend_dump_ht(Z_ARRVAL_P(zv));
+ fprintf(stderr, "]");
+ } else if (IS_PARTIAL_OBJECT(zv)) {
+ fprintf(stderr, " {");
+ zend_dump_ht(Z_ARRVAL_P(zv));
+ fprintf(stderr, "}");
+ } else {
+ zend_dump_const(zv);
+ }
+}
+
+static void empty_partial_array(zval *zv)
+{
+ MAKE_PARTIAL_ARRAY(zv);
+ Z_ARR_P(zv) = zend_new_array(8);
+}
+
+static void dup_partial_array(zval *dst, zval *src)
+{
+ MAKE_PARTIAL_ARRAY(dst);
+ Z_ARR_P(dst) = zend_array_dup(Z_ARR_P(src));
+}
+
+static void empty_partial_object(zval *zv)
+{
+ MAKE_PARTIAL_OBJECT(zv);
+ Z_ARR_P(zv) = zend_new_array(8);
+}
+
+static void dup_partial_object(zval *dst, zval *src)
+{
+ MAKE_PARTIAL_OBJECT(dst);
+ Z_ARR_P(dst) = zend_array_dup(Z_ARR_P(src));
+}
+
+static inline 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 SCP_DEBUG
+ fprintf(stderr, "Lowering #%d.", var);
+ zend_dump_var(scdf->op_array, IS_CV, scdf->ssa->vars[var].var);
+ fprintf(stderr, " from");
+ scp_dump_value(value);
+ fprintf(stderr, " to");
+ scp_dump_value(new);
+ fprintf(stderr, "\n");
+#endif
+
+ if (IS_TOP(value) || IS_BOT(new)) {
+ zval_ptr_dtor_nogc(value);
+ ZVAL_COPY(value, new);
+ scdf_add_to_worklist(scdf, var);
+ return;
+ }
+
+ /* Always replace PARTIAL_(ARRAY|OBJECT), as new maybe changed by join_partial_(arrays|object) */
+ if (IS_PARTIAL_ARRAY(new) || IS_PARTIAL_OBJECT(new)) {
+ if (Z_TYPE_P(value) != Z_TYPE_P(new)
+ || zend_hash_num_elements(Z_ARR_P(new)) != zend_hash_num_elements(Z_ARR_P(value))) {
+ 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 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_OBJ_REF:
+ case ZEND_ASSIGN_OP:
+ case ZEND_ASSIGN_DIM_OP:
+ case ZEND_ASSIGN_OBJ_OP:
+ case ZEND_ASSIGN_STATIC_PROP_OP:
+ 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_FETCH_LIST_W:
+ case ZEND_UNSET_DIM:
+ case ZEND_UNSET_OBJ:
+ case ZEND_SEND_REF:
+ case ZEND_SEND_VAR_EX:
+ case ZEND_SEND_FUNC_ARG:
+ 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_ROPE_ADD:
+ case ZEND_ROPE_END:
+ case ZEND_BIND_STATIC:
+ case ZEND_BIND_GLOBAL:
+ case ZEND_MAKE_REF:
+ case ZEND_UNSET_CV:
+ case ZEND_ISSET_ISEMPTY_CV:
+ return 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;
+ case ZEND_OP_DATA:
+ return (opline - 1)->opcode != ZEND_ASSIGN_OBJ_REF &&
+ (opline - 1)->opcode != ZEND_ASSIGN_STATIC_PROP_REF;
+ default:
+ if (ssa_op->op1_def != -1) {
+ ZEND_UNREACHABLE();
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static 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_CLASS_DELAYED:
+ case ZEND_BIND_LEXICAL:
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ return 0;
+ }
+ return 1;
+}
+
+static 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_CASE:
+ opline->opcode = ZEND_IS_EQUAL;
+ goto replace_op1_simple;
+ case ZEND_CASE_STRICT:
+ opline->opcode = ZEND_IS_IDENTICAL;
+ goto replace_op1_simple;
+ case ZEND_FETCH_LIST_R:
+ case ZEND_SWITCH_STRING:
+ case ZEND_SWITCH_LONG:
+ case ZEND_MATCH:
+replace_op1_simple:
+ 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;
+ case ZEND_INSTANCEOF:
+ zval_ptr_dtor_nogc(&zv);
+ ZVAL_FALSE(&zv);
+ opline->opcode = ZEND_QM_ASSIGN;
+ opline->op1_type = IS_CONST;
+ opline->op1.constant = zend_optimizer_add_literal(ctx->scdf.op_array, &zv);
+ opline->op2_type = IS_UNUSED;
+ if (ssa_op->op2_use >= 0) {
+ ZEND_ASSERT(ssa_op->op2_def == -1);
+ zend_ssa_unlink_use_chain(ctx->scdf.ssa, ssa_op - ctx->scdf.ssa->ops, ssa_op->op2_use);
+ ssa_op->op2_use = -1;
+ ssa_op->op2_use_chain = -1;
+ }
+ return 1;
+ default:
+ break;
+ }
+ zval_ptr_dtor_nogc(&zv);
+ }
+ }
+ return 0;
+}
+
+static 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 {
+ switch (opline->opcode) {
+ case ZEND_FETCH_CLASS:
+ if (Z_TYPE(zv) == IS_STRING) {
+ ZEND_ASSERT((opline + 1)->opcode == ZEND_INSTANCEOF);
+ ZEND_ASSERT(ssa_op->result_def == (ssa_op + 1)->op2_use);
+ if (zend_optimizer_update_op2_const(ctx->scdf.op_array, opline + 1, &zv)) {
+ zend_ssa_op *next_op = ssa_op + 1;
+ zend_ssa_unlink_use_chain(ctx->scdf.ssa, next_op - ctx->scdf.ssa->ops, next_op->op2_use);
+ next_op->op2_use = -1;
+ next_op->op2_use_chain = -1;
+ zend_ssa_remove_result_def(ctx->scdf.ssa, ssa_op);
+ MAKE_NOP(opline);
+ return 1;
+ }
+ }
+ default:
+ break;
+ }
+ zval_ptr_dtor_nogc(&zv);
+ }
+ }
+ return 0;
+}
+
+static inline int ct_eval_binary_op(zval *result, zend_uchar binop, zval *op1, zval *op2) {
+ /* TODO: We could implement support for evaluation of + on partial arrays. */
+ if (IS_PARTIAL_ARRAY(op1) || IS_PARTIAL_ARRAY(op2)) {
+ return FAILURE;
+ }
+
+ return zend_optimizer_eval_binary_op(result, binop, op1, op2);
+}
+
+static inline int ct_eval_bool_cast(zval *result, zval *op) {
+ if (IS_PARTIAL_ARRAY(op)) {
+ if (zend_hash_num_elements(Z_ARRVAL_P(op)) == 0) {
+ /* An empty partial array may be non-empty at runtime, we don't know whether the
+ * result will be true or false. */
+ return FAILURE;
+ }
+
+ ZVAL_TRUE(result);
+ return SUCCESS;
+ }
+
+ ZVAL_BOOL(result, zend_is_true(op));
+ return SUCCESS;
+}
+
+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 || IS_PARTIAL_ARRAY(op1)) {
+ zval *value;
+ if (fetch_array_elem(&value, op1, op2) == SUCCESS && value && !IS_BOT(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;
+}
+
+/* op1 may be NULL here to indicate an unset value */
+static inline int ct_eval_isset_isempty(zval *result, uint32_t extended_value, zval *op1) {
+ zval zv;
+ if (!(extended_value & ZEND_ISEMPTY)) {
+ ZVAL_BOOL(result, op1 && Z_TYPE_P(op1) != IS_NULL);
+ return SUCCESS;
+ } else if (!op1) {
+ ZVAL_TRUE(result);
+ return SUCCESS;
+ } else if (ct_eval_bool_cast(&zv, op1) == SUCCESS) {
+ ZVAL_BOOL(result, Z_TYPE(zv) == IS_FALSE);
+ return SUCCESS;
+ } else {
+ 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 || IS_PARTIAL_ARRAY(op1)) {
+ zval *value;
+ if (fetch_array_elem(&value, op1, op2) == FAILURE) {
+ return FAILURE;
+ }
+ if (IS_PARTIAL_ARRAY(op1) && (!value || IS_BOT(value))) {
+ return FAILURE;
+ }
+ return ct_eval_isset_isempty(result, extended_value, value);
+ } else if (Z_TYPE_P(op1) == IS_STRING) {
+ // TODO
+ return FAILURE;
+ } else {
+ ZVAL_BOOL(result, (extended_value & ZEND_ISEMPTY));
+ return SUCCESS;
+ }
+}
+
+static inline int ct_eval_del_array_elem(zval *result, zval *key) {
+ ZEND_ASSERT(IS_PARTIAL_ARRAY(result));
+
+ switch (Z_TYPE_P(key)) {
+ case IS_NULL:
+ zend_hash_del(Z_ARR_P(result), ZSTR_EMPTY_ALLOC());
+ break;
+ case IS_FALSE:
+ zend_hash_index_del(Z_ARR_P(result), 0);
+ break;
+ case IS_TRUE:
+ zend_hash_index_del(Z_ARR_P(result), 1);
+ break;
+ case IS_LONG:
+ zend_hash_index_del(Z_ARR_P(result), Z_LVAL_P(key));
+ break;
+ case IS_DOUBLE:
+ zend_hash_index_del(Z_ARR_P(result), zend_dval_to_lval(Z_DVAL_P(key)));
+ break;
+ case IS_STRING:
+ zend_symtable_del(Z_ARR_P(result), Z_STR_P(key));
+ break;
+ default:
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+
+static inline int ct_eval_add_array_elem(zval *result, zval *value, zval *key) {
+ if (!key) {
+ SEPARATE_ARRAY(result);
+ 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:
+ SEPARATE_ARRAY(result);
+ value = zend_hash_update(Z_ARR_P(result), ZSTR_EMPTY_ALLOC(), value);
+ break;
+ case IS_FALSE:
+ SEPARATE_ARRAY(result);
+ value = zend_hash_index_update(Z_ARR_P(result), 0, value);
+ break;
+ case IS_TRUE:
+ SEPARATE_ARRAY(result);
+ value = zend_hash_index_update(Z_ARR_P(result), 1, value);
+ break;
+ case IS_LONG:
+ SEPARATE_ARRAY(result);
+ value = zend_hash_index_update(Z_ARR_P(result), Z_LVAL_P(key), value);
+ break;
+ case IS_DOUBLE:
+ SEPARATE_ARRAY(result);
+ value = zend_hash_index_update(
+ Z_ARR_P(result), zend_dval_to_lval(Z_DVAL_P(key)), value);
+ break;
+ case IS_STRING:
+ SEPARATE_ARRAY(result);
+ 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_add_array_unpack(zval *result, zval *array) {
+ zend_string *key;
+ zval *value;
+ if (Z_TYPE_P(array) != IS_ARRAY) {
+ return FAILURE;
+ }
+
+ SEPARATE_ARRAY(result);
+ ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(array), key, value) {
+ if (key) {
+ return FAILURE;
+ }
+ value = zend_hash_next_index_insert(Z_ARR_P(result), value);
+ if (!value) {
+ return FAILURE;
+ }
+ Z_TRY_ADDREF_P(value);
+ } ZEND_HASH_FOREACH_END();
+ 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:
+ case PARTIAL_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_ex(value_str, 0);
+#endif
+ return FAILURE;
+ default:
+ return FAILURE;
+ }
+}
+
+static inline int fetch_obj_prop(zval **result, zval *op1, zval *op2) {
+ switch (Z_TYPE_P(op2)) {
+ 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_obj(zval *result, zval *op1, zval *op2) {
+ if (IS_PARTIAL_OBJECT(op1)) {
+ zval *value;
+ if (fetch_obj_prop(&value, op1, op2) == SUCCESS && value && !IS_BOT(value)) {
+ ZVAL_COPY(result, value);
+ return SUCCESS;
+ }
+ }
+ return FAILURE;
+}
+
+static inline int ct_eval_isset_obj(zval *result, uint32_t extended_value, zval *op1, zval *op2) {
+ if (IS_PARTIAL_OBJECT(op1)) {
+ zval *value;
+ if (fetch_obj_prop(&value, op1, op2) == FAILURE) {
+ return FAILURE;
+ }
+ if (!value || IS_BOT(value)) {
+ return FAILURE;
+ }
+ return ct_eval_isset_isempty(result, extended_value, value);
+ } else {
+ ZVAL_BOOL(result, (extended_value & ZEND_ISEMPTY));
+ return SUCCESS;
+ }
+}
+
+static inline int ct_eval_del_obj_prop(zval *result, zval *key) {
+ ZEND_ASSERT(IS_PARTIAL_OBJECT(result));
+
+ switch (Z_TYPE_P(key)) {
+ case IS_STRING:
+ zend_symtable_del(Z_ARR_P(result), Z_STR_P(key));
+ break;
+ default:
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+
+static inline int ct_eval_add_obj_prop(zval *result, zval *value, zval *key) {
+ switch (Z_TYPE_P(key)) {
+ 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_obj(zval *result, zval *value, zval *key) {
+ switch (Z_TYPE_P(result)) {
+ case IS_NULL:
+ case IS_FALSE:
+ empty_partial_object(result);
+ /* break missing intentionally */
+ case PARTIAL_OBJECT:
+ return ct_eval_add_obj_prop(result, value, key);
+ 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
+ || opcode == ZEND_PRE_INC_OBJ
+ || opcode == ZEND_POST_INC_OBJ) {
+ increment_function(result);
+ } else {
+ decrement_function(result);
+ }
+ return SUCCESS;
+}
+
+static inline void ct_eval_type_check(zval *result, uint32_t type_mask, zval *op1) {
+ uint32_t type = Z_TYPE_P(op1);
+ if (type == PARTIAL_ARRAY) {
+ type = IS_ARRAY;
+ } else if (type == PARTIAL_OBJECT) {
+ type = IS_OBJECT;
+ }
+ ZVAL_BOOL(result, (type_mask >> type) & 1);
+}
+
+static inline int ct_eval_in_array(zval *result, uint32_t extended_value, zval *op1, zval *op2) {
+ HashTable *ht;
+ bool res;
+
+ if (Z_TYPE_P(op2) != IS_ARRAY) {
+ return FAILURE;
+ }
+ ht = Z_ARRVAL_P(op2);
+ if (EXPECTED(Z_TYPE_P(op1) == IS_STRING)) {
+ res = zend_hash_exists(ht, Z_STR_P(op1));
+ } else if (extended_value) {
+ if (EXPECTED(Z_TYPE_P(op1) == IS_LONG)) {
+ res = zend_hash_index_exists(ht, Z_LVAL_P(op1));
+ } else {
+ res = 0;
+ }
+ } else if (Z_TYPE_P(op1) <= IS_FALSE) {
+ res = zend_hash_exists(ht, ZSTR_EMPTY_ALLOC());
+ } else {
+ zend_string *key;
+ zval key_tmp;
+
+ res = 0;
+ ZEND_HASH_FOREACH_STR_KEY(ht, key) {
+ ZVAL_STR(&key_tmp, key);
+ if (zend_compare(op1, &key_tmp) == 0) {
+ res = 1;
+ break;
+ }
+ } ZEND_HASH_FOREACH_END();
+ }
+ ZVAL_BOOL(result, res);
+ return SUCCESS;
+}
+
+static inline int ct_eval_array_key_exists(zval *result, zval *op1, zval *op2) {
+ zval *value;
+
+ if (Z_TYPE_P(op2) != IS_ARRAY && !IS_PARTIAL_ARRAY(op2)) {
+ return FAILURE;
+ }
+ if (Z_TYPE_P(op1) != IS_STRING && Z_TYPE_P(op1) != IS_LONG && Z_TYPE_P(op1) != IS_NULL) {
+ return FAILURE;
+ }
+ if (fetch_array_elem(&value, op2, op1) == FAILURE) {
+ return FAILURE;
+ }
+ if (IS_PARTIAL_ARRAY(op2) && (!value || IS_BOT(value))) {
+ return FAILURE;
+ }
+
+ ZVAL_BOOL(result, value != NULL);
+ return SUCCESS;
+}
+
+static bool can_ct_eval_func_call(zend_string *name, uint32_t num_args, zval **args) {
+ /* Functions that can be evaluated independently of what the arguments are.
+ * It's okay if these functions throw on invalid arguments, but they should not warn. */
+ if (false
+ || zend_string_equals_literal(name, "array_diff")
+ || zend_string_equals_literal(name, "array_diff_assoc")
+ || zend_string_equals_literal(name, "array_diff_key")
+ || zend_string_equals_literal(name, "array_is_list")
+ || zend_string_equals_literal(name, "array_key_exists")
+ || zend_string_equals_literal(name, "array_keys")
+ || zend_string_equals_literal(name, "array_merge")
+ || zend_string_equals_literal(name, "array_merge_recursive")
+ || zend_string_equals_literal(name, "array_replace")
+ || zend_string_equals_literal(name, "array_replace_recursive")
+ || zend_string_equals_literal(name, "array_values")
+ || zend_string_equals_literal(name, "base64_decode")
+ || zend_string_equals_literal(name, "base64_encode")
+#ifndef ZEND_WIN32
+ /* On Windows this function may be code page dependent. */
+ || zend_string_equals_literal(name, "dirname")
+#endif
+ || zend_string_equals_literal(name, "imagetypes")
+ || zend_string_equals_literal(name, "in_array")
+ || zend_string_equals_literal(name, "ltrim")
+ || zend_string_equals_literal(name, "php_sapi_name")
+ || zend_string_equals_literal(name, "php_uname")
+ || zend_string_equals_literal(name, "phpversion")
+ || zend_string_equals_literal(name, "pow")
+ || zend_string_equals_literal(name, "preg_quote")
+ || zend_string_equals_literal(name, "rawurldecode")
+ || zend_string_equals_literal(name, "rawurlencode")
+ || zend_string_equals_literal(name, "rtrim")
+ || zend_string_equals_literal(name, "serialize")
+ || zend_string_equals_literal(name, "str_contains")
+ || zend_string_equals_literal(name, "str_ends_with")
+ || zend_string_equals_literal(name, "str_split")
+ || zend_string_equals_literal(name, "str_starts_with")
+ || zend_string_equals_literal(name, "strpos")
+ || zend_string_equals_literal(name, "substr")
+ || zend_string_equals_literal(name, "trim")
+ || zend_string_equals_literal(name, "urldecode")
+ || zend_string_equals_literal(name, "urlencode")
+ || zend_string_equals_literal(name, "version_compare")
+ ) {
+ return true;
+ }
+
+ /* For the following functions we need to check arguments to prevent warnings during
+ * evaluation. */
+ if (num_args == 1) {
+ if (zend_string_equals_literal(name, "array_flip")) {
+ zval *entry;
+
+ if (Z_TYPE_P(args[0]) != IS_ARRAY) {
+ return false;
+ }
+ ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args[0]), entry) {
+ /* Throws warning for non int/string values. */
+ if (Z_TYPE_P(entry) != IS_LONG && Z_TYPE_P(entry) != IS_STRING) {
+ return false;
+ }
+ } ZEND_HASH_FOREACH_END();
+ return true;
+ }
+ if (zend_string_equals_literal(name, "implode")) {
+ zval *entry;
+
+ if (Z_TYPE_P(args[0]) != IS_ARRAY) {
+ return false;
+ }
+
+ ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args[0]), entry) {
+ /* May throw warning during conversion to string. */
+ if (Z_TYPE_P(entry) > IS_STRING) {
+ return false;
+ }
+ } ZEND_HASH_FOREACH_END();
+ return true;
+ }
+ return false;
+ }
+
+ if (num_args == 2) {
+ if (zend_string_equals_literal(name, "str_repeat")) {
+ /* Avoid creating overly large strings at compile-time. */
+ bool overflow;
+ return 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;
+ } else if (zend_string_equals_literal(name, "implode")) {
+ zval *entry;
+
+ if ((Z_TYPE_P(args[0]) != IS_STRING || Z_TYPE_P(args[1]) != IS_ARRAY)
+ && (Z_TYPE_P(args[0]) != IS_ARRAY || Z_TYPE_P(args[1]) != IS_STRING)) {
+ return false;
+ }
+
+ /* May throw warning during conversion to string. */
+ 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 false;
+ }
+ } ZEND_HASH_FOREACH_END();
+ } else {
+ ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args[1]), entry) {
+ if (Z_TYPE_P(entry) > IS_STRING) {
+ return false;
+ }
+ } ZEND_HASH_FOREACH_END();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ return false;
+}
+
+/* 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(
+ zend_op_array *op_array, zval *result, zend_string *name, uint32_t num_args, zval **args) {
+ uint32_t i;
+ zend_function *func = zend_hash_find_ptr(CG(function_table), name);
+ if (!func || func->type != ZEND_INTERNAL_FUNCTION) {
+ return FAILURE;
+ }
+
+ if (num_args == 1) {
+ /* Handle a few functions for which we manually implement evaluation here. */
+ if (zend_string_equals_literal(name, "chr")) {
+ zend_long c;
+ if (Z_TYPE_P(args[0]) != IS_LONG) {
+ return FAILURE;
+ }
+
+ c = Z_LVAL_P(args[0]) & 0xff;
+ ZVAL_CHAR(result, c);
+ return SUCCESS;
+ } else if (zend_string_equals_literal(name, "count")) {
+ if (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 (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;
+ }
+ }
+
+ if (!can_ct_eval_func_call(name, num_args, args)) {
+ return FAILURE;
+ }
+
+ zend_execute_data *prev_execute_data = EG(current_execute_data);
+ zend_execute_data *execute_data, dummy_frame;
+ zend_op dummy_opline;
+
+ /* Add a dummy frame to get the correct strict_types behavior. */
+ memset(&dummy_frame, 0, sizeof(zend_execute_data));
+ memset(&dummy_opline, 0, sizeof(zend_op));
+ dummy_frame.func = (zend_function *) op_array;
+ dummy_frame.opline = &dummy_opline;
+ dummy_opline.opcode = ZEND_DO_FCALL;
+
+ execute_data = safe_emalloc(num_args, sizeof(zval), ZEND_CALL_FRAME_SLOT * sizeof(zval));
+ memset(execute_data, 0, sizeof(zend_execute_data));
+ execute_data->prev_execute_data = &dummy_frame;
+ EG(current_execute_data) = 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]);
+ }
+ ZVAL_NULL(result);
+ func->internal_function.handler(execute_data, result);
+ for (i = 0; i < num_args; i++) {
+ zval_ptr_dtor_nogc(EX_VAR_NUM(i));
+ }
+
+ int retval = SUCCESS;
+ if (EG(exception)) {
+ zval_ptr_dtor(result);
+ zend_clear_exception();
+ retval = FAILURE;
+ }
+
+ efree(execute_data);
+ EG(current_execute_data) = prev_execute_data;
+ return retval;
+}
+
+#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)) return;
+
+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_mask = opline->extended_value;
+ if (!(type & expected_type_mask) && !(type & MAY_BE_UNDEF)) {
+ ZVAL_FALSE(&zv);
+ SET_RESULT(result, &zv);
+ return;
+ } else if (!(type & ((MAY_BE_ANY|MAY_BE_UNDEF) - expected_type_mask))
+ && !(expected_type_mask & MAY_BE_RESOURCE)) {
+ ZVAL_TRUE(&zv);
+ SET_RESULT(result, &zv);
+ return;
+ }
+ }
+ break;
+ case ZEND_ASSIGN_DIM:
+ {
+ zval *data = get_op1_value(ctx, opline+1, ssa_op+1);
+
+ /* 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);
+ }
+
+ if (IS_BOT(op1)) {
+ SET_RESULT_BOT(result);
+ SET_RESULT_BOT(op1);
+ return;
+ }
+
+ SKIP_IF_TOP(op1);
+ SKIP_IF_TOP(data);
+ if (op2) {
+ SKIP_IF_TOP(op2);
+ }
+
+ if (op2 && IS_BOT(op2)) {
+ /* Update of unknown index */
+ SET_RESULT_BOT(result);
+ if (ssa_op->op1_def >= 0) {
+ empty_partial_array(&zv);
+ SET_RESULT(op1, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ } else {
+ SET_RESULT_BOT(op1);
+ }
+ return;
+ }
+
+ if (IS_BOT(data)) {
+
+ SET_RESULT_BOT(result);
+ if ((IS_PARTIAL_ARRAY(op1)
+ || Z_TYPE_P(op1) == IS_NULL
+ || Z_TYPE_P(op1) == IS_FALSE
+ || Z_TYPE_P(op1) == IS_ARRAY)
+ && ssa_op->op1_def >= 0) {
+
+ if (Z_TYPE_P(op1) == IS_NULL || Z_TYPE_P(op1) == IS_FALSE) {
+ empty_partial_array(&zv);
+ } else {
+ dup_partial_array(&zv, op1);
+ }
+
+ if (!op2) {
+ /* We can't add NEXT element into partial array (skip it) */
+ SET_RESULT(op1, &zv);
+ } else if (ct_eval_del_array_elem(&zv, op2) == SUCCESS) {
+ SET_RESULT(op1, &zv);
+ } else {
+ SET_RESULT_BOT(op1);
+ }
+
+ zval_ptr_dtor_nogc(&zv);
+ } else {
+ SET_RESULT_BOT(op1);
+ }
+
+ } else {
+
+ if (IS_PARTIAL_ARRAY(op1)) {
+ dup_partial_array(&zv, op1);
+ } else {
+ ZVAL_COPY(&zv, op1);
+ }
+
+ if (!op2 && IS_PARTIAL_ARRAY(&zv)) {
+ /* We can't add NEXT element into partial array (skip it) */
+ SET_RESULT(result, data);
+ SET_RESULT(op1, &zv);
+ } else if (ct_eval_assign_dim(&zv, data, op2) == SUCCESS) {
+ /* Mark array containing partial array as partial */
+ if (IS_PARTIAL_ARRAY(data)) {
+ MAKE_PARTIAL_ARRAY(&zv);
+ }
+ SET_RESULT(result, data);
+ SET_RESULT(op1, &zv);
+ } else {
+ SET_RESULT_BOT(result);
+ SET_RESULT_BOT(op1);
+ }
+
+ zval_ptr_dtor_nogc(&zv);
+ }
+ return;
+ }
+
+ case ZEND_ASSIGN_OBJ:
+ if (ssa_op->op1_def >= 0
+ && ctx->scdf.ssa->vars[ssa_op->op1_def].escape_state == ESCAPE_STATE_NO_ESCAPE) {
+ zval *data = get_op1_value(ctx, opline+1, ssa_op+1);
+ zend_ssa_var_info *var_info = &ctx->scdf.ssa->var_info[ssa_op->op1_use];
+
+ /* Don't try to propagate assignments to (potentially) typed properties. We would
+ * need to deal with errors and type conversions first. */
+ if (!var_info->ce || (var_info->ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
+ SET_RESULT_BOT(result);
+ SET_RESULT_BOT(op1);
+ return;
+ }
+
+ if (IS_BOT(op1)) {
+ SET_RESULT_BOT(result);
+ SET_RESULT_BOT(op1);
+ return;
+ }
+
+ SKIP_IF_TOP(op1);
+ SKIP_IF_TOP(data);
+ SKIP_IF_TOP(op2);
+
+ if (IS_BOT(op2)) {
+ /* Update of unknown property */
+ SET_RESULT_BOT(result);
+ empty_partial_object(&zv);
+ SET_RESULT(op1, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ return;
+ }
+
+ if (IS_BOT(data)) {
+ SET_RESULT_BOT(result);
+ if (IS_PARTIAL_OBJECT(op1)
+ || Z_TYPE_P(op1) == IS_NULL
+ || Z_TYPE_P(op1) == IS_FALSE) {
+
+ if (Z_TYPE_P(op1) == IS_NULL || Z_TYPE_P(op1) == IS_FALSE) {
+ empty_partial_object(&zv);
+ } else {
+ dup_partial_object(&zv, op1);
+ }
+
+ if (ct_eval_del_obj_prop(&zv, op2) == SUCCESS) {
+ SET_RESULT(op1, &zv);
+ } else {
+ SET_RESULT_BOT(op1);
+ }
+ zval_ptr_dtor_nogc(&zv);
+ } else {
+ SET_RESULT_BOT(op1);
+ }
+
+ } else {
+
+ if (IS_PARTIAL_OBJECT(op1)) {
+ dup_partial_object(&zv, op1);
+ } else {
+ ZVAL_COPY(&zv, op1);
+ }
+
+ if (ct_eval_assign_obj(&zv, data, op2) == SUCCESS) {
+ SET_RESULT(result, data);
+ SET_RESULT(op1, &zv);
+ } else {
+ SET_RESULT_BOT(result);
+ SET_RESULT_BOT(op1);
+ }
+
+ zval_ptr_dtor_nogc(&zv);
+ }
+ } else {
+ SET_RESULT_BOT(result);
+ SET_RESULT_BOT(op1);
+ }
+ return;
+
+ 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
+ || 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;
+ }
+ case ZEND_INIT_ARRAY:
+ case ZEND_ADD_ARRAY_ELEMENT:
+ {
+ zval *result = NULL;
+
+ if (opline->opcode == ZEND_ADD_ARRAY_ELEMENT) {
+ result = &ctx->values[ssa_op->result_use];
+ if (IS_BOT(result)) {
+ SET_RESULT_BOT(result);
+ SET_RESULT_BOT(op1);
+ return;
+ }
+ SKIP_IF_TOP(result);
+ }
+
+ if (op1) {
+ 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 (result && Z_TYPE_P(result) == IS_NULL) {
+ SET_RESULT_BOT(result);
+ return;
+ }
+
+ if (op2 && IS_BOT(op2)) {
+ /* Update of unknown index */
+ SET_RESULT_BOT(op1);
+ if (ssa_op->result_def >= 0) {
+ empty_partial_array(&zv);
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ } else {
+ SET_RESULT_BOT(result);
+ }
+ return;
+ }
+
+ if ((op1 && IS_BOT(op1))
+ || (opline->extended_value & ZEND_ARRAY_ELEMENT_REF)) {
+
+ SET_RESULT_BOT(op1);
+ if (ssa_op->result_def >= 0) {
+ if (!result) {
+ empty_partial_array(&zv);
+ } else {
+ MAKE_PARTIAL_ARRAY(result);
+ ZVAL_COPY_VALUE(&zv, result);
+ ZVAL_NULL(result);
+ }
+ if (!op2) {
+ /* We can't add NEXT element into partial array (skip it) */
+ SET_RESULT(result, &zv);
+ } else if (ct_eval_del_array_elem(&zv, op2) == SUCCESS) {
+ SET_RESULT(result, &zv);
+ } else {
+ SET_RESULT_BOT(result);
+ }
+ zval_ptr_dtor_nogc(&zv);
+ } else {
+ /* If any operand is BOT, mark the result as BOT right away.
+ * Exceptions to this rule are handled above. */
+ SET_RESULT_BOT(result);
+ }
+
+ } else {
+ if (result) {
+ ZVAL_COPY_VALUE(&zv, result);
+ ZVAL_NULL(result);
+ } else {
+ array_init(&zv);
+ }
+
+ if (op1) {
+ if (!op2 && IS_PARTIAL_ARRAY(&zv)) {
+ /* We can't add NEXT element into partial array (skip it) */
+ SET_RESULT(result, &zv);
+ } else if (ct_eval_add_array_elem(&zv, op1, op2) == SUCCESS) {
+ if (IS_PARTIAL_ARRAY(op1)) {
+ MAKE_PARTIAL_ARRAY(&zv);
+ }
+ SET_RESULT(result, &zv);
+ } else {
+ SET_RESULT_BOT(result);
+ }
+ } else {
+ SET_RESULT(result, &zv);
+ }
+
+ zval_ptr_dtor_nogc(&zv);
+ }
+ return;
+ }
+ case ZEND_ADD_ARRAY_UNPACK: {
+ zval *result = &ctx->values[ssa_op->result_use];
+ if (IS_BOT(result) || IS_BOT(op1)) {
+ SET_RESULT_BOT(result);
+ return;
+ }
+ SKIP_IF_TOP(result);
+ SKIP_IF_TOP(op1);
+
+ /* See comment for ADD_ARRAY_ELEMENT. */
+ if (Z_TYPE_P(result) == IS_NULL) {
+ SET_RESULT_BOT(result);
+ return;
+ }
+ ZVAL_COPY_VALUE(&zv, result);
+ ZVAL_NULL(result);
+
+ if (ct_eval_add_array_unpack(&zv, op1) == SUCCESS) {
+ SET_RESULT(result, &zv);
+ } else {
+ SET_RESULT_BOT(result);
+ }
+ zval_ptr_dtor_nogc(&zv);
+ return;
+ }
+ case ZEND_NEW:
+ if (ssa_op->result_def >= 0
+ && ctx->scdf.ssa->vars[ssa_op->result_def].escape_state == ESCAPE_STATE_NO_ESCAPE) {
+ empty_partial_object(&zv);
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ } else {
+ SET_RESULT_BOT(result);
+ }
+ return;
+ case ZEND_ASSIGN_STATIC_PROP_REF:
+ case ZEND_ASSIGN_OBJ_REF:
+ /* Handled here because we also need to BOT the OP_DATA operand, while the generic
+ * code below will not do so. */
+ SET_RESULT_BOT(result);
+ SET_RESULT_BOT(op1);
+ SET_RESULT_BOT(op2);
+ opline++;
+ ssa_op++;
+ SET_RESULT_BOT(op1);
+ 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:
+ case ZEND_CASE_STRICT:
+ SKIP_IF_TOP(op1);
+ SKIP_IF_TOP(op2);
+
+ if (ct_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_OP:
+ case ZEND_ASSIGN_DIM_OP:
+ case ZEND_ASSIGN_OBJ_OP:
+ case ZEND_ASSIGN_STATIC_PROP_OP:
+ if (op1) {
+ SKIP_IF_TOP(op1);
+ }
+ if (op2) {
+ SKIP_IF_TOP(op2);
+ }
+ if (opline->opcode == ZEND_ASSIGN_OP) {
+ if (ct_eval_binary_op(&zv, opline->extended_value, op1, op2) == SUCCESS) {
+ SET_RESULT(op1, &zv);
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ } else if (opline->opcode == ZEND_ASSIGN_DIM_OP) {
+ if ((IS_PARTIAL_ARRAY(op1) || Z_TYPE_P(op1) == IS_ARRAY)
+ && ssa_op->op1_def >= 0 && op2) {
+ zval tmp;
+ zval *data = get_op1_value(ctx, opline+1, ssa_op+1);
+
+ SKIP_IF_TOP(data);
+
+ if (ct_eval_fetch_dim(&tmp, op1, op2, 0) == SUCCESS) {
+ if (IS_BOT(data)) {
+ dup_partial_array(&zv, op1);
+ ct_eval_del_array_elem(&zv, op2);
+ SET_RESULT_BOT(result);
+ SET_RESULT(op1, &zv);
+ zval_ptr_dtor_nogc(&tmp);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+
+ if (ct_eval_binary_op(&tmp, opline->extended_value, &tmp, data) != SUCCESS) {
+ SET_RESULT_BOT(result);
+ SET_RESULT_BOT(op1);
+ zval_ptr_dtor_nogc(&tmp);
+ break;
+ }
+
+ if (IS_PARTIAL_ARRAY(op1)) {
+ dup_partial_array(&zv, op1);
+ } else {
+ ZVAL_COPY(&zv, op1);
+ }
+
+ if (ct_eval_assign_dim(&zv, &tmp, op2) == SUCCESS) {
+ SET_RESULT(result, &tmp);
+ SET_RESULT(op1, &zv);
+ zval_ptr_dtor_nogc(&tmp);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+
+ zval_ptr_dtor_nogc(&tmp);
+ zval_ptr_dtor_nogc(&zv);
+ }
+ }
+ } else if (opline->opcode == ZEND_ASSIGN_OBJ_OP) {
+ if (op1 && IS_PARTIAL_OBJECT(op1)
+ && ssa_op->op1_def >= 0
+ && ctx->scdf.ssa->vars[ssa_op->op1_def].escape_state == ESCAPE_STATE_NO_ESCAPE) {
+ zval tmp;
+ zval *data = get_op1_value(ctx, opline+1, ssa_op+1);
+
+ SKIP_IF_TOP(data);
+
+ if (ct_eval_fetch_obj(&tmp, op1, op2) == SUCCESS) {
+ if (IS_BOT(data)) {
+ dup_partial_object(&zv, op1);
+ ct_eval_del_obj_prop(&zv, op2);
+ SET_RESULT_BOT(result);
+ SET_RESULT(op1, &zv);
+ zval_ptr_dtor_nogc(&tmp);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+
+ if (ct_eval_binary_op(&tmp, opline->extended_value, &tmp, data) != SUCCESS) {
+ SET_RESULT_BOT(result);
+ SET_RESULT_BOT(op1);
+ zval_ptr_dtor_nogc(&tmp);
+ break;
+ }
+
+ dup_partial_object(&zv, op1);
+
+ if (ct_eval_assign_obj(&zv, &tmp, op2) == SUCCESS) {
+ SET_RESULT(result, &tmp);
+ SET_RESULT(op1, &zv);
+ zval_ptr_dtor_nogc(&tmp);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+
+ zval_ptr_dtor_nogc(&tmp);
+ zval_ptr_dtor_nogc(&zv);
+ }
+ }
+ }
+ SET_RESULT_BOT(result);
+ SET_RESULT_BOT(op1);
+ break;
+ case ZEND_PRE_INC_OBJ:
+ case ZEND_PRE_DEC_OBJ:
+ case ZEND_POST_INC_OBJ:
+ case ZEND_POST_DEC_OBJ:
+ if (op1) {
+ SKIP_IF_TOP(op1);
+ SKIP_IF_TOP(op2);
+ if (IS_PARTIAL_OBJECT(op1)
+ && ssa_op->op1_def >= 0
+ && ctx->scdf.ssa->vars[ssa_op->op1_def].escape_state == ESCAPE_STATE_NO_ESCAPE) {
+ zval tmp1, tmp2;
+
+ if (ct_eval_fetch_obj(&tmp1, op1, op2) == SUCCESS
+ && ct_eval_incdec(&tmp2, opline->opcode, &tmp1) == SUCCESS) {
+
+ dup_partial_object(&zv, op1);
+ ct_eval_assign_obj(&zv, &tmp2, op2);
+ if (opline->opcode == ZEND_PRE_INC_OBJ
+ || opline->opcode == ZEND_PRE_DEC_OBJ) {
+ SET_RESULT(result, &tmp2);
+ } else {
+ SET_RESULT(result, &tmp1);
+ }
+ SET_RESULT(op1, &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 (IS_PARTIAL_ARRAY(op1)) {
+ SET_RESULT_BOT(result);
+ break;
+ }
+ 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 (IS_PARTIAL_ARRAY(op1)) {
+ SET_RESULT_BOT(result);
+ break;
+ }
+ 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);
+ if (ct_eval_bool_cast(&zv, op1) == SUCCESS) {
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ SET_RESULT_BOT(result);
+ 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_YIELD_FROM:
+ // tmp = yield from [] -> tmp = null
+ SKIP_IF_TOP(op1);
+ if (Z_TYPE_P(op1) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(op1)) == 0) {
+ ZVAL_NULL(&zv);
+ SET_RESULT(result, &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_IN_ARRAY:
+ SKIP_IF_TOP(op1);
+ SKIP_IF_TOP(op2);
+ if (ct_eval_in_array(&zv, opline->extended_value, op1, op2) == SUCCESS) {
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ SET_RESULT_BOT(result);
+ break;
+ case ZEND_ARRAY_KEY_EXISTS:
+ SKIP_IF_TOP(op1);
+ SKIP_IF_TOP(op2);
+ if (ct_eval_array_key_exists(&zv, op1, op2) == SUCCESS) {
+ 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_R:
+ SKIP_IF_TOP(op1);
+ SKIP_IF_TOP(op2);
+
+ if (ct_eval_fetch_dim(&zv, op1, op2, (opline->opcode != ZEND_FETCH_LIST_R)) == 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_FETCH_OBJ_R:
+ case ZEND_FETCH_OBJ_IS:
+ if (op1) {
+ SKIP_IF_TOP(op1);
+ SKIP_IF_TOP(op2);
+
+ if (ct_eval_fetch_obj(&zv, op1, op2) == SUCCESS) {
+ SET_RESULT(result, &zv);
+ zval_ptr_dtor_nogc(&zv);
+ break;
+ }
+ }
+ SET_RESULT_BOT(result);
+ break;
+ case ZEND_ISSET_ISEMPTY_PROP_OBJ:
+ if (op1) {
+ SKIP_IF_TOP(op1);
+ SKIP_IF_TOP(op2);
+
+ if (ct_eval_isset_obj(&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:
+ case ZEND_COPY_TMP:
+ SET_RESULT(result, op1);
+ break;
+ case ZEND_JMP_NULL:
+ switch (opline->extended_value) {
+ case ZEND_SHORT_CIRCUITING_CHAIN_EXPR:
+ ZVAL_NULL(&zv);
+ break;
+ case ZEND_SHORT_CIRCUITING_CHAIN_ISSET:
+ ZVAL_FALSE(&zv);
+ break;
+ case ZEND_SHORT_CIRCUITING_CHAIN_EMPTY:
+ ZVAL_TRUE(&zv);
+ break;
+ EMPTY_SWITCH_DEFAULT_CASE()
+ }
+ SET_RESULT(result, &zv);
+ break;
+#if 0
+ case ZEND_FETCH_CLASS:
+ if (!op1) {
+ SET_RESULT_BOT(result);
+ break;
+ }
+ SET_RESULT(result, op1);
+ break;
+#endif
+ case ZEND_ISSET_ISEMPTY_CV:
+ 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 (IS_PARTIAL_ARRAY(op2)) {
+ SET_RESULT_BOT(result);
+ break;
+ }
+ 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 (ct_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_DO_ICALL:
+ {
+ zend_call_info *call;
+ zval *name, *args[3] = {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 up to three arguments right now */
+ if (call->num_args > 3 || call->send_unpack) {
+ 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]) || IS_PARTIAL_ARRAY(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(scdf->op_array, &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, zv;
+ 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_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:
+ {
+ if (ct_eval_bool_cast(&zv, op1) == FAILURE) {
+ scdf_mark_edge_feasible(scdf, block_num, block->successors[0]);
+ scdf_mark_edge_feasible(scdf, block_num, block->successors[1]);
+ return;
+ }
+ s = Z_TYPE(zv) == IS_TRUE;
+ break;
+ }
+ case ZEND_JMPNZ:
+ case ZEND_JMPNZ_EX:
+ case ZEND_JMP_SET:
+ {
+ if (ct_eval_bool_cast(&zv, op1) == FAILURE) {
+ scdf_mark_edge_feasible(scdf, block_num, block->successors[0]);
+ scdf_mark_edge_feasible(scdf, block_num, block->successors[1]);
+ return;
+ }
+ s = Z_TYPE(zv) == IS_FALSE;
+ break;
+ }
+ case ZEND_COALESCE:
+ s = (Z_TYPE_P(op1) == IS_NULL);
+ break;
+ case ZEND_JMP_NULL:
+ s = (Z_TYPE_P(op1) != IS_NULL);
+ break;
+ case ZEND_FE_RESET_R:
+ case ZEND_FE_RESET_RW:
+ /* A non-empty partial array is definitely non-empty, but an
+ * empty partial array may be non-empty at runtime. */
+ if (Z_TYPE_P(op1) != IS_ARRAY ||
+ (IS_PARTIAL_ARRAY(op1) && zend_hash_num_elements(Z_ARR_P(op1)) == 0)) {
+ 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;
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ case ZEND_MATCH:
+ {
+ bool strict_comparison = opline->opcode == ZEND_MATCH;
+ zend_uchar type = Z_TYPE_P(op1);
+ bool correct_type =
+ (opline->opcode == ZEND_SWITCH_LONG && type == IS_LONG)
+ || (opline->opcode == ZEND_SWITCH_STRING && type == IS_STRING)
+ || (opline->opcode == ZEND_MATCH && (type == IS_LONG || type == IS_STRING));
+
+ if (correct_type) {
+ zend_op_array *op_array = scdf->op_array;
+ zend_ssa *ssa = scdf->ssa;
+ HashTable *jmptable = Z_ARRVAL_P(CT_CONSTANT_EX(op_array, opline->op2.constant));
+ zval *jmp_zv = type == IS_LONG
+ ? zend_hash_index_find(jmptable, Z_LVAL_P(op1))
+ : zend_hash_find(jmptable, Z_STR_P(op1));
+ int target;
+
+ if (jmp_zv) {
+ target = ssa->cfg.map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(jmp_zv))];
+ } else {
+ target = ssa->cfg.map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)];
+ }
+ scdf_mark_edge_feasible(scdf, block_num, target);
+ return;
+ } else if (strict_comparison) {
+ zend_op_array *op_array = scdf->op_array;
+ zend_ssa *ssa = scdf->ssa;
+ int target = ssa->cfg.map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)];
+ scdf_mark_edge_feasible(scdf, block_num, target);
+ return;
+ }
+ s = 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_hash_tables(HashTable *ret, HashTable *ht1, HashTable *ht2)
+{
+ zend_ulong index;
+ zend_string *key;
+ zval *val1, *val2;
+
+ ZEND_HASH_FOREACH_KEY_VAL(ht1, index, key, val1) {
+ if (key) {
+ val2 = zend_hash_find(ht2, key);
+ } else {
+ val2 = zend_hash_index_find(ht2, index);
+ }
+ if (val2 && zend_is_identical(val1, val2)) {
+ if (key) {
+ val1 = zend_hash_add_new(ret, key, val1);
+ } else {
+ val1 = zend_hash_index_add_new(ret, index, val1);
+ }
+ Z_TRY_ADDREF_P(val1);
+ }
+ } ZEND_HASH_FOREACH_END();
+}
+
+static int join_partial_arrays(zval *a, zval *b)
+{
+ zval ret;
+
+ if ((Z_TYPE_P(a) != IS_ARRAY && !IS_PARTIAL_ARRAY(a))
+ || (Z_TYPE_P(b) != IS_ARRAY && !IS_PARTIAL_ARRAY(b))) {
+ return FAILURE;
+ }
+
+ empty_partial_array(&ret);
+ join_hash_tables(Z_ARRVAL(ret), Z_ARRVAL_P(a), Z_ARRVAL_P(b));
+ zval_ptr_dtor_nogc(a);
+ ZVAL_COPY_VALUE(a, &ret);
+
+ return SUCCESS;
+}
+
+static int join_partial_objects(zval *a, zval *b)
+{
+ zval ret;
+
+ if (!IS_PARTIAL_OBJECT(a) || !IS_PARTIAL_OBJECT(b)) {
+ return FAILURE;
+ }
+
+ empty_partial_object(&ret);
+ join_hash_tables(Z_ARRVAL(ret), Z_ARRVAL_P(a), Z_ARRVAL_P(b));
+ zval_ptr_dtor_nogc(a);
+ ZVAL_COPY_VALUE(a, &ret);
+
+ return SUCCESS;
+}
+
+static void join_phi_values(zval *a, zval *b, bool escape) {
+ 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)) {
+ zval_ptr_dtor_nogc(a);
+ MAKE_BOT(a);
+ return;
+ }
+ if (IS_PARTIAL_ARRAY(a) || IS_PARTIAL_ARRAY(b)) {
+ if (join_partial_arrays(a, b) != SUCCESS) {
+ zval_ptr_dtor_nogc(a);
+ MAKE_BOT(a);
+ }
+ } else if (IS_PARTIAL_OBJECT(a) || IS_PARTIAL_OBJECT(b)) {
+ if (escape || join_partial_objects(a, b) != SUCCESS) {
+ zval_ptr_dtor_nogc(a);
+ MAKE_BOT(a);
+ }
+ } else if (!zend_is_identical(a, b)) {
+ if (join_partial_arrays(a, b) != SUCCESS) {
+ 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);
+#if SCP_DEBUG
+ fprintf(stderr, "Handling phi(");
+#endif
+ 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]], ssa->vars[phi->ssa_var].escape_state != ESCAPE_STATE_NO_ESCAPE);
+ }
+ } 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)) {
+#if SCP_DEBUG
+ scp_dump_value(&ctx->values[phi->sources[i]]);
+ fprintf(stderr, ",");
+#endif
+ join_phi_values(&result, &ctx->values[phi->sources[i]], ssa->vars[phi->ssa_var].escape_state != ESCAPE_STATE_NO_ESCAPE);
+ } else {
+#if SCP_DEBUG
+ fprintf(stderr, " --,");
+#endif
+ }
+ }
+ }
+#if SCP_DEBUG
+ fprintf(stderr, ")\n");
+#endif
+
+ 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 (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;
+}
+
+/* Call instruction -> remove opcodes that are part of the call */
+static int remove_call(sccp_ctx *ctx, zend_op *opline, zend_ssa_op *ssa_op)
+{
+ zend_ssa *ssa = ctx->scdf.ssa;
+ zend_op_array *op_array = ctx->scdf.op_array;
+ zend_call_info *call;
+ int i;
+
+ ZEND_ASSERT(ctx->call_map);
+ call = ctx->call_map[opline - op_array->opcodes];
+ ZEND_ASSERT(call);
+ ZEND_ASSERT(call->caller_call_opline == opline);
+ 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]);
+ }
+
+ // TODO: remove call_info completely???
+ call->callee_func = NULL;
+
+ return call->num_args + 2;
+}
+
+/* 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.
+ * d) The ordinary DCE pass cannot collect construction of dead non-escaping arrays and objects.
+ */
+static int try_remove_definition(sccp_ctx *ctx, int var_num, zend_ssa_var *var, zval *value)
+{
+ zend_ssa *ssa = ctx->scdf.ssa;
+ zend_op_array *op_array = ctx->scdf.op_array;
+ int removed_ops = 0;
+
+ if (var->definition >= 0) {
+ 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) */
+ return 0;
+ }
+
+ if (ssa_op->result_def == var_num) {
+ if (ssa_op->op1_def >= 0
+ || ssa_op->op2_def >= 0) {
+ /* we cannot remove instruction that defines other variables */
+ return 0;
+ } else if (opline->opcode == ZEND_JMPZ_EX
+ || opline->opcode == ZEND_JMPNZ_EX
+ || opline->opcode == ZEND_JMP_SET
+ || opline->opcode == ZEND_COALESCE
+ || opline->opcode == ZEND_JMP_NULL
+ || opline->opcode == ZEND_FE_RESET_R
+ || opline->opcode == ZEND_FE_RESET_RW
+ || opline->opcode == ZEND_FE_FETCH_R
+ || opline->opcode == ZEND_FE_FETCH_RW
+ || opline->opcode == ZEND_NEW) {
+ /* we cannot simple remove jump instructions */
+ return 0;
+ } else if (var->use_chain >= 0
+ || var->phi_use_chain != NULL) {
+ if (value
+ && (opline->result_type & (IS_VAR|IS_TMP_VAR))
+ && opline->opcode != ZEND_QM_ASSIGN
+ && opline->opcode != ZEND_ROPE_INIT
+ && opline->opcode != ZEND_ROPE_ADD
+ && opline->opcode != ZEND_INIT_ARRAY
+ && opline->opcode != ZEND_ADD_ARRAY_ELEMENT
+ && opline->opcode != ZEND_ADD_ARRAY_UNPACK) {
+ /* Replace with QM_ASSIGN */
+ zend_uchar old_type = opline->result_type;
+ uint32_t old_var = opline->result.var;
+
+ ssa_op->result_def = -1;
+ if (opline->opcode == ZEND_DO_ICALL) {
+ removed_ops = remove_call(ctx, opline, ssa_op) - 1;
+ } else {
+ zend_ssa_remove_instr(ssa, opline, ssa_op);
+ }
+ ssa_op->result_def = var_num;
+ opline->opcode = ZEND_QM_ASSIGN;
+ opline->result_type = old_type;
+ opline->result.var = old_var;
+ Z_TRY_ADDREF_P(value);
+ zend_optimizer_update_op1_const(ctx->scdf.op_array, opline, value);
+ }
+ return 0;
+ } else {
+ zend_ssa_remove_result_def(ssa, ssa_op);
+ if (opline->opcode == ZEND_DO_ICALL) {
+ removed_ops = remove_call(ctx, opline, ssa_op);
+ } else if (opline->opcode == ZEND_TYPE_CHECK
+ && (opline->op1_type & (IS_VAR|IS_TMP_VAR))
+ && !value_known(&ctx->values[ssa_op->op1_use])) {
+ /* For TYPE_CHECK we may compute the result value without knowing the
+ * operand, based on type inference information. Make sure the operand is
+ * freed and leave further cleanup to DCE. */
+ opline->opcode = ZEND_FREE;
+ opline->result_type = IS_UNUSED;
+ removed_ops++;
+ } else {
+ zend_ssa_remove_instr(ssa, opline, ssa_op);
+ removed_ops++;
+ }
+ }
+ } else if (ssa_op->op1_def == var_num) {
+ /* Compound assign or incdec -> convert to direct ASSIGN */
+
+ if (!value) {
+ /* In some cases zend_may_throw() may be avoided */
+ switch (opline->opcode) {
+ case ZEND_ASSIGN_DIM:
+ case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_OP:
+ case ZEND_ASSIGN_DIM_OP:
+ case ZEND_ASSIGN_OBJ_OP:
+ case ZEND_ASSIGN_STATIC_PROP_OP:
+ if ((ssa_op->op2_use >= 0 && !value_known(&ctx->values[ssa_op->op2_use]))
+ || ((ssa_op+1)->op1_use >= 0 &&!value_known(&ctx->values[(ssa_op+1)->op1_use]))) {
+ return 0;
+ }
+ break;
+ case ZEND_PRE_INC_OBJ:
+ case ZEND_PRE_DEC_OBJ:
+ case ZEND_POST_INC_OBJ:
+ case ZEND_POST_DEC_OBJ:
+ if (ssa_op->op2_use >= 0 && !value_known(&ctx->values[ssa_op->op2_use])) {
+ return 0;
+ }
+ break;
+ default:
+ if (zend_may_throw(opline, ssa_op, op_array, ssa)) {
+ return 0;
+ }
+ break;
+ }
+ }
+
+ /* Mark result unused, if possible */
+ if (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) {
+ zend_ssa_remove_result_def(ssa, ssa_op);
+ opline->result_type = IS_UNUSED;
+ } else if (opline->opcode != ZEND_PRE_INC &&
+ opline->opcode != ZEND_PRE_DEC) {
+ /* op1_def and result_def are different */
+ return removed_ops;
+ }
+ }
+
+ /* 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;
+ }
+
+ /* Remove OP_DATA opcode */
+ switch (opline->opcode) {
+ case ZEND_ASSIGN_DIM:
+ case ZEND_ASSIGN_OBJ:
+ removed_ops++;
+ zend_ssa_remove_instr(ssa, opline + 1, ssa_op + 1);
+ break;
+ case ZEND_ASSIGN_DIM_OP:
+ case ZEND_ASSIGN_OBJ_OP:
+ case ZEND_ASSIGN_STATIC_PROP_OP:
+ removed_ops++;
+ zend_ssa_remove_instr(ssa, opline + 1, ssa_op + 1);
+ break;
+ default:
+ break;
+ }
+
+ if (value) {
+ /* 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);
+ } else {
+ /* Remove dead array or object construction */
+ removed_ops++;
+ if (var->use_chain >= 0 || var->phi_use_chain != NULL) {
+ zend_ssa_rename_var_uses(ssa, ssa_op->op1_def, ssa_op->op1_use, 1);
+ }
+ zend_ssa_remove_op1_def(ssa, ssa_op);
+ zend_ssa_remove_instr(ssa, opline, ssa_op);
+ }
+ }
+ } else if (var->definition_phi
+ && var->use_chain < 0
+ && var->phi_use_chain == NULL) {
+ zend_ssa_remove_phi(ssa, var->definition_phi);
+ }
+ return removed_ops;
+}
+
+/* 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 (IS_PARTIAL_ARRAY(&ctx->values[i])
+ || IS_PARTIAL_OBJECT(&ctx->values[i])) {
+ if (!Z_DELREF(ctx->values[i])) {
+ zend_array_destroy(Z_ARR(ctx->values[i]));
+ }
+ MAKE_BOT(&ctx->values[i]);
+ if ((var->use_chain < 0 && var->phi_use_chain == NULL) || var->no_val) {
+ removed_ops += try_remove_definition(ctx, i, var, NULL);
+ }
+ continue;
+ } else 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);
+ if (ssa_op->op2_use != ssa_op->op1_use) {
+ 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();
+
+ if (value_known(&ctx->values[i])) {
+ removed_ops += try_remove_definition(ctx, i, var, value);
+ }
+ }
+
+ 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");
+
+ if (ctx->debug_level & ZEND_DUMP_SCCP) {
+ int i, first = 1;
+
+ for (i = op_array->last_var; i < ssa->vars_count; i++) {
+ zval *zv = &sccp.values[i];
+
+ if (IS_TOP(zv) || IS_BOT(zv)) {
+ continue;
+ }
+ if (first) {
+ first = 0;
+ fprintf(stderr, "\nSCCP Values for \"");
+ zend_dump_op_array_name(op_array);
+ fprintf(stderr, "\":\n");
+ }
+ fprintf(stderr, " #%d.", i);
+ zend_dump_var(op_array, IS_CV, ssa->vars[i].var);
+ fprintf(stderr, " =");
+ scp_dump_value(zv);
+ fprintf(stderr, "\n");
+ }
+ }
+
+ 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/Zend/Optimizer/scdf.c b/Zend/Optimizer/scdf.c
new file mode 100644
index 0000000000..e414081987
--- /dev/null
+++ b/Zend/Optimizer/scdf.c
@@ -0,0 +1,229 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, Sparse Conditional Data Flow Propagation Framework |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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 "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) {
+ 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(ssa->cfg.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 = NULL;
+ 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) {
+ ZEND_ASSERT(opline && "Should have opline in non-empty block");
+ 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 bool kept_alive_by_loop_var_free(scdf_ctx *scdf, uint32_t block_idx) {
+ uint32_t i;
+ const zend_op_array *op_array = scdf->op_array;
+ const zend_cfg *cfg = &scdf->ssa->cfg;
+ const zend_basic_block *block = &cfg->blocks[block_idx];
+ if (!(cfg->flags & ZEND_FUNC_FREE_LOOP_VAR)) {
+ return 0;
+ }
+ for (i = block->start; i < block->start + block->len; i++) {
+ zend_op *opline = &op_array->opcodes[i];
+ if (zend_optimizer_is_loop_var_free(opline)) {
+ int ssa_var = scdf->ssa->ops[i].op1_use;
+ if (ssa_var >= 0) {
+ int op_num = scdf->ssa->vars[ssa_var].definition;
+ uint32_t def_block;
+ ZEND_ASSERT(op_num >= 0);
+ def_block = cfg->map[op_num];
+ if (zend_bitset_in(scdf->executable_blocks, def_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_loop_var_free(scdf, i)) {
+ removed_ops += ssa->cfg.blocks[i].len;
+ zend_ssa_remove_block(scdf->op_array, ssa, i);
+ }
+ }
+ return removed_ops;
+}
diff --git a/Zend/Optimizer/scdf.h b/Zend/Optimizer/scdf.h
new file mode 100644
index 0000000000..1ab1cec3bd
--- /dev/null
+++ b/Zend/Optimizer/scdf.h
@@ -0,0 +1,99 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, Call Graph |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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_UNREACHABLE();
+}
+
+static inline 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/Zend/Optimizer/ssa_integrity.c b/Zend/Optimizer/ssa_integrity.c
new file mode 100644
index 0000000000..4bd7705816
--- /dev/null
+++ b/Zend/Optimizer/ssa_integrity.c
@@ -0,0 +1,378 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, SSA validation |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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 "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 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 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 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 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 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 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 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 bool is_var_type(zend_uchar type) {
+ return (type & (IS_CV|IS_VAR|IS_TMP_VAR)) != 0;
+}
+
+#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("op2 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) {
+ unsigned num_sources = NUM_PHI_SOURCES(phi);
+ for (i = 0; i < num_sources; i++) {
+ int source = phi->sources[i];
+ 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));
+ }
+ if (phi->use_chains[i]) {
+ int j;
+ for (j = i + 1; j < num_sources; j++) {
+ if (phi->sources[j] == source && phi->use_chains[j]) {
+ FAIL("use chain for source " VARFMT " of phi " VARFMT
+ " at %d despite earlier use\n", VAR(source), VAR(phi->ssa_var), j);
+ }
+ }
+ }
+ }
+ 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/Zend/Optimizer/zend_call_graph.c b/Zend/Optimizer/zend_call_graph.c
new file mode 100644
index 0000000000..b0f3247cd7
--- /dev/null
+++ b/Zend/Optimizer/zend_call_graph.c
@@ -0,0 +1,271 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, Call Graph |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#include "php.h"
+#include "zend_compile.h"
+#include "zend_extensions.h"
+#include "Optimizer/zend_optimizer.h"
+#include "zend_optimizer_internal.h"
+#include "zend_inference.h"
+#include "zend_call_graph.h"
+#include "zend_func_info.h"
+#include "zend_inference.h"
+#include "zend_call_graph.h"
+
+static void zend_op_array_calc(zend_op_array *op_array, void *context)
+{
+ zend_call_graph *call_graph = context;
+ call_graph->op_arrays_count++;
+}
+
+static void zend_op_array_collect(zend_op_array *op_array, void *context)
+{
+ zend_call_graph *call_graph = context;
+ zend_func_info *func_info = call_graph->func_infos + call_graph->op_arrays_count;
+
+ ZEND_SET_FUNC_INFO(op_array, func_info);
+ call_graph->op_arrays[call_graph->op_arrays_count] = op_array;
+ func_info->num = call_graph->op_arrays_count;
+ call_graph->op_arrays_count++;
+}
+
+ZEND_API int zend_analyze_calls(zend_arena **arena, zend_script *script, uint32_t build_flags, zend_op_array *op_array, zend_func_info *func_info)
+{
+ zend_op *opline = op_array->opcodes;
+ zend_op *end = opline + op_array->last;
+ zend_function *func;
+ zend_call_info *call_info;
+ int call = 0;
+ zend_call_info **call_stack;
+ ALLOCA_FLAG(use_heap);
+ bool is_prototype;
+
+ call_stack = do_alloca((op_array->last / 2) * sizeof(zend_call_info*), use_heap);
+ call_info = NULL;
+ while (opline != end) {
+ switch (opline->opcode) {
+ case ZEND_INIT_FCALL:
+ case ZEND_INIT_METHOD_CALL:
+ case ZEND_INIT_STATIC_METHOD_CALL:
+ call_stack[call] = call_info;
+ func = zend_optimizer_get_called_func(
+ script, op_array, opline, &is_prototype);
+ /* TODO: Support prototypes? */
+ if (func && !is_prototype) {
+ call_info = zend_arena_calloc(arena, 1, sizeof(zend_call_info) + (sizeof(zend_send_arg_info) * ((int)opline->extended_value - 1)));
+ call_info->caller_op_array = op_array;
+ call_info->caller_init_opline = opline;
+ call_info->caller_call_opline = NULL;
+ call_info->callee_func = func;
+ call_info->num_args = opline->extended_value;
+ call_info->next_callee = func_info->callee_info;
+ func_info->callee_info = call_info;
+
+ if (build_flags & ZEND_CALL_TREE) {
+ call_info->next_caller = NULL;
+ } else if (func->type == ZEND_INTERNAL_FUNCTION) {
+ call_info->next_caller = NULL;
+ } else {
+ zend_func_info *callee_func_info = ZEND_FUNC_INFO(&func->op_array);
+ if (callee_func_info) {
+ call_info->next_caller = callee_func_info->caller_info;
+ callee_func_info->caller_info = call_info;
+ } else {
+ call_info->next_caller = NULL;
+ }
+ }
+ } else {
+ call_info = NULL;
+ }
+ call++;
+ break;
+ case ZEND_INIT_FCALL_BY_NAME:
+ case ZEND_INIT_NS_FCALL_BY_NAME:
+ case ZEND_INIT_DYNAMIC_CALL:
+ case ZEND_NEW:
+ case ZEND_INIT_USER_CALL:
+ call_stack[call] = call_info;
+ call_info = NULL;
+ call++;
+ break;
+ case ZEND_DO_FCALL:
+ case ZEND_DO_ICALL:
+ case ZEND_DO_UCALL:
+ case ZEND_DO_FCALL_BY_NAME:
+ func_info->flags |= ZEND_FUNC_HAS_CALLS;
+ if (call_info) {
+ call_info->caller_call_opline = opline;
+ }
+ call--;
+ call_info = call_stack[call];
+ break;
+ case ZEND_SEND_VAL:
+ case ZEND_SEND_VAR:
+ case ZEND_SEND_VAL_EX:
+ case ZEND_SEND_VAR_EX:
+ case ZEND_SEND_FUNC_ARG:
+ case ZEND_SEND_REF:
+ case ZEND_SEND_VAR_NO_REF:
+ case ZEND_SEND_VAR_NO_REF_EX:
+ case ZEND_SEND_USER:
+ if (call_info) {
+ if (opline->op2_type == IS_CONST) {
+ call_info->named_args = 1;
+ break;
+ }
+
+ uint32_t num = opline->op2.num;
+ if (num > 0) {
+ num--;
+ }
+ call_info->arg_info[num].opline = opline;
+ }
+ break;
+ case ZEND_SEND_ARRAY:
+ case ZEND_SEND_UNPACK:
+ if (call_info) {
+ call_info->send_unpack = 1;
+ }
+ break;
+ case ZEND_EXIT:
+ /* In this case the DO_CALL opcode may have been dropped
+ * and caller_call_opline will be NULL. */
+ break;
+ }
+ opline++;
+ }
+ free_alloca(call_stack, use_heap);
+ return SUCCESS;
+}
+
+static int zend_is_indirectly_recursive(zend_op_array *root, zend_op_array *op_array, zend_bitset visited)
+{
+ zend_func_info *func_info;
+ zend_call_info *call_info;
+ int ret = 0;
+
+ if (op_array == root) {
+ return 1;
+ }
+
+ func_info = ZEND_FUNC_INFO(op_array);
+ if (zend_bitset_in(visited, func_info->num)) {
+ return 0;
+ }
+ zend_bitset_incl(visited, func_info->num);
+ call_info = func_info->caller_info;
+ while (call_info) {
+ if (zend_is_indirectly_recursive(root, call_info->caller_op_array, visited)) {
+ call_info->recursive = 1;
+ ret = 1;
+ }
+ call_info = call_info->next_caller;
+ }
+ return ret;
+}
+
+static void zend_analyze_recursion(zend_call_graph *call_graph)
+{
+ zend_op_array *op_array;
+ zend_func_info *func_info;
+ zend_call_info *call_info;
+ int i;
+ int set_len = zend_bitset_len(call_graph->op_arrays_count);
+ zend_bitset visited;
+ ALLOCA_FLAG(use_heap);
+
+ visited = ZEND_BITSET_ALLOCA(set_len, use_heap);
+ for (i = 0; i < call_graph->op_arrays_count; i++) {
+ op_array = call_graph->op_arrays[i];
+ func_info = call_graph->func_infos + i;
+ call_info = func_info->caller_info;
+ while (call_info) {
+ if (call_info->caller_op_array == op_array) {
+ call_info->recursive = 1;
+ func_info->flags |= ZEND_FUNC_RECURSIVE | ZEND_FUNC_RECURSIVE_DIRECTLY;
+ } else {
+ memset(visited, 0, sizeof(zend_ulong) * set_len);
+ if (zend_is_indirectly_recursive(op_array, call_info->caller_op_array, visited)) {
+ call_info->recursive = 1;
+ func_info->flags |= ZEND_FUNC_RECURSIVE | ZEND_FUNC_RECURSIVE_INDIRECTLY;
+ }
+ }
+ call_info = call_info->next_caller;
+ }
+ }
+
+ free_alloca(visited, use_heap);
+}
+
+static void zend_sort_op_arrays(zend_call_graph *call_graph)
+{
+ (void) call_graph;
+
+ // TODO: perform topological sort of cyclic call graph
+}
+
+ZEND_API int zend_build_call_graph(zend_arena **arena, zend_script *script, zend_call_graph *call_graph) /* {{{ */
+{
+ call_graph->op_arrays_count = 0;
+ zend_foreach_op_array(script, zend_op_array_calc, call_graph);
+
+ call_graph->op_arrays = (zend_op_array**)zend_arena_calloc(arena, call_graph->op_arrays_count, sizeof(zend_op_array*));
+ call_graph->func_infos = (zend_func_info*)zend_arena_calloc(arena, call_graph->op_arrays_count, sizeof(zend_func_info));
+ call_graph->op_arrays_count = 0;
+ zend_foreach_op_array(script, zend_op_array_collect, call_graph);
+
+ return SUCCESS;
+}
+/* }}} */
+
+ZEND_API void zend_analyze_call_graph(zend_arena **arena, zend_script *script, zend_call_graph *call_graph) /* {{{ */
+{
+ int i;
+
+ for (i = 0; i < call_graph->op_arrays_count; i++) {
+ zend_analyze_calls(arena, script, 0, call_graph->op_arrays[i], call_graph->func_infos + i);
+ }
+ zend_analyze_recursion(call_graph);
+ zend_sort_op_arrays(call_graph);
+}
+/* }}} */
+
+ZEND_API zend_call_info **zend_build_call_map(zend_arena **arena, zend_func_info *info, const zend_op_array *op_array) /* {{{ */
+{
+ zend_call_info **map, *call;
+ if (!info->callee_info) {
+ /* Don't build call map if function contains no calls */
+ return NULL;
+ }
+
+ map = zend_arena_calloc(arena, sizeof(zend_call_info *), op_array->last);
+ for (call = info->callee_info; call; call = call->next_callee) {
+ int i;
+ map[call->caller_init_opline - op_array->opcodes] = call;
+ if (call->caller_call_opline) {
+ map[call->caller_call_opline - op_array->opcodes] = call;
+ }
+ for (i = 0; i < call->num_args; i++) {
+ if (call->arg_info[i].opline) {
+ map[call->arg_info[i].opline - op_array->opcodes] = call;
+ }
+ }
+ }
+ return map;
+}
+/* }}} */
diff --git a/Zend/Optimizer/zend_call_graph.h b/Zend/Optimizer/zend_call_graph.h
new file mode 100644
index 0000000000..a456dcfbb8
--- /dev/null
+++ b/Zend/Optimizer/zend_call_graph.h
@@ -0,0 +1,69 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, Call Graph |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef ZEND_CALL_GRAPH_H
+#define ZEND_CALL_GRAPH_H
+
+#include "zend_ssa.h"
+#include "zend_func_info.h"
+#include "zend_optimizer.h"
+
+typedef struct _zend_send_arg_info {
+ zend_op *opline;
+} zend_send_arg_info;
+
+struct _zend_call_info {
+ zend_op_array *caller_op_array;
+ zend_op *caller_init_opline;
+ zend_op *caller_call_opline;
+ zend_function *callee_func;
+ zend_call_info *next_caller;
+ zend_call_info *next_callee;
+ bool recursive;
+ bool send_unpack; /* Parameters passed by SEND_UNPACK or SEND_ARRAY */
+ bool named_args; /* Function has named arguments */
+ int num_args;
+ zend_send_arg_info arg_info[1];
+};
+
+struct _zend_func_info {
+ int num;
+ uint32_t flags;
+ zend_ssa ssa; /* Static Single Assignmnt Form */
+ zend_call_info *caller_info; /* where this function is called from */
+ zend_call_info *callee_info; /* which functions are called from this one */
+ zend_call_info **call_map; /* Call info associated with init/call/send opnum */
+ zend_ssa_var_info return_info;
+};
+
+typedef struct _zend_call_graph {
+ int op_arrays_count;
+ zend_op_array **op_arrays;
+ zend_func_info *func_infos;
+} zend_call_graph;
+
+BEGIN_EXTERN_C()
+
+ZEND_API int zend_build_call_graph(zend_arena **arena, zend_script *script, zend_call_graph *call_graph);
+ZEND_API void zend_analyze_call_graph(zend_arena **arena, zend_script *script, zend_call_graph *call_graph);
+ZEND_API zend_call_info **zend_build_call_map(zend_arena **arena, zend_func_info *info, const zend_op_array *op_array);
+ZEND_API int zend_analyze_calls(zend_arena **arena, zend_script *script, uint32_t build_flags, zend_op_array *op_array, zend_func_info *func_info);
+
+END_EXTERN_C()
+
+#endif /* ZEND_CALL_GRAPH_H */
diff --git a/Zend/Optimizer/zend_cfg.c b/Zend/Optimizer/zend_cfg.c
new file mode 100644
index 0000000000..973a93ef6c
--- /dev/null
+++ b/Zend/Optimizer/zend_cfg.c
@@ -0,0 +1,909 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, CFG - Control Flow Graph |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#include "php.h"
+#include "zend_compile.h"
+#include "zend_cfg.h"
+#include "zend_func_info.h"
+#include "zend_worklist.h"
+#include "zend_optimizer.h"
+#include "zend_optimizer_internal.h"
+
+static void zend_mark_reachable(zend_op *opcodes, zend_cfg *cfg, zend_basic_block *b) /* {{{ */
+{
+ zend_basic_block *blocks = cfg->blocks;
+
+ while (1) {
+ int i;
+
+ b->flags |= ZEND_BB_REACHABLE;
+ if (b->successors_count == 0) {
+ b->flags |= ZEND_BB_EXIT;
+ return;
+ }
+
+ for (i = 0; i < b->successors_count; i++) {
+ zend_basic_block *succ = blocks + b->successors[i];
+
+ if (b->len != 0) {
+ zend_uchar opcode = opcodes[b->start + b->len - 1].opcode;
+ if (b->successors_count == 1) {
+ if (opcode == ZEND_JMP) {
+ succ->flags |= ZEND_BB_TARGET;
+ } else {
+ succ->flags |= ZEND_BB_FOLLOW;
+
+ if ((cfg->flags & ZEND_CFG_STACKLESS)) {
+ if (opcode == ZEND_INCLUDE_OR_EVAL ||
+ opcode == ZEND_GENERATOR_CREATE ||
+ opcode == ZEND_YIELD ||
+ opcode == ZEND_YIELD_FROM ||
+ opcode == ZEND_DO_FCALL ||
+ opcode == ZEND_DO_UCALL ||
+ opcode == ZEND_DO_FCALL_BY_NAME) {
+ succ->flags |= ZEND_BB_ENTRY;
+ }
+ }
+ if ((cfg->flags & ZEND_CFG_RECV_ENTRY)) {
+ if (opcode == ZEND_RECV ||
+ opcode == ZEND_RECV_INIT) {
+ succ->flags |= ZEND_BB_RECV_ENTRY;
+ }
+ }
+ }
+ } else if (b->successors_count == 2) {
+ if (i == 0 || opcode == ZEND_JMPZNZ) {
+ succ->flags |= ZEND_BB_TARGET;
+ } else {
+ succ->flags |= ZEND_BB_FOLLOW;
+ }
+ } else {
+ ZEND_ASSERT(
+ opcode == ZEND_SWITCH_LONG
+ || opcode == ZEND_SWITCH_STRING
+ || opcode == ZEND_MATCH
+ );
+ if (i == b->successors_count - 1) {
+ succ->flags |= ZEND_BB_FOLLOW | ZEND_BB_TARGET;
+ } else {
+ succ->flags |= ZEND_BB_TARGET;
+ }
+ }
+ } else {
+ succ->flags |= ZEND_BB_FOLLOW;
+ }
+
+ if (i == b->successors_count - 1) {
+ /* Tail call optimization */
+ if (succ->flags & ZEND_BB_REACHABLE) {
+ return;
+ }
+
+ b = succ;
+ break;
+ } else {
+ /* Recursively check reachability */
+ if (!(succ->flags & ZEND_BB_REACHABLE)) {
+ zend_mark_reachable(opcodes, cfg, succ);
+ }
+ }
+ }
+ }
+}
+/* }}} */
+
+static void zend_mark_reachable_blocks(const zend_op_array *op_array, zend_cfg *cfg, int start) /* {{{ */
+{
+ zend_basic_block *blocks = cfg->blocks;
+
+ blocks[start].flags = ZEND_BB_START;
+ zend_mark_reachable(op_array->opcodes, cfg, blocks + start);
+
+ if (op_array->last_try_catch) {
+ zend_basic_block *b;
+ int j, changed;
+ uint32_t *block_map = cfg->map;
+
+ do {
+ changed = 0;
+
+ /* Add exception paths */
+ for (j = 0; j < op_array->last_try_catch; j++) {
+
+ /* check for jumps into the middle of try block */
+ b = blocks + block_map[op_array->try_catch_array[j].try_op];
+ if (!(b->flags & ZEND_BB_REACHABLE)) {
+ zend_basic_block *end;
+
+ if (op_array->try_catch_array[j].catch_op) {
+ end = blocks + block_map[op_array->try_catch_array[j].catch_op];
+ while (b != end) {
+ if (b->flags & ZEND_BB_REACHABLE) {
+ op_array->try_catch_array[j].try_op = b->start;
+ break;
+ }
+ b++;
+ }
+ }
+ b = blocks + block_map[op_array->try_catch_array[j].try_op];
+ if (!(b->flags & ZEND_BB_REACHABLE)) {
+ if (op_array->try_catch_array[j].finally_op) {
+ end = blocks + block_map[op_array->try_catch_array[j].finally_op];
+ while (b != end) {
+ if (b->flags & ZEND_BB_REACHABLE) {
+ op_array->try_catch_array[j].try_op = op_array->try_catch_array[j].catch_op;
+ changed = 1;
+ zend_mark_reachable(op_array->opcodes, cfg, blocks + block_map[op_array->try_catch_array[j].try_op]);
+ break;
+ }
+ b++;
+ }
+ }
+ }
+ }
+
+ b = blocks + block_map[op_array->try_catch_array[j].try_op];
+ if (b->flags & ZEND_BB_REACHABLE) {
+ b->flags |= ZEND_BB_TRY;
+ if (op_array->try_catch_array[j].catch_op) {
+ b = blocks + block_map[op_array->try_catch_array[j].catch_op];
+ b->flags |= ZEND_BB_CATCH;
+ if (!(b->flags & ZEND_BB_REACHABLE)) {
+ changed = 1;
+ zend_mark_reachable(op_array->opcodes, cfg, b);
+ }
+ }
+ if (op_array->try_catch_array[j].finally_op) {
+ b = blocks + block_map[op_array->try_catch_array[j].finally_op];
+ b->flags |= ZEND_BB_FINALLY;
+ if (!(b->flags & ZEND_BB_REACHABLE)) {
+ changed = 1;
+ zend_mark_reachable(op_array->opcodes, cfg, b);
+ }
+ }
+ if (op_array->try_catch_array[j].finally_end) {
+ b = blocks + block_map[op_array->try_catch_array[j].finally_end];
+ b->flags |= ZEND_BB_FINALLY_END;
+ if (!(b->flags & ZEND_BB_REACHABLE)) {
+ changed = 1;
+ zend_mark_reachable(op_array->opcodes, cfg, b);
+ }
+ }
+ } else {
+ if (op_array->try_catch_array[j].catch_op) {
+ ZEND_ASSERT(!(blocks[block_map[op_array->try_catch_array[j].catch_op]].flags & ZEND_BB_REACHABLE));
+ }
+ if (op_array->try_catch_array[j].finally_op) {
+ ZEND_ASSERT(!(blocks[block_map[op_array->try_catch_array[j].finally_op]].flags & ZEND_BB_REACHABLE));
+ }
+ if (op_array->try_catch_array[j].finally_end) {
+ ZEND_ASSERT(!(blocks[block_map[op_array->try_catch_array[j].finally_end]].flags & ZEND_BB_REACHABLE));
+ }
+ }
+ }
+ } while (changed);
+ }
+
+ if (cfg->flags & ZEND_FUNC_FREE_LOOP_VAR) {
+ zend_basic_block *b;
+ int j;
+ uint32_t *block_map = cfg->map;
+
+ /* Mark blocks that are unreachable, but free a loop var created in a reachable block. */
+ for (b = blocks; b < blocks + cfg->blocks_count; b++) {
+ if (b->flags & ZEND_BB_REACHABLE) {
+ continue;
+ }
+
+ for (j = b->start; j < b->start + b->len; j++) {
+ zend_op *opline = &op_array->opcodes[j];
+ if (zend_optimizer_is_loop_var_free(opline)) {
+ zend_op *def_opline = zend_optimizer_get_loop_var_def(op_array, opline);
+ if (def_opline) {
+ uint32_t def_block = block_map[def_opline - op_array->opcodes];
+ if (blocks[def_block].flags & ZEND_BB_REACHABLE) {
+ b->flags |= ZEND_BB_UNREACHABLE_FREE;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+/* }}} */
+
+void zend_cfg_remark_reachable_blocks(const zend_op_array *op_array, zend_cfg *cfg) /* {{{ */
+{
+ zend_basic_block *blocks = cfg->blocks;
+ int i;
+ int start = 0;
+
+ for (i = 0; i < cfg->blocks_count; i++) {
+ if (blocks[i].flags & ZEND_BB_REACHABLE) {
+ start = i;
+ i++;
+ break;
+ }
+ }
+
+ /* clear all flags */
+ for (i = 0; i < cfg->blocks_count; i++) {
+ blocks[i].flags = 0;
+ }
+
+ zend_mark_reachable_blocks(op_array, cfg, start);
+}
+/* }}} */
+
+static void initialize_block(zend_basic_block *block) {
+ block->flags = 0;
+ block->successors = block->successors_storage;
+ block->successors_count = 0;
+ block->predecessors_count = 0;
+ block->predecessor_offset = -1;
+ block->idom = -1;
+ block->loop_header = -1;
+ block->level = -1;
+ block->children = -1;
+ block->next_child = -1;
+}
+
+#define BB_START(i) do { \
+ if (!block_map[i]) { blocks_count++;} \
+ block_map[i]++; \
+ } while (0)
+
+ZEND_API int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t build_flags, zend_cfg *cfg) /* {{{ */
+{
+ uint32_t flags = 0;
+ uint32_t i;
+ int j;
+ uint32_t *block_map;
+ zend_function *fn;
+ int blocks_count = 0;
+ zend_basic_block *blocks;
+ zval *zv;
+ bool extra_entry_block = 0;
+
+ cfg->flags = build_flags & (ZEND_CFG_STACKLESS|ZEND_CFG_RECV_ENTRY);
+
+ cfg->map = block_map = zend_arena_calloc(arena, op_array->last, sizeof(uint32_t));
+
+ /* Build CFG, Step 1: Find basic blocks starts, calculate number of blocks */
+ BB_START(0);
+ for (i = 0; i < op_array->last; i++) {
+ zend_op *opline = op_array->opcodes + i;
+ switch (opline->opcode) {
+ case ZEND_RECV:
+ case ZEND_RECV_INIT:
+ if (build_flags & ZEND_CFG_RECV_ENTRY) {
+ BB_START(i + 1);
+ }
+ break;
+ case ZEND_RETURN:
+ case ZEND_RETURN_BY_REF:
+ case ZEND_GENERATOR_RETURN:
+ case ZEND_EXIT:
+ case ZEND_MATCH_ERROR:
+ if (i + 1 < op_array->last) {
+ BB_START(i + 1);
+ }
+ break;
+ case ZEND_THROW:
+ /* Don't treat THROW as terminator if it's used in expression context,
+ * as we may lose live ranges when eliminating unreachable code. */
+ if (opline->extended_value != ZEND_THROW_IS_EXPR && i + 1 < op_array->last) {
+ BB_START(i + 1);
+ }
+ break;
+ case ZEND_INCLUDE_OR_EVAL:
+ flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS;
+ case ZEND_GENERATOR_CREATE:
+ case ZEND_YIELD:
+ case ZEND_YIELD_FROM:
+ if (build_flags & ZEND_CFG_STACKLESS) {
+ BB_START(i + 1);
+ }
+ break;
+ case ZEND_DO_FCALL:
+ case ZEND_DO_UCALL:
+ case ZEND_DO_FCALL_BY_NAME:
+ flags |= ZEND_FUNC_HAS_CALLS;
+ if (build_flags & ZEND_CFG_STACKLESS) {
+ BB_START(i + 1);
+ }
+ break;
+ case ZEND_DO_ICALL:
+ flags |= ZEND_FUNC_HAS_CALLS;
+ break;
+ case ZEND_INIT_FCALL:
+ case ZEND_INIT_NS_FCALL_BY_NAME:
+ zv = CRT_CONSTANT(opline->op2);
+ if (opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME) {
+ /* The third literal is the lowercased unqualified name */
+ zv += 2;
+ }
+ if ((fn = zend_hash_find_ptr(EG(function_table), Z_STR_P(zv))) != NULL) {
+ if (fn->type == ZEND_INTERNAL_FUNCTION) {
+ flags |= zend_optimizer_classify_function(
+ Z_STR_P(zv), opline->extended_value);
+ }
+ }
+ break;
+ case ZEND_FAST_CALL:
+ BB_START(OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes);
+ BB_START(i + 1);
+ break;
+ case ZEND_FAST_RET:
+ if (i + 1 < op_array->last) {
+ BB_START(i + 1);
+ }
+ break;
+ case ZEND_JMP:
+ BB_START(OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes);
+ if (i + 1 < op_array->last) {
+ BB_START(i + 1);
+ }
+ break;
+ case ZEND_JMPZNZ:
+ BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes);
+ BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
+ if (i + 1 < op_array->last) {
+ BB_START(i + 1);
+ }
+ break;
+ case ZEND_JMPZ:
+ case ZEND_JMPNZ:
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ case ZEND_ASSERT_CHECK:
+ case ZEND_JMP_NULL:
+ BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes);
+ BB_START(i + 1);
+ break;
+ case ZEND_CATCH:
+ if (!(opline->extended_value & ZEND_LAST_CATCH)) {
+ BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes);
+ }
+ BB_START(i + 1);
+ break;
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
+ BB_START(i + 1);
+ break;
+ case ZEND_FE_RESET_R:
+ case ZEND_FE_RESET_RW:
+ BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes);
+ BB_START(i + 1);
+ break;
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ case ZEND_MATCH:
+ {
+ HashTable *jumptable = Z_ARRVAL_P(CRT_CONSTANT(opline->op2));
+ zval *zv;
+ ZEND_HASH_FOREACH_VAL(jumptable, zv) {
+ BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv)));
+ } ZEND_HASH_FOREACH_END();
+ BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
+ BB_START(i + 1);
+ break;
+ }
+ case ZEND_FETCH_R:
+ case ZEND_FETCH_W:
+ case ZEND_FETCH_RW:
+ case ZEND_FETCH_FUNC_ARG:
+ case ZEND_FETCH_IS:
+ case ZEND_FETCH_UNSET:
+ case ZEND_UNSET_VAR:
+ case ZEND_ISSET_ISEMPTY_VAR:
+ if (opline->extended_value & ZEND_FETCH_LOCAL) {
+ flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS;
+ } else if ((opline->extended_value & (ZEND_FETCH_GLOBAL | ZEND_FETCH_GLOBAL_LOCK)) &&
+ !op_array->function_name) {
+ flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS;
+ }
+ break;
+ case ZEND_FUNC_GET_ARGS:
+ flags |= ZEND_FUNC_VARARG;
+ break;
+ case ZEND_EXT_STMT:
+ flags |= ZEND_FUNC_HAS_EXTENDED_STMT;
+ break;
+ case ZEND_EXT_FCALL_BEGIN:
+ case ZEND_EXT_FCALL_END:
+ flags |= ZEND_FUNC_HAS_EXTENDED_FCALL;
+ break;
+ case ZEND_FREE:
+ if (opline->extended_value == ZEND_FREE_SWITCH) {
+ flags |= ZEND_FUNC_FREE_LOOP_VAR;
+ }
+ break;
+ case ZEND_FE_FREE:
+ flags |= ZEND_FUNC_FREE_LOOP_VAR;
+ break;
+ }
+ }
+
+ /* If the entry block has predecessors, we may need to split it */
+ if ((build_flags & ZEND_CFG_NO_ENTRY_PREDECESSORS)
+ && op_array->last > 0 && block_map[0] > 1) {
+ extra_entry_block = 1;
+ }
+
+ if (op_array->last_try_catch) {
+ for (j = 0; j < op_array->last_try_catch; j++) {
+ BB_START(op_array->try_catch_array[j].try_op);
+ if (op_array->try_catch_array[j].catch_op) {
+ BB_START(op_array->try_catch_array[j].catch_op);
+ }
+ if (op_array->try_catch_array[j].finally_op) {
+ BB_START(op_array->try_catch_array[j].finally_op);
+ }
+ if (op_array->try_catch_array[j].finally_end) {
+ BB_START(op_array->try_catch_array[j].finally_end);
+ }
+ }
+ }
+
+ blocks_count += extra_entry_block;
+ cfg->blocks_count = blocks_count;
+
+ /* Build CFG, Step 2: Build Array of Basic Blocks */
+ cfg->blocks = blocks = zend_arena_calloc(arena, sizeof(zend_basic_block), blocks_count);
+
+ blocks_count = -1;
+
+ if (extra_entry_block) {
+ initialize_block(&blocks[0]);
+ blocks[0].start = 0;
+ blocks[0].len = 0;
+ blocks_count++;
+ }
+
+ for (i = 0; i < op_array->last; i++) {
+ if (block_map[i]) {
+ if (blocks_count >= 0) {
+ blocks[blocks_count].len = i - blocks[blocks_count].start;
+ }
+ blocks_count++;
+ initialize_block(&blocks[blocks_count]);
+ blocks[blocks_count].start = i;
+ }
+ block_map[i] = blocks_count;
+ }
+
+ blocks[blocks_count].len = i - blocks[blocks_count].start;
+ blocks_count++;
+
+ /* Build CFG, Step 3: Calculate successors */
+ for (j = 0; j < blocks_count; j++) {
+ zend_basic_block *block = &blocks[j];
+ zend_op *opline;
+ if (block->len == 0) {
+ block->successors_count = 1;
+ block->successors[0] = j + 1;
+ continue;
+ }
+
+ opline = op_array->opcodes + block->start + block->len - 1;
+ switch (opline->opcode) {
+ case ZEND_FAST_RET:
+ case ZEND_RETURN:
+ case ZEND_RETURN_BY_REF:
+ case ZEND_GENERATOR_RETURN:
+ case ZEND_EXIT:
+ case ZEND_THROW:
+ case ZEND_MATCH_ERROR:
+ break;
+ case ZEND_JMP:
+ block->successors_count = 1;
+ block->successors[0] = block_map[OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes];
+ break;
+ case ZEND_JMPZNZ:
+ block->successors_count = 2;
+ block->successors[0] = block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes];
+ block->successors[1] = block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)];
+ break;
+ case ZEND_JMPZ:
+ case ZEND_JMPNZ:
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ case ZEND_ASSERT_CHECK:
+ case ZEND_JMP_NULL:
+ block->successors_count = 2;
+ block->successors[0] = block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes];
+ block->successors[1] = j + 1;
+ break;
+ case ZEND_CATCH:
+ if (!(opline->extended_value & ZEND_LAST_CATCH)) {
+ block->successors_count = 2;
+ block->successors[0] = block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes];
+ block->successors[1] = j + 1;
+ } else {
+ block->successors_count = 1;
+ block->successors[0] = j + 1;
+ }
+ break;
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ block->successors_count = 2;
+ block->successors[0] = block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)];
+ block->successors[1] = j + 1;
+ break;
+ case ZEND_FE_RESET_R:
+ case ZEND_FE_RESET_RW:
+ block->successors_count = 2;
+ block->successors[0] = block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes];
+ block->successors[1] = j + 1;
+ break;
+ case ZEND_FAST_CALL:
+ block->successors_count = 2;
+ block->successors[0] = block_map[OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes];
+ block->successors[1] = j + 1;
+ break;
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ case ZEND_MATCH:
+ {
+ HashTable *jumptable = Z_ARRVAL_P(CRT_CONSTANT(opline->op2));
+ zval *zv;
+ uint32_t s = 0;
+
+ block->successors_count = (opline->opcode == ZEND_MATCH ? 1 : 2) + zend_hash_num_elements(jumptable);
+ block->successors = zend_arena_calloc(arena, block->successors_count, sizeof(int));
+
+ ZEND_HASH_FOREACH_VAL(jumptable, zv) {
+ block->successors[s++] = block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv))];
+ } ZEND_HASH_FOREACH_END();
+
+ block->successors[s++] = block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)];
+ if (opline->opcode != ZEND_MATCH) {
+ block->successors[s++] = j + 1;
+ }
+ break;
+ }
+ default:
+ block->successors_count = 1;
+ block->successors[0] = j + 1;
+ break;
+ }
+ }
+
+ /* Build CFG, Step 4, Mark Reachable Basic Blocks */
+ cfg->flags |= flags;
+ zend_mark_reachable_blocks(op_array, cfg, 0);
+
+ return SUCCESS;
+}
+/* }}} */
+
+ZEND_API int zend_cfg_build_predecessors(zend_arena **arena, zend_cfg *cfg) /* {{{ */
+{
+ int j, s, edges;
+ zend_basic_block *b;
+ zend_basic_block *blocks = cfg->blocks;
+ zend_basic_block *end = blocks + cfg->blocks_count;
+ int *predecessors;
+
+ edges = 0;
+ for (b = blocks; b < end; b++) {
+ b->predecessors_count = 0;
+ }
+ for (b = blocks; b < end; b++) {
+ if (!(b->flags & ZEND_BB_REACHABLE)) {
+ b->successors_count = 0;
+ b->predecessors_count = 0;
+ } else {
+ for (s = 0; s < b->successors_count; s++) {
+ edges++;
+ blocks[b->successors[s]].predecessors_count++;
+ }
+ }
+ }
+
+ cfg->edges_count = edges;
+ cfg->predecessors = predecessors = (int*)zend_arena_calloc(arena, sizeof(int), edges);
+
+ edges = 0;
+ for (b = blocks; b < end; b++) {
+ if (b->flags & ZEND_BB_REACHABLE) {
+ b->predecessor_offset = edges;
+ edges += b->predecessors_count;
+ b->predecessors_count = 0;
+ }
+ }
+
+ for (j = 0; j < cfg->blocks_count; j++) {
+ if (blocks[j].flags & ZEND_BB_REACHABLE) {
+ /* SWITCH_STRING/LONG may have few identical successors */
+ for (s = 0; s < blocks[j].successors_count; s++) {
+ int duplicate = 0;
+ int p;
+
+ for (p = 0; p < s; p++) {
+ if (blocks[j].successors[p] == blocks[j].successors[s]) {
+ duplicate = 1;
+ break;
+ }
+ }
+ if (!duplicate) {
+ zend_basic_block *b = blocks + blocks[j].successors[s];
+
+ predecessors[b->predecessor_offset + b->predecessors_count] = j;
+ b->predecessors_count++;
+ }
+ }
+ }
+ }
+
+ return SUCCESS;
+}
+/* }}} */
+
+/* Computes a postorder numbering of the CFG */
+static void compute_postnum_recursive(
+ int *postnum, int *cur, const zend_cfg *cfg, int block_num) /* {{{ */
+{
+ int s;
+ zend_basic_block *block = &cfg->blocks[block_num];
+ if (postnum[block_num] != -1) {
+ return;
+ }
+
+ postnum[block_num] = -2; /* Marker for "currently visiting" */
+ for (s = 0; s < block->successors_count; s++) {
+ compute_postnum_recursive(postnum, cur, cfg, block->successors[s]);
+ }
+ postnum[block_num] = (*cur)++;
+}
+/* }}} */
+
+/* Computes dominator tree using algorithm from "A Simple, Fast Dominance Algorithm" by
+ * Cooper, Harvey and Kennedy. */
+ZEND_API int zend_cfg_compute_dominators_tree(const zend_op_array *op_array, zend_cfg *cfg) /* {{{ */
+{
+ zend_basic_block *blocks = cfg->blocks;
+ int blocks_count = cfg->blocks_count;
+ int j, k, changed;
+
+ ALLOCA_FLAG(use_heap)
+ int *postnum = do_alloca(sizeof(int) * cfg->blocks_count, use_heap);
+ memset(postnum, -1, sizeof(int) * cfg->blocks_count);
+ j = 0;
+ compute_postnum_recursive(postnum, &j, cfg, 0);
+
+ /* FIXME: move declarations */
+ blocks[0].idom = 0;
+ do {
+ changed = 0;
+ /* Iterating in RPO here would converge faster */
+ for (j = 1; j < blocks_count; j++) {
+ int idom = -1;
+
+ if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) {
+ continue;
+ }
+ for (k = 0; k < blocks[j].predecessors_count; k++) {
+ int pred = cfg->predecessors[blocks[j].predecessor_offset + k];
+
+ if (idom < 0) {
+ if (blocks[pred].idom >= 0)
+ idom = pred;
+ continue;
+ }
+
+ if (blocks[pred].idom >= 0) {
+ while (idom != pred) {
+ while (postnum[pred] < postnum[idom]) pred = blocks[pred].idom;
+ while (postnum[idom] < postnum[pred]) idom = blocks[idom].idom;
+ }
+ }
+ }
+
+ if (idom >= 0 && blocks[j].idom != idom) {
+ blocks[j].idom = idom;
+ changed = 1;
+ }
+ }
+ } while (changed);
+ blocks[0].idom = -1;
+
+ for (j = 1; j < blocks_count; j++) {
+ if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) {
+ continue;
+ }
+ if (blocks[j].idom >= 0) {
+ /* Sort by block number to traverse children in pre-order */
+ if (blocks[blocks[j].idom].children < 0 ||
+ j < blocks[blocks[j].idom].children) {
+ blocks[j].next_child = blocks[blocks[j].idom].children;
+ blocks[blocks[j].idom].children = j;
+ } else {
+ int k = blocks[blocks[j].idom].children;
+ while (blocks[k].next_child >=0 && j > blocks[k].next_child) {
+ k = blocks[k].next_child;
+ }
+ blocks[j].next_child = blocks[k].next_child;
+ blocks[k].next_child = j;
+ }
+ }
+ }
+
+ for (j = 0; j < blocks_count; j++) {
+ int idom = blocks[j].idom, level = 0;
+ if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) {
+ continue;
+ }
+ while (idom >= 0) {
+ level++;
+ if (blocks[idom].level >= 0) {
+ level += blocks[idom].level;
+ break;
+ } else {
+ idom = blocks[idom].idom;
+ }
+ }
+ blocks[j].level = level;
+ }
+
+ free_alloca(postnum, use_heap);
+ return SUCCESS;
+}
+/* }}} */
+
+static int dominates(zend_basic_block *blocks, int a, int b) /* {{{ */
+{
+ while (blocks[b].level > blocks[a].level) {
+ b = blocks[b].idom;
+ }
+ return a == b;
+}
+/* }}} */
+
+typedef struct {
+ int id;
+ int level;
+} block_info;
+static int compare_block_level(const block_info *a, const block_info *b) {
+ return b->level - a->level;
+}
+static void swap_blocks(block_info *a, block_info *b) {
+ block_info tmp = *a;
+ *a = *b;
+ *b = tmp;
+}
+
+ZEND_API int zend_cfg_identify_loops(const zend_op_array *op_array, zend_cfg *cfg) /* {{{ */
+{
+ int i, j, k, n;
+ int time;
+ zend_basic_block *blocks = cfg->blocks;
+ int *entry_times, *exit_times;
+ zend_worklist work;
+ int flag = ZEND_FUNC_NO_LOOPS;
+ block_info *sorted_blocks;
+ ALLOCA_FLAG(list_use_heap)
+ ALLOCA_FLAG(tree_use_heap)
+ ALLOCA_FLAG(sorted_blocks_use_heap)
+
+ ZEND_WORKLIST_ALLOCA(&work, cfg->blocks_count, list_use_heap);
+
+ /* We don't materialize the DJ spanning tree explicitly, as we are only interested in ancestor
+ * queries. These are implemented by checking entry/exit times of the DFS search. */
+ entry_times = do_alloca(2 * sizeof(int) * cfg->blocks_count, tree_use_heap);
+ exit_times = entry_times + cfg->blocks_count;
+ memset(entry_times, -1, 2 * sizeof(int) * cfg->blocks_count);
+
+ zend_worklist_push(&work, 0);
+ time = 0;
+ while (zend_worklist_len(&work)) {
+ next:
+ i = zend_worklist_peek(&work);
+ if (entry_times[i] == -1) {
+ entry_times[i] = time++;
+ }
+ /* Visit blocks immediately dominated by i. */
+ for (j = blocks[i].children; j >= 0; j = blocks[j].next_child) {
+ if (zend_worklist_push(&work, j)) {
+ goto next;
+ }
+ }
+ /* Visit join edges. */
+ for (j = 0; j < blocks[i].successors_count; j++) {
+ int succ = blocks[i].successors[j];
+ if (blocks[succ].idom == i) {
+ continue;
+ } else if (zend_worklist_push(&work, succ)) {
+ goto next;
+ }
+ }
+ exit_times[i] = time++;
+ zend_worklist_pop(&work);
+ }
+
+ /* Sort blocks by decreasing level, which is the order in which we want to process them */
+ sorted_blocks = do_alloca(sizeof(block_info) * cfg->blocks_count, sorted_blocks_use_heap);
+ for (i = 0; i < cfg->blocks_count; i++) {
+ sorted_blocks[i].id = i;
+ sorted_blocks[i].level = blocks[i].level;
+ }
+ zend_sort(sorted_blocks, cfg->blocks_count, sizeof(block_info),
+ (compare_func_t) compare_block_level, (swap_func_t) swap_blocks);
+
+ /* Identify loops. See Sreedhar et al, "Identifying Loops Using DJ
+ Graphs". */
+
+ for (n = 0; n < cfg->blocks_count; n++) {
+ i = sorted_blocks[n].id;
+
+ zend_bitset_clear(work.visited, zend_bitset_len(cfg->blocks_count));
+ for (j = 0; j < blocks[i].predecessors_count; j++) {
+ int pred = cfg->predecessors[blocks[i].predecessor_offset + j];
+
+ /* A join edge is one for which the predecessor does not
+ immediately dominate the successor. */
+ if (blocks[i].idom == pred) {
+ continue;
+ }
+
+ /* In a loop back-edge (back-join edge), the successor dominates
+ the predecessor. */
+ if (dominates(blocks, i, pred)) {
+ blocks[i].flags |= ZEND_BB_LOOP_HEADER;
+ flag &= ~ZEND_FUNC_NO_LOOPS;
+ zend_worklist_push(&work, pred);
+ } else {
+ /* Otherwise it's a cross-join edge. See if it's a branch
+ to an ancestor on the DJ spanning tree. */
+ if (entry_times[pred] > entry_times[i] && exit_times[pred] < exit_times[i]) {
+ blocks[i].flags |= ZEND_BB_IRREDUCIBLE_LOOP;
+ flag |= ZEND_FUNC_IRREDUCIBLE;
+ flag &= ~ZEND_FUNC_NO_LOOPS;
+ }
+ }
+ }
+ while (zend_worklist_len(&work)) {
+ j = zend_worklist_pop(&work);
+ while (blocks[j].loop_header >= 0) {
+ j = blocks[j].loop_header;
+ }
+ if (j != i) {
+ blocks[j].loop_header = i;
+ for (k = 0; k < blocks[j].predecessors_count; k++) {
+ zend_worklist_push(&work, cfg->predecessors[blocks[j].predecessor_offset + k]);
+ }
+ }
+ }
+ }
+
+ free_alloca(sorted_blocks, sorted_blocks_use_heap);
+ free_alloca(entry_times, tree_use_heap);
+ ZEND_WORKLIST_FREE_ALLOCA(&work, list_use_heap);
+
+ cfg->flags |= flag;
+
+ return SUCCESS;
+}
+/* }}} */
diff --git a/Zend/Optimizer/zend_cfg.h b/Zend/Optimizer/zend_cfg.h
new file mode 100644
index 0000000000..6fff720ed3
--- /dev/null
+++ b/Zend/Optimizer/zend_cfg.h
@@ -0,0 +1,127 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, CFG - Control Flow Graph |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef ZEND_CFG_H
+#define ZEND_CFG_H
+
+/* zend_basic_block.flags */
+#define ZEND_BB_START (1<<0) /* first block */
+#define ZEND_BB_FOLLOW (1<<1) /* follows the next block */
+#define ZEND_BB_TARGET (1<<2) /* jump target */
+#define ZEND_BB_EXIT (1<<3) /* without successors */
+#define ZEND_BB_ENTRY (1<<4) /* stackless entry */
+#define ZEND_BB_TRY (1<<5) /* start of try block */
+#define ZEND_BB_CATCH (1<<6) /* start of catch block */
+#define ZEND_BB_FINALLY (1<<7) /* start of finally block */
+#define ZEND_BB_FINALLY_END (1<<8) /* end of finally block */
+#define ZEND_BB_UNREACHABLE_FREE (1<<11) /* unreachable loop free */
+#define ZEND_BB_RECV_ENTRY (1<<12) /* RECV entry */
+
+#define ZEND_BB_LOOP_HEADER (1<<16)
+#define ZEND_BB_IRREDUCIBLE_LOOP (1<<17)
+
+#define ZEND_BB_REACHABLE (1U<<31)
+
+#define ZEND_BB_PROTECTED (ZEND_BB_ENTRY|ZEND_BB_RECV_ENTRY|ZEND_BB_TRY|ZEND_BB_CATCH|ZEND_BB_FINALLY|ZEND_BB_FINALLY_END|ZEND_BB_UNREACHABLE_FREE)
+
+typedef struct _zend_basic_block {
+ int *successors; /* successor block indices */
+ uint32_t flags;
+ uint32_t start; /* first opcode number */
+ uint32_t len; /* number of opcodes */
+ int successors_count; /* number of successors */
+ int predecessors_count; /* number of predecessors */
+ int predecessor_offset; /* offset of 1-st predecessor */
+ int idom; /* immediate dominator block */
+ int loop_header; /* closest loop header, or -1 */
+ int level; /* steps away from the entry in the dom. tree */
+ int children; /* list of dominated blocks */
+ int next_child; /* next dominated block */
+ int successors_storage[2]; /* up to 2 successor blocks */
+} zend_basic_block;
+
+/*
++------------+---+---+---+---+---+
+| |OP1|OP2|EXT| 0 | 1 |
++------------+---+---+---+---+---+
+|JMP |ADR| | |OP1| - |
+|JMPZ | |ADR| |OP2|FOL|
+|JMPNZ | |ADR| |OP2|FOL|
+|JMPZNZ | |ADR|ADR|OP2|EXT|
+|JMPZ_EX | |ADR| |OP2|FOL|
+|JMPNZ_EX | |ADR| |OP2|FOL|
+|JMP_SET | |ADR| |OP2|FOL|
+|COALESCE | |ADR| |OP2|FOL|
+|ASSERT_CHK | |ADR| |OP2|FOL|
+|NEW | |ADR| |OP2|FOL|
+|DCL_ANON* |ADR| | |OP1|FOL|
+|FE_RESET_* | |ADR| |OP2|FOL|
+|FE_FETCH_* | | |ADR|EXT|FOL|
+|CATCH | | |ADR|EXT|FOL|
+|FAST_CALL |ADR| | |OP1|FOL|
+|FAST_RET | | | | - | - |
+|RETURN* | | | | - | - |
+|EXIT | | | | - | - |
+|THROW | | | | - | - |
+|* | | | |FOL| - |
++------------+---+---+---+---+---+
+*/
+
+typedef struct _zend_cfg {
+ int blocks_count; /* number of basic blocks */
+ int edges_count; /* number of edges */
+ zend_basic_block *blocks; /* array of basic blocks */
+ int *predecessors;
+ uint32_t *map;
+ uint32_t flags;
+} zend_cfg;
+
+/* Build Flags */
+#define ZEND_CFG_STACKLESS (1<<30)
+#define ZEND_SSA_DEBUG_LIVENESS (1<<29)
+#define ZEND_SSA_DEBUG_PHI_PLACEMENT (1<<28)
+#define ZEND_SSA_RC_INFERENCE (1<<27)
+#define ZEND_CFG_NO_ENTRY_PREDECESSORS (1<<25)
+#define ZEND_CFG_RECV_ENTRY (1<<24)
+#define ZEND_CALL_TREE (1<<23)
+#define ZEND_SSA_USE_CV_RESULTS (1<<22)
+
+#define CRT_CONSTANT_EX(op_array, opline, node) \
+ (((op_array)->fn_flags & ZEND_ACC_DONE_PASS_TWO) ? \
+ RT_CONSTANT(opline, (node)) \
+ : \
+ CT_CONSTANT_EX(op_array, (node).constant) \
+ )
+
+#define CRT_CONSTANT(node) \
+ CRT_CONSTANT_EX(op_array, opline, node)
+
+#define RETURN_VALUE_USED(opline) \
+ ((opline)->result_type != IS_UNUSED)
+
+BEGIN_EXTERN_C()
+
+ZEND_API int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t build_flags, zend_cfg *cfg);
+void zend_cfg_remark_reachable_blocks(const zend_op_array *op_array, zend_cfg *cfg);
+ZEND_API int zend_cfg_build_predecessors(zend_arena **arena, zend_cfg *cfg);
+ZEND_API int zend_cfg_compute_dominators_tree(const zend_op_array *op_array, zend_cfg *cfg);
+ZEND_API int zend_cfg_identify_loops(const zend_op_array *op_array, zend_cfg *cfg);
+
+END_EXTERN_C()
+
+#endif /* ZEND_CFG_H */
diff --git a/Zend/Optimizer/zend_dfg.c b/Zend/Optimizer/zend_dfg.c
new file mode 100644
index 0000000000..b059f1f0ea
--- /dev/null
+++ b/Zend/Optimizer/zend_dfg.c
@@ -0,0 +1,333 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, DFG - Data Flow Graph |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#include "php.h"
+#include "zend_compile.h"
+#include "zend_dfg.h"
+
+static zend_always_inline void _zend_dfg_add_use_def_op(const zend_op_array *op_array, const zend_op *opline, uint32_t build_flags, zend_bitset use, zend_bitset def) /* {{{ */
+{
+ uint32_t var_num;
+ const zend_op *next;
+
+ if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ var_num = EX_VAR_TO_NUM(opline->op1.var);
+ if (!zend_bitset_in(def, var_num)) {
+ zend_bitset_incl(use, var_num);
+ }
+ }
+ if (((opline->op2_type & (IS_VAR|IS_TMP_VAR)) != 0
+ && opline->opcode != ZEND_FE_FETCH_R
+ && opline->opcode != ZEND_FE_FETCH_RW)
+ || (opline->op2_type == IS_CV)) {
+ var_num = EX_VAR_TO_NUM(opline->op2.var);
+ if (!zend_bitset_in(def, var_num)) {
+ zend_bitset_incl(use, var_num);
+ }
+ }
+ if ((build_flags & ZEND_SSA_USE_CV_RESULTS)
+ && opline->result_type == IS_CV
+ && opline->opcode != ZEND_RECV) {
+ var_num = EX_VAR_TO_NUM(opline->result.var);
+ if (!zend_bitset_in(def, var_num)) {
+ zend_bitset_incl(use, var_num);
+ }
+ }
+
+ switch (opline->opcode) {
+ case ZEND_ASSIGN:
+ if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op2_type == IS_CV) {
+ zend_bitset_incl(def, EX_VAR_TO_NUM(opline->op2.var));
+ }
+ if (opline->op1_type == IS_CV) {
+add_op1_def:
+ zend_bitset_incl(def, EX_VAR_TO_NUM(opline->op1.var));
+ }
+ break;
+ case ZEND_ASSIGN_REF:
+ if (opline->op2_type == IS_CV) {
+ zend_bitset_incl(def, EX_VAR_TO_NUM(opline->op2.var));
+ }
+ if (opline->op1_type == IS_CV) {
+ goto add_op1_def;
+ }
+ break;
+ case ZEND_ASSIGN_DIM:
+ case ZEND_ASSIGN_OBJ:
+ next = opline + 1;
+ if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ var_num = EX_VAR_TO_NUM(next->op1.var);
+ if (!zend_bitset_in(def, var_num)) {
+ zend_bitset_incl(use, var_num);
+ }
+ if (build_flags & ZEND_SSA_RC_INFERENCE && next->op1_type == IS_CV) {
+ zend_bitset_incl(def, var_num);
+ }
+ }
+ if (opline->op1_type == IS_CV) {
+ goto add_op1_def;
+ }
+ break;
+ case ZEND_ASSIGN_OBJ_REF:
+ next = opline + 1;
+ if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ var_num = EX_VAR_TO_NUM(next->op1.var);
+ if (!zend_bitset_in(def, var_num)) {
+ zend_bitset_incl(use, var_num);
+ }
+ if (next->op1_type == IS_CV) {
+ zend_bitset_incl(def, var_num);
+ }
+ }
+ if (opline->op1_type == IS_CV) {
+ goto add_op1_def;
+ }
+ break;
+ case ZEND_ASSIGN_STATIC_PROP:
+ next = opline + 1;
+ if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ var_num = EX_VAR_TO_NUM(next->op1.var);
+ if (!zend_bitset_in(def, var_num)) {
+ zend_bitset_incl(use, var_num);
+ }
+ if ((build_flags & ZEND_SSA_RC_INFERENCE) && next->op1_type == IS_CV) {
+ zend_bitset_incl(def, var_num);
+ }
+ }
+ break;
+ case ZEND_ASSIGN_STATIC_PROP_REF:
+ next = opline + 1;
+ if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ var_num = EX_VAR_TO_NUM(next->op1.var);
+ if (!zend_bitset_in(def, var_num)) {
+ zend_bitset_incl(use, var_num);
+ }
+ if (next->op1_type == IS_CV) {
+ zend_bitset_incl(def, var_num);
+ }
+ }
+ break;
+ case ZEND_ASSIGN_STATIC_PROP_OP:
+ next = opline + 1;
+ if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ var_num = EX_VAR_TO_NUM(next->op1.var);
+ if (!zend_bitset_in(def, var_num)) {
+ zend_bitset_incl(use, var_num);
+ }
+ }
+ break;
+ case ZEND_ASSIGN_DIM_OP:
+ case ZEND_ASSIGN_OBJ_OP:
+ next = opline + 1;
+ if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ var_num = EX_VAR_TO_NUM(next->op1.var);
+ if (!zend_bitset_in(def, var_num)) {
+ zend_bitset_incl(use, var_num);
+ }
+ }
+ if (opline->op1_type == IS_CV) {
+ goto add_op1_def;
+ }
+ break;
+ case ZEND_ASSIGN_OP:
+ case ZEND_PRE_INC:
+ case ZEND_PRE_DEC:
+ case ZEND_POST_INC:
+ case ZEND_POST_DEC:
+ case ZEND_BIND_GLOBAL:
+ case ZEND_BIND_STATIC:
+ case ZEND_SEND_VAR_NO_REF:
+ case ZEND_SEND_VAR_NO_REF_EX:
+ case ZEND_SEND_VAR_EX:
+ case ZEND_SEND_FUNC_ARG:
+ case ZEND_SEND_REF:
+ case ZEND_SEND_UNPACK:
+ case ZEND_FE_RESET_RW:
+ case ZEND_MAKE_REF:
+ case ZEND_PRE_INC_OBJ:
+ case ZEND_PRE_DEC_OBJ:
+ case ZEND_POST_INC_OBJ:
+ case ZEND_POST_DEC_OBJ:
+ case ZEND_UNSET_DIM:
+ case ZEND_UNSET_OBJ:
+ case ZEND_FETCH_DIM_W:
+ case ZEND_FETCH_DIM_RW:
+ case ZEND_FETCH_DIM_FUNC_ARG:
+ case ZEND_FETCH_DIM_UNSET:
+ case ZEND_FETCH_LIST_W:
+ if (opline->op1_type == IS_CV) {
+ goto add_op1_def;
+ }
+ break;
+ case ZEND_SEND_VAR:
+ case ZEND_CAST:
+ case ZEND_QM_ASSIGN:
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ case ZEND_FE_RESET_R:
+ if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op1_type == IS_CV) {
+ goto add_op1_def;
+ }
+ break;
+ case ZEND_ADD_ARRAY_UNPACK:
+ var_num = EX_VAR_TO_NUM(opline->result.var);
+ if (!zend_bitset_in(def, var_num)) {
+ zend_bitset_incl(use, var_num);
+ }
+ break;
+ case ZEND_ADD_ARRAY_ELEMENT:
+ var_num = EX_VAR_TO_NUM(opline->result.var);
+ if (!zend_bitset_in(def, var_num)) {
+ zend_bitset_incl(use, var_num);
+ }
+ /* break missing intentionally */
+ case ZEND_INIT_ARRAY:
+ if (((build_flags & ZEND_SSA_RC_INFERENCE)
+ || (opline->extended_value & ZEND_ARRAY_ELEMENT_REF))
+ && opline->op1_type == IS_CV) {
+ goto add_op1_def;
+ }
+ break;
+ case ZEND_YIELD:
+ if (opline->op1_type == IS_CV
+ && ((op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE)
+ || (build_flags & ZEND_SSA_RC_INFERENCE))) {
+ goto add_op1_def;
+ }
+ break;
+ case ZEND_UNSET_CV:
+ goto add_op1_def;
+ case ZEND_VERIFY_RETURN_TYPE:
+ if (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) {
+ goto add_op1_def;
+ }
+ break;
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+#if 0
+ /* This special case was handled above the switch */
+ if (opline->op2_type != IS_CV) {
+ op2_use = -1; /* not used */
+ }
+#endif
+ zend_bitset_incl(def, EX_VAR_TO_NUM(opline->op2.var));
+ break;
+ case ZEND_BIND_LEXICAL:
+ if ((opline->extended_value & ZEND_BIND_REF) || (build_flags & ZEND_SSA_RC_INFERENCE)) {
+ zend_bitset_incl(def, EX_VAR_TO_NUM(opline->op2.var));
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ zend_bitset_incl(def, EX_VAR_TO_NUM(opline->result.var));
+ }
+}
+/* }}} */
+
+ZEND_API void zend_dfg_add_use_def_op(const zend_op_array *op_array, const zend_op *opline, uint32_t build_flags, zend_bitset use, zend_bitset def) /* {{{ */
+{
+ _zend_dfg_add_use_def_op(op_array, opline, build_flags, use, def);
+}
+/* }}} */
+
+int zend_build_dfg(const zend_op_array *op_array, const zend_cfg *cfg, zend_dfg *dfg, uint32_t build_flags) /* {{{ */
+{
+ int set_size;
+ zend_basic_block *blocks = cfg->blocks;
+ int blocks_count = cfg->blocks_count;
+ zend_bitset tmp, def, use, in, out;
+ int k;
+ int j;
+
+ set_size = dfg->size;
+ tmp = dfg->tmp;
+ def = dfg->def;
+ use = dfg->use;
+ in = dfg->in;
+ out = dfg->out;
+
+ /* Collect "def" and "use" sets */
+ for (j = 0; j < blocks_count; j++) {
+ zend_op *opline, *end;
+ zend_bitset b_use, b_def;
+
+ if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) {
+ continue;
+ }
+
+ opline = op_array->opcodes + blocks[j].start;
+ end = opline + blocks[j].len;
+ b_use = DFG_BITSET(use, set_size, j);
+ b_def = DFG_BITSET(def, set_size, j);
+ for (; opline < end; opline++) {
+ if (opline->opcode != ZEND_OP_DATA) {
+ _zend_dfg_add_use_def_op(op_array, opline, build_flags, b_use, b_def);
+ }
+ }
+ }
+
+ /* Calculate "in" and "out" sets */
+ {
+ uint32_t worklist_len = zend_bitset_len(blocks_count);
+ zend_bitset worklist;
+ ALLOCA_FLAG(use_heap);
+ worklist = ZEND_BITSET_ALLOCA(worklist_len, use_heap);
+ memset(worklist, 0, worklist_len * ZEND_BITSET_ELM_SIZE);
+ for (j = 0; j < blocks_count; j++) {
+ zend_bitset_incl(worklist, j);
+ }
+ while (!zend_bitset_empty(worklist, worklist_len)) {
+ /* We use the last block on the worklist, because predecessors tend to be located
+ * before the succeeding block, so this converges faster. */
+ j = zend_bitset_last(worklist, worklist_len);
+ zend_bitset_excl(worklist, j);
+
+ if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) {
+ continue;
+ }
+ if (blocks[j].successors_count != 0) {
+ zend_bitset_copy(DFG_BITSET(out, set_size, j), DFG_BITSET(in, set_size, blocks[j].successors[0]), set_size);
+ for (k = 1; k < blocks[j].successors_count; k++) {
+ zend_bitset_union(DFG_BITSET(out, set_size, j), DFG_BITSET(in, set_size, blocks[j].successors[k]), set_size);
+ }
+ } else {
+ zend_bitset_clear(DFG_BITSET(out, set_size, j), set_size);
+ }
+ zend_bitset_union_with_difference(tmp, DFG_BITSET(use, set_size, j), DFG_BITSET(out, set_size, j), DFG_BITSET(def, set_size, j), set_size);
+ if (!zend_bitset_equal(DFG_BITSET(in, set_size, j), tmp, set_size)) {
+ zend_bitset_copy(DFG_BITSET(in, set_size, j), tmp, set_size);
+
+ /* Add predecessors of changed block to worklist */
+ {
+ int *predecessors = &cfg->predecessors[blocks[j].predecessor_offset];
+ for (k = 0; k < blocks[j].predecessors_count; k++) {
+ zend_bitset_incl(worklist, predecessors[k]);
+ }
+ }
+ }
+ }
+
+ free_alloca(worklist, use_heap);
+ }
+
+ return SUCCESS;
+}
+/* }}} */
diff --git a/Zend/Optimizer/zend_dfg.h b/Zend/Optimizer/zend_dfg.h
new file mode 100644
index 0000000000..6ec92be307
--- /dev/null
+++ b/Zend/Optimizer/zend_dfg.h
@@ -0,0 +1,51 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, DFG - Data Flow Graph |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef ZEND_DFG_H
+#define ZEND_DFG_H
+
+#include "zend_bitset.h"
+#include "zend_cfg.h"
+
+typedef struct _zend_dfg {
+ int vars;
+ uint32_t size;
+ zend_bitset tmp;
+ zend_bitset def;
+ zend_bitset use;
+ zend_bitset in;
+ zend_bitset out;
+} zend_dfg;
+
+#define DFG_BITSET(set, set_size, block_num) \
+ ((set) + ((block_num) * (set_size)))
+
+#define DFG_SET(set, set_size, block_num, var_num) \
+ zend_bitset_incl(DFG_BITSET(set, set_size, block_num), (var_num))
+
+#define DFG_ISSET(set, set_size, block_num, var_num) \
+ zend_bitset_in(DFG_BITSET(set, set_size, block_num), (var_num))
+
+BEGIN_EXTERN_C()
+
+int zend_build_dfg(const zend_op_array *op_array, const zend_cfg *cfg, zend_dfg *dfg, uint32_t build_flags);
+ZEND_API void zend_dfg_add_use_def_op(const zend_op_array *op_array, const zend_op *opline, uint32_t build_flags, zend_bitset use, zend_bitset def);
+
+END_EXTERN_C()
+
+#endif /* ZEND_DFG_H */
diff --git a/Zend/Optimizer/zend_dump.c b/Zend/Optimizer/zend_dump.c
new file mode 100644
index 0000000000..e2fdbbcbf7
--- /dev/null
+++ b/Zend/Optimizer/zend_dump.c
@@ -0,0 +1,1239 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, Bytecode Visualisation |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#include "php.h"
+#include "zend_compile.h"
+#include "zend_cfg.h"
+#include "zend_ssa.h"
+#include "zend_inference.h"
+#include "zend_func_info.h"
+#include "zend_call_graph.h"
+#include "zend_dump.h"
+
+void zend_dump_ht(HashTable *ht)
+{
+ zend_ulong index;
+ zend_string *key;
+ zval *val;
+ int first = 1;
+
+ ZEND_HASH_FOREACH_KEY_VAL(ht, index, key, val) {
+ if (first) {
+ first = 0;
+ } else {
+ fprintf(stderr, ", ");
+ }
+ if (key) {
+ fprintf(stderr, "\"%s\"", ZSTR_VAL(key));
+ } else {
+ fprintf(stderr, ZEND_LONG_FMT, index);
+ }
+ fprintf(stderr, " =>");
+ zend_dump_const(val);
+ } ZEND_HASH_FOREACH_END();
+}
+
+void zend_dump_const(const zval *zv)
+{
+ switch (Z_TYPE_P(zv)) {
+ case IS_NULL:
+ fprintf(stderr, " null");
+ break;
+ case IS_FALSE:
+ fprintf(stderr, " bool(false)");
+ break;
+ case IS_TRUE:
+ fprintf(stderr, " bool(true)");
+ break;
+ case IS_LONG:
+ fprintf(stderr, " int(" ZEND_LONG_FMT ")", Z_LVAL_P(zv));
+ break;
+ case IS_DOUBLE:
+ fprintf(stderr, " float(%g)", Z_DVAL_P(zv));
+ break;
+ case IS_STRING:
+ fprintf(stderr, " string(\"%s\")", Z_STRVAL_P(zv));
+ break;
+ case IS_ARRAY:
+ fprintf(stderr, " array(...)");
+ break;
+ default:
+ fprintf(stderr, " zval(type=%d)", Z_TYPE_P(zv));
+ break;
+ }
+}
+
+static void zend_dump_class_fetch_type(uint32_t fetch_type)
+{
+ switch (fetch_type & ZEND_FETCH_CLASS_MASK) {
+ case ZEND_FETCH_CLASS_SELF:
+ fprintf(stderr, " (self)");
+ break;
+ case ZEND_FETCH_CLASS_PARENT:
+ fprintf(stderr, " (parent)");
+ break;
+ case ZEND_FETCH_CLASS_STATIC:
+ fprintf(stderr, " (static)");
+ break;
+ case ZEND_FETCH_CLASS_AUTO:
+ fprintf(stderr, " (auto)");
+ break;
+ case ZEND_FETCH_CLASS_INTERFACE:
+ fprintf(stderr, " (interface)");
+ break;
+ case ZEND_FETCH_CLASS_TRAIT:
+ fprintf(stderr, " (trait)");
+ break;
+ }
+ if (fetch_type & ZEND_FETCH_CLASS_NO_AUTOLOAD) {
+ fprintf(stderr, " (no-autolod)");
+ }
+ if (fetch_type & ZEND_FETCH_CLASS_SILENT) {
+ fprintf(stderr, " (silent)");
+ }
+ if (fetch_type & ZEND_FETCH_CLASS_EXCEPTION) {
+ fprintf(stderr, " (exception)");
+ }
+}
+
+static void zend_dump_unused_op(const zend_op *opline, znode_op op, uint32_t flags) {
+ if (ZEND_VM_OP_NUM == (flags & ZEND_VM_OP_MASK)) {
+ fprintf(stderr, " %u", op.num);
+ } else if (ZEND_VM_OP_TRY_CATCH == (flags & ZEND_VM_OP_MASK)) {
+ if (op.num != (uint32_t)-1) {
+ fprintf(stderr, " try-catch(%u)", op.num);
+ }
+ } else if (ZEND_VM_OP_THIS == (flags & ZEND_VM_OP_MASK)) {
+ fprintf(stderr, " THIS");
+ } else if (ZEND_VM_OP_NEXT == (flags & ZEND_VM_OP_MASK)) {
+ fprintf(stderr, " NEXT");
+ } else if (ZEND_VM_OP_CLASS_FETCH == (flags & ZEND_VM_OP_MASK)) {
+ zend_dump_class_fetch_type(op.num);
+ } else if (ZEND_VM_OP_CONSTRUCTOR == (flags & ZEND_VM_OP_MASK)) {
+ fprintf(stderr, " CONSTRUCTOR");
+ } else if (ZEND_VM_OP_CONST_FETCH == (flags & ZEND_VM_OP_MASK)) {
+ if (op.num & IS_CONSTANT_UNQUALIFIED_IN_NAMESPACE) {
+ fprintf(stderr, " (unqualified-in-namespace)");
+ }
+ }
+}
+
+ZEND_API void zend_dump_var(const zend_op_array *op_array, zend_uchar var_type, int var_num)
+{
+ if (var_type == IS_CV && var_num < op_array->last_var) {
+ fprintf(stderr, "CV%d($%s)", var_num, op_array->vars[var_num]->val);
+ } else if (var_type == IS_VAR) {
+ fprintf(stderr, "V%d", var_num);
+ } else if ((var_type & (IS_VAR|IS_TMP_VAR)) == IS_TMP_VAR) {
+ fprintf(stderr, "T%d", var_num);
+ } else {
+ fprintf(stderr, "X%d", var_num);
+ }
+}
+
+static void zend_dump_range(const zend_ssa_range *r)
+{
+ if (r->underflow && r->overflow) {
+ return;
+ }
+ fprintf(stderr, " RANGE[");
+ if (r->underflow) {
+ fprintf(stderr, "--..");
+ } else if (r->min == ZEND_LONG_MIN) {
+ fprintf(stderr, "MIN..");
+ } else {
+ fprintf(stderr, ZEND_LONG_FMT "..", r->min);
+ }
+ if (r->overflow) {
+ fprintf(stderr, "++]");
+ } else if (r->max == ZEND_LONG_MAX) {
+ fprintf(stderr, "MAX]");
+ } else {
+ fprintf(stderr, ZEND_LONG_FMT "]", r->max);
+ }
+}
+
+static void zend_dump_type_info(uint32_t info, zend_class_entry *ce, int is_instanceof, uint32_t dump_flags)
+{
+ int first = 1;
+
+ fprintf(stderr, " [");
+ if (info & MAY_BE_GUARD) {
+ fprintf(stderr, "!");
+ }
+ if (info & MAY_BE_UNDEF) {
+ if (first) first = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "undef");
+ }
+ if (info & MAY_BE_REF) {
+ if (first) first = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "ref");
+ }
+ if (dump_flags & ZEND_DUMP_RC_INFERENCE) {
+ if (info & MAY_BE_RC1) {
+ if (first) first = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "rc1");
+ }
+ if (info & MAY_BE_RCN) {
+ if (first) first = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "rcn");
+ }
+ }
+ if (info & MAY_BE_CLASS) {
+ if (first) first = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "class");
+ if (ce) {
+ if (is_instanceof) {
+ fprintf(stderr, " (instanceof %s)", ce->name->val);
+ } else {
+ fprintf(stderr, " (%s)", ce->name->val);
+ }
+ }
+ } else if ((info & MAY_BE_ANY) == MAY_BE_ANY) {
+ if (first) first = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "any");
+ } else {
+ if (info & MAY_BE_NULL) {
+ if (first) first = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "null");
+ }
+ if ((info & MAY_BE_FALSE) && (info & MAY_BE_TRUE)) {
+ if (first) first = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "bool");
+ } else if (info & MAY_BE_FALSE) {
+ if (first) first = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "false");
+ } else if (info & MAY_BE_TRUE) {
+ if (first) first = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "true");
+ }
+ if (info & MAY_BE_LONG) {
+ if (first) first = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "long");
+ }
+ if (info & MAY_BE_DOUBLE) {
+ if (first) first = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "double");
+ }
+ if (info & MAY_BE_STRING) {
+ if (first) first = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "string");
+ }
+ if (info & MAY_BE_ARRAY) {
+ if (first) first = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "array");
+ if ((info & MAY_BE_ARRAY_KEY_ANY) != 0 &&
+ ((info & MAY_BE_ARRAY_KEY_LONG) == 0 ||
+ (info & MAY_BE_ARRAY_KEY_STRING) == 0)) {
+ int afirst = 1;
+ fprintf(stderr, " [");
+ if (info & MAY_BE_ARRAY_KEY_LONG) {
+ if (afirst) afirst = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "long");
+ }
+ if (info & MAY_BE_ARRAY_KEY_STRING) {
+ if (afirst) afirst = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "string");
+ }
+ fprintf(stderr, "]");
+ }
+ if (info & (MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF)) {
+ int afirst = 1;
+ fprintf(stderr, " of [");
+ if ((info & MAY_BE_ARRAY_OF_ANY) == MAY_BE_ARRAY_OF_ANY) {
+ if (afirst) afirst = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "any");
+ } else {
+ if (info & MAY_BE_ARRAY_OF_NULL) {
+ if (afirst) afirst = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "null");
+ }
+ if (info & MAY_BE_ARRAY_OF_FALSE) {
+ if (afirst) afirst = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "false");
+ }
+ if (info & MAY_BE_ARRAY_OF_TRUE) {
+ if (afirst) afirst = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "true");
+ }
+ if (info & MAY_BE_ARRAY_OF_LONG) {
+ if (afirst) afirst = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "long");
+ }
+ if (info & MAY_BE_ARRAY_OF_DOUBLE) {
+ if (afirst) afirst = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "double");
+ }
+ if (info & MAY_BE_ARRAY_OF_STRING) {
+ if (afirst) afirst = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "string");
+ }
+ if (info & MAY_BE_ARRAY_OF_ARRAY) {
+ if (afirst) afirst = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "array");
+ }
+ if (info & MAY_BE_ARRAY_OF_OBJECT) {
+ if (afirst) afirst = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "object");
+ }
+ if (info & MAY_BE_ARRAY_OF_RESOURCE) {
+ if (afirst) afirst = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "resource");
+ }
+ }
+ if (info & MAY_BE_ARRAY_OF_REF) {
+ if (afirst) afirst = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "ref");
+ }
+ fprintf(stderr, "]");
+ }
+ }
+ if (info & MAY_BE_OBJECT) {
+ if (first) first = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "object");
+ if (ce) {
+ if (is_instanceof) {
+ fprintf(stderr, " (instanceof %s)", ce->name->val);
+ } else {
+ fprintf(stderr, " (%s)", ce->name->val);
+ }
+ }
+ }
+ if (info & MAY_BE_RESOURCE) {
+ if (first) first = 0; else fprintf(stderr, ", ");
+ fprintf(stderr, "resource");
+ }
+ }
+ fprintf(stderr, "]");
+}
+
+static void zend_dump_ssa_var_info(const zend_ssa *ssa, int ssa_var_num, uint32_t dump_flags)
+{
+ zend_dump_type_info(
+ ssa->var_info[ssa_var_num].type,
+ ssa->var_info[ssa_var_num].ce,
+ ssa->var_info[ssa_var_num].ce ?
+ ssa->var_info[ssa_var_num].is_instanceof : 0,
+ dump_flags);
+}
+
+ZEND_API void zend_dump_ssa_var(const zend_op_array *op_array, const zend_ssa *ssa, int ssa_var_num, zend_uchar var_type, int var_num, uint32_t dump_flags)
+{
+ if (ssa_var_num >= 0) {
+ fprintf(stderr, "#%d.", ssa_var_num);
+ } else {
+ fprintf(stderr, "#?.");
+ }
+ zend_dump_var(op_array, (var_num < op_array->last_var ? IS_CV : var_type), var_num);
+
+ if (ssa_var_num >= 0 && ssa->vars) {
+ if (ssa->vars[ssa_var_num].no_val) {
+ fprintf(stderr, " NOVAL");
+ }
+ if (ssa->vars[ssa_var_num].escape_state == ESCAPE_STATE_NO_ESCAPE) {
+ fprintf(stderr, " NOESC");
+ }
+ if (ssa->var_info) {
+ zend_dump_ssa_var_info(ssa, ssa_var_num, dump_flags);
+ if (ssa->var_info[ssa_var_num].has_range) {
+ zend_dump_range(&ssa->var_info[ssa_var_num].range);
+ }
+ }
+ }
+}
+
+static void zend_dump_type_constraint(const zend_op_array *op_array, const zend_ssa *ssa, const zend_ssa_type_constraint *constraint, uint32_t dump_flags)
+{
+ fprintf(stderr, " TYPE");
+ zend_dump_type_info(constraint->type_mask, constraint->ce, 1, dump_flags);
+}
+
+static void zend_dump_range_constraint(const zend_op_array *op_array, const zend_ssa *ssa, const zend_ssa_range_constraint *r, uint32_t dump_flags)
+{
+ if (r->range.underflow && r->range.overflow) {
+ return;
+ }
+ fprintf(stderr, " RANGE");
+ if (r->negative) {
+ fprintf(stderr, "~");
+ }
+ fprintf(stderr, "[");
+ if (r->range.underflow) {
+ fprintf(stderr, "-- .. ");
+ } else {
+ if (r->min_ssa_var >= 0) {
+ zend_dump_ssa_var(op_array, ssa, r->min_ssa_var, (r->min_var < op_array->last_var ? IS_CV : 0), r->min_var, dump_flags);
+ if (r->range.min > 0) {
+ fprintf(stderr, " + " ZEND_LONG_FMT, r->range.min);
+ } else if (r->range.min < 0) {
+ fprintf(stderr, " - " ZEND_LONG_FMT, -r->range.min);
+ }
+ fprintf(stderr, " .. ");
+ } else {
+ fprintf(stderr, ZEND_LONG_FMT " .. ", r->range.min);
+ }
+ }
+ if (r->range.overflow) {
+ fprintf(stderr, "++]");
+ } else {
+ if (r->max_ssa_var >= 0) {
+ zend_dump_ssa_var(op_array, ssa, r->max_ssa_var, (r->max_var < op_array->last_var ? IS_CV : 0), r->max_var, dump_flags);
+ if (r->range.max > 0) {
+ fprintf(stderr, " + " ZEND_LONG_FMT, r->range.max);
+ } else if (r->range.max < 0) {
+ fprintf(stderr, " - " ZEND_LONG_FMT, -r->range.max);
+ }
+ fprintf(stderr, "]");
+ } else {
+ fprintf(stderr, ZEND_LONG_FMT "]", r->range.max);
+ }
+ }
+}
+
+ZEND_API void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *b, const zend_op *opline, uint32_t dump_flags, const zend_ssa *ssa, const zend_ssa_op *ssa_op)
+{
+ const char *name = zend_get_opcode_name(opline->opcode);
+ uint32_t flags = zend_get_opcode_flags(opline->opcode);
+ uint32_t n = 0;
+
+ if (!ssa_op || ssa_op->result_use < 0) {
+ if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ if (ssa_op && ssa_op->result_def >= 0) {
+ int ssa_var_num = ssa_op->result_def;
+ zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->result_type, EX_VAR_TO_NUM(opline->result.var), dump_flags);
+ } else {
+ zend_dump_var(op_array, opline->result_type, EX_VAR_TO_NUM(opline->result.var));
+ }
+ fprintf(stderr, " = ");
+ }
+ }
+
+ if (name) {
+ fprintf(stderr, "%s", (name + 5));
+ } else {
+ fprintf(stderr, "OP_%d", (int)opline->opcode);
+ }
+
+ if (ZEND_VM_EXT_NUM == (flags & ZEND_VM_EXT_MASK)) {
+ fprintf(stderr, " %u", opline->extended_value);
+ } else if (ZEND_VM_EXT_OP == (flags & ZEND_VM_EXT_MASK)) {
+ fprintf(stderr, " (%s)", zend_get_opcode_name(opline->extended_value) + 5);
+ } else if (ZEND_VM_EXT_TYPE == (flags & ZEND_VM_EXT_MASK)) {
+ switch (opline->extended_value) {
+ case IS_NULL:
+ fprintf(stderr, " (null)");
+ break;
+ case IS_FALSE:
+ fprintf(stderr, " (false)");
+ break;
+ case IS_TRUE:
+ fprintf(stderr, " (true)");
+ break;
+ case IS_LONG:
+ fprintf(stderr, " (long)");
+ break;
+ case IS_DOUBLE:
+ fprintf(stderr, " (double)");
+ break;
+ case IS_STRING:
+ fprintf(stderr, " (string)");
+ break;
+ case IS_ARRAY:
+ fprintf(stderr, " (array)");
+ break;
+ case IS_OBJECT:
+ fprintf(stderr, " (object)");
+ break;
+ case IS_RESOURCE:
+ fprintf(stderr, " (resource)");
+ break;
+ case _IS_BOOL:
+ fprintf(stderr, " (bool)");
+ break;
+ case IS_CALLABLE:
+ fprintf(stderr, " (callable)");
+ break;
+ case IS_VOID:
+ fprintf(stderr, " (void)");
+ break;
+ default:
+ fprintf(stderr, " (\?\?\?)");
+ break;
+ }
+ } else if (ZEND_VM_EXT_TYPE_MASK == (flags & ZEND_VM_EXT_MASK)) {
+ switch (opline->extended_value) {
+ case (1<<IS_NULL):
+ fprintf(stderr, " (null)");
+ break;
+ case (1<<IS_FALSE):
+ fprintf(stderr, " (false)");
+ break;
+ case (1<<IS_TRUE):
+ fprintf(stderr, " (true)");
+ break;
+ case (1<<IS_LONG):
+ fprintf(stderr, " (long)");
+ break;
+ case (1<<IS_DOUBLE):
+ fprintf(stderr, " (double)");
+ break;
+ case (1<<IS_STRING):
+ fprintf(stderr, " (string)");
+ break;
+ case (1<<IS_ARRAY):
+ fprintf(stderr, " (array)");
+ break;
+ case (1<<IS_OBJECT):
+ fprintf(stderr, " (object)");
+ break;
+ case (1<<IS_RESOURCE):
+ fprintf(stderr, " (resource)");
+ break;
+ case ((1<<IS_FALSE)|(1<<IS_TRUE)):
+ fprintf(stderr, " (bool)");
+ break;
+ default:
+ fprintf(stderr, " TYPE");
+ zend_dump_type_info(opline->extended_value, NULL, 0, dump_flags);
+ break;
+ }
+ } else if (ZEND_VM_EXT_EVAL == (flags & ZEND_VM_EXT_MASK)) {
+ switch (opline->extended_value) {
+ case ZEND_EVAL:
+ fprintf(stderr, " (eval)");
+ break;
+ case ZEND_INCLUDE:
+ fprintf(stderr, " (include)");
+ break;
+ case ZEND_INCLUDE_ONCE:
+ fprintf(stderr, " (include_once)");
+ break;
+ case ZEND_REQUIRE:
+ fprintf(stderr, " (require)");
+ break;
+ case ZEND_REQUIRE_ONCE:
+ fprintf(stderr, " (require_once)");
+ break;
+ default:
+ fprintf(stderr, " (\?\?\?)");
+ break;
+ }
+ } else if (ZEND_VM_EXT_SRC == (flags & ZEND_VM_EXT_MASK)) {
+ if (opline->extended_value == ZEND_RETURNS_VALUE) {
+ fprintf(stderr, " (value)");
+ } else if (opline->extended_value & ZEND_RETURNS_FUNCTION) {
+ fprintf(stderr, " (function)");
+ }
+ } else {
+ if (ZEND_VM_EXT_VAR_FETCH & flags) {
+ if (opline->extended_value & ZEND_FETCH_GLOBAL) {
+ fprintf(stderr, " (global)");
+ } else if (opline->extended_value & ZEND_FETCH_LOCAL) {
+ fprintf(stderr, " (local)");
+ } else if (opline->extended_value & ZEND_FETCH_GLOBAL_LOCK) {
+ fprintf(stderr, " (global+lock)");
+ }
+ }
+ if (ZEND_VM_EXT_ISSET & flags) {
+ if (!(opline->extended_value & ZEND_ISEMPTY)) {
+ fprintf(stderr, " (isset)");
+ } else {
+ fprintf(stderr, " (empty)");
+ }
+ }
+ if (ZEND_VM_EXT_ARRAY_INIT & flags) {
+ fprintf(stderr, " %u", opline->extended_value >> ZEND_ARRAY_SIZE_SHIFT);
+ if (!(opline->extended_value & ZEND_ARRAY_NOT_PACKED)) {
+ fprintf(stderr, " (packed)");
+ }
+ }
+ if (ZEND_VM_EXT_REF & flags) {
+ if (opline->extended_value & ZEND_ARRAY_ELEMENT_REF) {
+ fprintf(stderr, " (ref)");
+ }
+ }
+ if ((ZEND_VM_EXT_DIM_WRITE|ZEND_VM_EXT_FETCH_REF) & flags) {
+ uint32_t obj_flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS;
+ if (obj_flags == ZEND_FETCH_REF) {
+ fprintf(stderr, " (ref)");
+ } else if (obj_flags == ZEND_FETCH_DIM_WRITE) {
+ fprintf(stderr, " (dim write)");
+ }
+ }
+ }
+
+ if (opline->op1_type == IS_CONST) {
+ zend_dump_const(CRT_CONSTANT(opline->op1));
+ } else if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ if (ssa_op) {
+ int ssa_var_num = ssa_op->op1_use;
+ if (ssa_var_num >= 0) {
+ fprintf(stderr, " ");
+ zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->op1_type, EX_VAR_TO_NUM(opline->op1.var), dump_flags);
+ } else if (ssa_op->op1_def < 0) {
+ fprintf(stderr, " ");
+ zend_dump_var(op_array, opline->op1_type, EX_VAR_TO_NUM(opline->op1.var));
+ }
+ } else {
+ fprintf(stderr, " ");
+ zend_dump_var(op_array, opline->op1_type, EX_VAR_TO_NUM(opline->op1.var));
+ }
+ if (ssa_op) {
+ int ssa_var_num = ssa_op->op1_def;
+ if (ssa_var_num >= 0) {
+ fprintf(stderr, " -> ");
+ zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->op1_type, EX_VAR_TO_NUM(opline->op1.var), dump_flags);
+ }
+ }
+ } else {
+ uint32_t op1_flags = ZEND_VM_OP1_FLAGS(flags);
+ if (ZEND_VM_OP_JMP_ADDR == (op1_flags & ZEND_VM_OP_MASK)) {
+ if (b) {
+ fprintf(stderr, " BB%d", b->successors[n++]);
+ } else {
+ fprintf(stderr, " %04u", (uint32_t)(OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes));
+ }
+ } else {
+ zend_dump_unused_op(opline, opline->op1, op1_flags);
+ }
+ }
+
+ if (opline->op2_type == IS_CONST) {
+ zval *op = CRT_CONSTANT(opline->op2);
+ if (
+ opline->opcode == ZEND_SWITCH_LONG
+ || opline->opcode == ZEND_SWITCH_STRING
+ || opline->opcode == ZEND_MATCH
+ ) {
+ HashTable *jumptable = Z_ARRVAL_P(op);
+ zend_string *key;
+ zend_ulong num_key;
+ zval *zv;
+ ZEND_HASH_FOREACH_KEY_VAL(jumptable, num_key, key, zv) {
+ if (key) {
+ fprintf(stderr, " \"%s\":", ZSTR_VAL(key));
+ } else {
+ fprintf(stderr, " " ZEND_LONG_FMT ":", num_key);
+ }
+ if (b) {
+ fprintf(stderr, " BB%d,", b->successors[n++]);
+ } else {
+ fprintf(stderr, " %04u,", (uint32_t)ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv)));
+ }
+ } ZEND_HASH_FOREACH_END();
+ fprintf(stderr, " default:");
+ } else {
+ zend_dump_const(op);
+ }
+ } else if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ if (ssa_op) {
+ int ssa_var_num = ssa_op->op2_use;
+ if (ssa_var_num >= 0) {
+ fprintf(stderr, " ");
+ zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->op2_type, EX_VAR_TO_NUM(opline->op2.var), dump_flags);
+ } else if (ssa_op->op2_def < 0) {
+ fprintf(stderr, " ");
+ zend_dump_var(op_array, opline->op2_type, EX_VAR_TO_NUM(opline->op2.var));
+ }
+ } else {
+ fprintf(stderr, " ");
+ zend_dump_var(op_array, opline->op2_type, EX_VAR_TO_NUM(opline->op2.var));
+ }
+ if (ssa_op) {
+ int ssa_var_num = ssa_op->op2_def;
+ if (ssa_var_num >= 0) {
+ fprintf(stderr, " -> ");
+ zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->op2_type, EX_VAR_TO_NUM(opline->op2.var), dump_flags);
+ }
+ }
+ } else {
+ uint32_t op2_flags = ZEND_VM_OP2_FLAGS(flags);
+ if (ZEND_VM_OP_JMP_ADDR == (op2_flags & ZEND_VM_OP_MASK)) {
+ if (opline->opcode != ZEND_CATCH || !(opline->extended_value & ZEND_LAST_CATCH)) {
+ if (b) {
+ fprintf(stderr, " BB%d", b->successors[n++]);
+ } else {
+ fprintf(stderr, " %04u", (uint32_t)(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes));
+ }
+ }
+ } else {
+ zend_dump_unused_op(opline, opline->op2, op2_flags);
+ }
+ }
+
+ if (ZEND_VM_EXT_JMP_ADDR == (flags & ZEND_VM_EXT_MASK)) {
+ if (b) {
+ fprintf(stderr, " BB%d", b->successors[n++]);
+ } else {
+ fprintf(stderr, " %04u", (uint32_t)ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
+ }
+ }
+ if (opline->result_type == IS_CONST) {
+ zend_dump_const(CRT_CONSTANT(opline->result));
+#if 0
+ } else if (opline->result_type & IS_SMART_BRANCH_JMPZ) {
+ fprintf(stderr, " jmpz");
+ } else if (opline->result_type & IS_SMART_BRANCH_JMPNZ) {
+ fprintf(stderr, " jmpnz");
+#endif
+ } else if (ssa_op && ssa_op->result_use >= 0) {
+ if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ if (ssa_op) {
+ int ssa_var_num = ssa_op->result_use;
+ if (ssa_var_num >= 0) {
+ fprintf(stderr, " ");
+ zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->result_type, EX_VAR_TO_NUM(opline->result.var), dump_flags);
+ }
+ } else {
+ fprintf(stderr, " ");
+ zend_dump_var(op_array, opline->result_type, EX_VAR_TO_NUM(opline->result.var));
+ }
+ if (ssa_op) {
+ int ssa_var_num = ssa_op->result_def;
+ if (ssa_var_num >= 0) {
+ fprintf(stderr, " -> ");
+ zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->result_type, EX_VAR_TO_NUM(opline->result.var), dump_flags);
+ }
+ }
+ }
+ }
+}
+
+static void zend_dump_op_line(const zend_op_array *op_array, const zend_basic_block *b, const zend_op *opline, uint32_t dump_flags, const void *data)
+{
+ int len = 0;
+ const zend_ssa *ssa = NULL;
+ zend_ssa_op *ssa_op = NULL;
+
+ len = fprintf(stderr, "%04u", (uint32_t)(opline - op_array->opcodes));
+ fprintf(stderr, "%*c", 5-len, ' ');
+
+ if (dump_flags & ZEND_DUMP_SSA) {
+ ssa = (const zend_ssa*)data;
+ if (ssa && ssa->ops) {
+ ssa_op = &ssa->ops[opline - op_array->opcodes];
+ }
+ }
+
+ zend_dump_op(op_array, b, opline, dump_flags, ssa, ssa_op);
+ fprintf(stderr, "\n");
+}
+
+static void zend_dump_block_info(const zend_cfg *cfg, int n, uint32_t dump_flags)
+{
+ zend_basic_block *b = cfg->blocks + n;
+
+ if (n > 0) {
+ fprintf(stderr, "\n");
+ }
+ fprintf(stderr, "BB%d:\n ;", n);
+ if (b->flags & ZEND_BB_START) {
+ fprintf(stderr, " start");
+ }
+ if (b->flags & ZEND_BB_RECV_ENTRY) {
+ fprintf(stderr, " recv");
+ }
+ if (b->flags & ZEND_BB_FOLLOW) {
+ fprintf(stderr, " follow");
+ }
+ if (b->flags & ZEND_BB_TARGET) {
+ fprintf(stderr, " target");
+ }
+ if (b->flags & ZEND_BB_EXIT) {
+ fprintf(stderr, " exit");
+ }
+ if (b->flags & (ZEND_BB_ENTRY|ZEND_BB_RECV_ENTRY)) {
+ fprintf(stderr, " entry");
+ }
+ if (b->flags & ZEND_BB_TRY) {
+ fprintf(stderr, " try");
+ }
+ if (b->flags & ZEND_BB_CATCH) {
+ fprintf(stderr, " catch");
+ }
+ if (b->flags & ZEND_BB_FINALLY) {
+ fprintf(stderr, " finally");
+ }
+ if (b->flags & ZEND_BB_FINALLY_END) {
+ fprintf(stderr, " finally_end");
+ }
+ if (!(dump_flags & ZEND_DUMP_HIDE_UNREACHABLE) && !(b->flags & ZEND_BB_REACHABLE)) {
+ fprintf(stderr, " unreachable");
+ }
+ if (b->flags & ZEND_BB_UNREACHABLE_FREE) {
+ fprintf(stderr, " unreachable_free");
+ }
+ if (b->flags & ZEND_BB_LOOP_HEADER) {
+ fprintf(stderr, " loop_header");
+ }
+ if (b->flags & ZEND_BB_IRREDUCIBLE_LOOP) {
+ fprintf(stderr, " irreducible");
+ }
+ if (b->len != 0) {
+ fprintf(stderr, " lines=[%d-%d]", b->start, b->start + b->len - 1);
+ } else {
+ fprintf(stderr, " empty");
+ }
+ fprintf(stderr, "\n");
+
+ if (b->predecessors_count) {
+ int *p = cfg->predecessors + b->predecessor_offset;
+ int *end = p + b->predecessors_count;
+
+ fprintf(stderr, " ; from=(BB%d", *p);
+ for (p++; p < end; p++) {
+ fprintf(stderr, ", BB%d", *p);
+ }
+ fprintf(stderr, ")\n");
+ }
+
+ if (b->successors_count > 0) {
+ int s;
+ fprintf(stderr, " ; to=(BB%d", b->successors[0]);
+ for (s = 1; s < b->successors_count; s++) {
+ fprintf(stderr, ", BB%d", b->successors[s]);
+ }
+ fprintf(stderr, ")\n");
+ }
+
+ if (b->idom >= 0) {
+ fprintf(stderr, " ; idom=BB%d\n", b->idom);
+ }
+ if (b->level >= 0) {
+ fprintf(stderr, " ; level=%d\n", b->level);
+ }
+ if (b->loop_header >= 0) {
+ fprintf(stderr, " ; loop_header=%d\n", b->loop_header);
+ }
+ if (b->children >= 0) {
+ int j = b->children;
+ fprintf(stderr, " ; children=(BB%d", j);
+ j = cfg->blocks[j].next_child;
+ while (j >= 0) {
+ fprintf(stderr, ", BB%d", j);
+ j = cfg->blocks[j].next_child;
+ }
+ fprintf(stderr, ")\n");
+ }
+}
+
+static void zend_dump_block_header(const zend_cfg *cfg, const zend_op_array *op_array, const zend_ssa *ssa, int n, uint32_t dump_flags)
+{
+ zend_dump_block_info(cfg, n, dump_flags);
+ if (ssa && ssa->blocks && ssa->blocks[n].phis) {
+ zend_ssa_phi *p = ssa->blocks[n].phis;
+
+ do {
+ int j;
+
+ fprintf(stderr, " ");
+ zend_dump_ssa_var(op_array, ssa, p->ssa_var, 0, p->var, dump_flags);
+ if (p->pi < 0) {
+ fprintf(stderr, " = Phi(");
+ for (j = 0; j < cfg->blocks[n].predecessors_count; j++) {
+ if (j > 0) {
+ fprintf(stderr, ", ");
+ }
+ zend_dump_ssa_var(op_array, ssa, p->sources[j], 0, p->var, dump_flags);
+ }
+ fprintf(stderr, ")\n");
+ } else {
+ fprintf(stderr, " = Pi<BB%d>(", p->pi);
+ zend_dump_ssa_var(op_array, ssa, p->sources[0], 0, p->var, dump_flags);
+ fprintf(stderr, " &");
+ if (p->has_range_constraint) {
+ zend_dump_range_constraint(op_array, ssa, &p->constraint.range, dump_flags);
+ } else {
+ zend_dump_type_constraint(op_array, ssa, &p->constraint.type, dump_flags);
+ }
+ fprintf(stderr, ")\n");
+ }
+ p = p->next;
+ } while (p);
+ }
+}
+
+void zend_dump_op_array_name(const zend_op_array *op_array)
+{
+ if (op_array->function_name) {
+ if (op_array->scope && op_array->scope->name) {
+ fprintf(stderr, "%s::%s", op_array->scope->name->val, op_array->function_name->val);
+ } else {
+ fprintf(stderr, "%s", op_array->function_name->val);
+ }
+ } else {
+ fprintf(stderr, "%s", "$_main");
+ }
+}
+
+ZEND_API void zend_dump_op_array(const zend_op_array *op_array, uint32_t dump_flags, const char *msg, const void *data)
+{
+ int i;
+ const zend_cfg *cfg = NULL;
+ const zend_ssa *ssa = NULL;
+ zend_func_info *func_info = NULL;
+ uint32_t func_flags = 0;
+
+ if (dump_flags & (ZEND_DUMP_CFG|ZEND_DUMP_SSA)) {
+ cfg = (const zend_cfg*)data;
+ if (!cfg->blocks) {
+ cfg = data = NULL;
+ }
+ }
+ if (dump_flags & ZEND_DUMP_SSA) {
+ ssa = (const zend_ssa*)data;
+ }
+
+ func_info = ZEND_FUNC_INFO(op_array);
+ if (func_info) {
+ func_flags = func_info->flags;
+ }
+
+ fprintf(stderr, "\n");
+ zend_dump_op_array_name(op_array);
+ fprintf(stderr, ":\n ; (lines=%d, args=%d",
+ op_array->last,
+ op_array->num_args);
+ fprintf(stderr, ", vars=%d, tmps=%d", op_array->last_var, op_array->T);
+ if (ssa) {
+ fprintf(stderr, ", ssa_vars=%d", ssa->vars_count);
+ }
+ if (func_flags & ZEND_FUNC_INDIRECT_VAR_ACCESS) {
+ fprintf(stderr, ", dynamic");
+ }
+ if (func_flags & ZEND_FUNC_RECURSIVE) {
+ fprintf(stderr, ", recursive");
+ if (func_flags & ZEND_FUNC_RECURSIVE_DIRECTLY) {
+ fprintf(stderr, " directly");
+ }
+ if (func_flags & ZEND_FUNC_RECURSIVE_INDIRECTLY) {
+ fprintf(stderr, " indirectly");
+ }
+ }
+ if (func_flags & ZEND_FUNC_IRREDUCIBLE) {
+ fprintf(stderr, ", irreducable");
+ }
+ if (func_flags & ZEND_FUNC_NO_LOOPS) {
+ fprintf(stderr, ", no_loops");
+ }
+ if (func_flags & ZEND_FUNC_HAS_EXTENDED_STMT) {
+ fprintf(stderr, ", extended_stmt");
+ }
+ if (func_flags & ZEND_FUNC_HAS_EXTENDED_FCALL) {
+ fprintf(stderr, ", extended_fcall");
+ }
+//TODO: this is useful only for JIT???
+#if 0
+ if (info->flags & ZEND_JIT_FUNC_NO_IN_MEM_CVS) {
+ fprintf(stderr, ", no_in_mem_cvs");
+ }
+ if (info->flags & ZEND_JIT_FUNC_NO_USED_ARGS) {
+ fprintf(stderr, ", no_used_args");
+ }
+ if (info->flags & ZEND_JIT_FUNC_NO_SYMTAB) {
+ fprintf(stderr, ", no_symtab");
+ }
+ if (info->flags & ZEND_JIT_FUNC_NO_FRAME) {
+ fprintf(stderr, ", no_frame");
+ }
+ if (info->flags & ZEND_JIT_FUNC_INLINE) {
+ fprintf(stderr, ", inline");
+ }
+#endif
+ fprintf(stderr, ")\n");
+ if (msg) {
+ fprintf(stderr, " ; (%s)\n", msg);
+ }
+ fprintf(stderr, " ; %s:%u-%u\n", op_array->filename->val, op_array->line_start, op_array->line_end);
+
+ if (func_info) {
+ fprintf(stderr, " ; return ");
+ zend_dump_type_info(func_info->return_info.type, func_info->return_info.ce, func_info->return_info.is_instanceof, dump_flags);
+ zend_dump_range(&func_info->return_info.range);
+ fprintf(stderr, "\n");
+ }
+
+ if (ssa && ssa->var_info) {
+ for (i = 0; i < op_array->last_var; i++) {
+ fprintf(stderr, " ; ");
+ zend_dump_ssa_var(op_array, ssa, i, IS_CV, i, dump_flags);
+ fprintf(stderr, "\n");
+ }
+ }
+
+ if (cfg) {
+ int n;
+ zend_basic_block *b;
+
+ for (n = 0; n < cfg->blocks_count; n++) {
+ b = cfg->blocks + n;
+ if (!(dump_flags & ZEND_DUMP_HIDE_UNREACHABLE) || (b->flags & ZEND_BB_REACHABLE)) {
+ const zend_op *opline;
+ const zend_op *end;
+
+ zend_dump_block_header(cfg, op_array, ssa, n, dump_flags);
+ opline = op_array->opcodes + b->start;
+ end = opline + b->len;
+ while (opline < end) {
+ zend_dump_op_line(op_array, b, opline, dump_flags, data);
+ opline++;
+ }
+ }
+ }
+ if (op_array->last_live_range && (dump_flags & ZEND_DUMP_LIVE_RANGES)) {
+ fprintf(stderr, "LIVE RANGES:\n");
+ for (i = 0; i < op_array->last_live_range; i++) {
+ fprintf(stderr,
+ " %u: %04u - %04u ",
+ EX_VAR_TO_NUM(op_array->live_range[i].var & ~ZEND_LIVE_MASK),
+ op_array->live_range[i].start,
+ op_array->live_range[i].end);
+ switch (op_array->live_range[i].var & ZEND_LIVE_MASK) {
+ case ZEND_LIVE_TMPVAR:
+ fprintf(stderr, "(tmp/var)\n");
+ break;
+ case ZEND_LIVE_LOOP:
+ fprintf(stderr, "(loop)\n");
+ break;
+ case ZEND_LIVE_SILENCE:
+ fprintf(stderr, "(silence)\n");
+ break;
+ case ZEND_LIVE_ROPE:
+ fprintf(stderr, "(rope)\n");
+ break;
+ case ZEND_LIVE_NEW:
+ fprintf(stderr, "(new)\n");
+ break;
+ }
+ }
+ }
+ if (op_array->last_try_catch) {
+ fprintf(stderr, "EXCEPTION TABLE:\n");
+ for (i = 0; i < op_array->last_try_catch; i++) {
+ fprintf(stderr, " BB%u",
+ cfg->map[op_array->try_catch_array[i].try_op]);
+ if (op_array->try_catch_array[i].catch_op) {
+ fprintf(stderr, ", BB%u",
+ cfg->map[op_array->try_catch_array[i].catch_op]);
+ } else {
+ fprintf(stderr, ", -");
+ }
+ if (op_array->try_catch_array[i].finally_op) {
+ fprintf(stderr, ", BB%u",
+ cfg->map[op_array->try_catch_array[i].finally_op]);
+ } else {
+ fprintf(stderr, ", -");
+ }
+ if (op_array->try_catch_array[i].finally_end) {
+ fprintf(stderr, ", BB%u\n",
+ cfg->map[op_array->try_catch_array[i].finally_end]);
+ } else {
+ fprintf(stderr, ", -\n");
+ }
+ }
+ }
+ } else {
+ const zend_op *opline = op_array->opcodes;
+ const zend_op *end = opline + op_array->last;
+
+ while (opline < end) {
+ zend_dump_op_line(op_array, NULL, opline, dump_flags, data);
+ opline++;
+ }
+ if (op_array->last_live_range && (dump_flags & ZEND_DUMP_LIVE_RANGES)) {
+ fprintf(stderr, "LIVE RANGES:\n");
+ for (i = 0; i < op_array->last_live_range; i++) {
+ fprintf(stderr,
+ " %u: %04u - %04u ",
+ EX_VAR_TO_NUM(op_array->live_range[i].var & ~ZEND_LIVE_MASK),
+ op_array->live_range[i].start,
+ op_array->live_range[i].end);
+ switch (op_array->live_range[i].var & ZEND_LIVE_MASK) {
+ case ZEND_LIVE_TMPVAR:
+ fprintf(stderr, "(tmp/var)\n");
+ break;
+ case ZEND_LIVE_LOOP:
+ fprintf(stderr, "(loop)\n");
+ break;
+ case ZEND_LIVE_SILENCE:
+ fprintf(stderr, "(silence)\n");
+ break;
+ case ZEND_LIVE_ROPE:
+ fprintf(stderr, "(rope)\n");
+ break;
+ case ZEND_LIVE_NEW:
+ fprintf(stderr, "(new)\n");
+ break;
+ }
+ }
+ }
+ if (op_array->last_try_catch) {
+ fprintf(stderr, "EXCEPTION TABLE:\n");
+ for (i = 0; i < op_array->last_try_catch; i++) {
+ fprintf(stderr,
+ " %04u",
+ op_array->try_catch_array[i].try_op);
+
+ if (op_array->try_catch_array[i].catch_op) {
+ fprintf(stderr,
+ ", %04u",
+ op_array->try_catch_array[i].catch_op);
+ } else {
+ fprintf(stderr, ", -");
+ }
+ if (op_array->try_catch_array[i].finally_op) {
+ fprintf(stderr,
+ ", %04u",
+ op_array->try_catch_array[i].finally_op);
+ } else {
+ fprintf(stderr, ", -");
+ }
+ if (op_array->try_catch_array[i].finally_end) {
+ fprintf(stderr,
+ ", %04u",
+ op_array->try_catch_array[i].finally_end);
+ } else {
+ fprintf(stderr, ", -\n");
+ }
+ }
+ }
+ }
+}
+
+void zend_dump_dominators(const zend_op_array *op_array, const zend_cfg *cfg)
+{
+ int j;
+
+ fprintf(stderr, "\nDOMINATORS-TREE for \"");
+ zend_dump_op_array_name(op_array);
+ fprintf(stderr, "\"\n");
+ for (j = 0; j < cfg->blocks_count; j++) {
+ zend_basic_block *b = cfg->blocks + j;
+ if (b->flags & ZEND_BB_REACHABLE) {
+ zend_dump_block_info(cfg, j, 0);
+ }
+ }
+}
+
+void zend_dump_variables(const zend_op_array *op_array)
+{
+ int j;
+
+ fprintf(stderr, "\nCV Variables for \"");
+ zend_dump_op_array_name(op_array);
+ fprintf(stderr, "\"\n");
+ for (j = 0; j < op_array->last_var; j++) {
+ fprintf(stderr, " ");
+ zend_dump_var(op_array, IS_CV, j);
+ fprintf(stderr, "\n");
+ }
+}
+
+void zend_dump_ssa_variables(const zend_op_array *op_array, const zend_ssa *ssa, uint32_t dump_flags)
+{
+ int j;
+
+ if (ssa->vars) {
+ fprintf(stderr, "\nSSA Variable for \"");
+ zend_dump_op_array_name(op_array);
+ fprintf(stderr, "\"\n");
+
+ for (j = 0; j < ssa->vars_count; j++) {
+ fprintf(stderr, " ");
+ zend_dump_ssa_var(op_array, ssa, j, IS_CV, ssa->vars[j].var, dump_flags);
+ if (ssa->vars[j].scc >= 0) {
+ if (ssa->vars[j].scc_entry) {
+ fprintf(stderr, " *");
+ } else {
+ fprintf(stderr, " ");
+ }
+ fprintf(stderr, "SCC=%d", ssa->vars[j].scc);
+ }
+ fprintf(stderr, "\n");
+ }
+ }
+}
+
+static void zend_dump_var_set(const zend_op_array *op_array, const char *name, zend_bitset set)
+{
+ int first = 1;
+ uint32_t i;
+
+ fprintf(stderr, " ; %s = {", name);
+ for (i = 0; i < op_array->last_var + op_array->T; i++) {
+ if (zend_bitset_in(set, i)) {
+ if (first) {
+ first = 0;
+ } else {
+ fprintf(stderr, ", ");
+ }
+ zend_dump_var(op_array, IS_CV, i);
+ }
+ }
+ fprintf(stderr, "}\n");
+}
+
+void zend_dump_dfg(const zend_op_array *op_array, const zend_cfg *cfg, const zend_dfg *dfg)
+{
+ int j;
+ fprintf(stderr, "\nVariable Liveness for \"");
+ zend_dump_op_array_name(op_array);
+ fprintf(stderr, "\"\n");
+
+ for (j = 0; j < cfg->blocks_count; j++) {
+ fprintf(stderr, " BB%d:\n", j);
+ zend_dump_var_set(op_array, "def", DFG_BITSET(dfg->def, dfg->size, j));
+ zend_dump_var_set(op_array, "use", DFG_BITSET(dfg->use, dfg->size, j));
+ zend_dump_var_set(op_array, "in ", DFG_BITSET(dfg->in, dfg->size, j));
+ zend_dump_var_set(op_array, "out", DFG_BITSET(dfg->out, dfg->size, j));
+ }
+}
+
+void zend_dump_phi_placement(const zend_op_array *op_array, const zend_ssa *ssa)
+{
+ int j;
+ zend_ssa_block *ssa_blocks = ssa->blocks;
+ int blocks_count = ssa->cfg.blocks_count;
+
+ fprintf(stderr, "\nSSA Phi() Placement for \"");
+ zend_dump_op_array_name(op_array);
+ fprintf(stderr, "\"\n");
+ for (j = 0; j < blocks_count; j++) {
+ if (ssa_blocks && ssa_blocks[j].phis) {
+ zend_ssa_phi *p = ssa_blocks[j].phis;
+ int first = 1;
+
+ fprintf(stderr, " BB%d:\n", j);
+ if (p->pi >= 0) {
+ fprintf(stderr, " ; pi={");
+ } else {
+ fprintf(stderr, " ; phi={");
+ }
+ do {
+ if (first) {
+ first = 0;
+ } else {
+ fprintf(stderr, ", ");
+ }
+ zend_dump_var(op_array, IS_CV, p->var);
+ p = p->next;
+ } while (p);
+ fprintf(stderr, "}\n");
+ }
+ }
+}
diff --git a/Zend/Optimizer/zend_dump.h b/Zend/Optimizer/zend_dump.h
new file mode 100644
index 0000000000..b0e0d7966b
--- /dev/null
+++ b/Zend/Optimizer/zend_dump.h
@@ -0,0 +1,48 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, Bytecode Visualisation |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef ZEND_DUMP_H
+#define ZEND_DUMP_H
+
+#include "zend_ssa.h"
+#include "zend_dfg.h"
+
+#define ZEND_DUMP_HIDE_UNREACHABLE (1<<0)
+#define ZEND_DUMP_RC_INFERENCE (1<<1)
+#define ZEND_DUMP_CFG (1<<2)
+#define ZEND_DUMP_SSA (1<<3)
+#define ZEND_DUMP_LIVE_RANGES (1<<4)
+
+BEGIN_EXTERN_C()
+
+ZEND_API void zend_dump_op_array(const zend_op_array *op_array, uint32_t dump_flags, const char *msg, const void *data);
+ZEND_API void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *b, const zend_op *opline, uint32_t dump_flags, const zend_ssa *ssa, const zend_ssa_op *ssa_op);
+void zend_dump_dominators(const zend_op_array *op_array, const zend_cfg *cfg);
+void zend_dump_dfg(const zend_op_array *op_array, const zend_cfg *cfg, const zend_dfg *dfg);
+void zend_dump_phi_placement(const zend_op_array *op_array, const zend_ssa *ssa);
+void zend_dump_variables(const zend_op_array *op_array);
+void zend_dump_ssa_variables(const zend_op_array *op_array, const zend_ssa *ssa, uint32_t dump_flags);
+ZEND_API void zend_dump_ssa_var(const zend_op_array *op_array, const zend_ssa *ssa, int ssa_var_num, zend_uchar var_type, int var_num, uint32_t dump_flags);
+ZEND_API void zend_dump_var(const zend_op_array *op_array, zend_uchar var_type, int var_num);
+void zend_dump_op_array_name(const zend_op_array *op_array);
+void zend_dump_const(const zval *zv);
+void zend_dump_ht(HashTable *ht);
+
+END_EXTERN_C()
+
+#endif /* ZEND_DUMP_H */
diff --git a/Zend/Optimizer/zend_func_info.c b/Zend/Optimizer/zend_func_info.c
new file mode 100644
index 0000000000..073a054c0f
--- /dev/null
+++ b/Zend/Optimizer/zend_func_info.c
@@ -0,0 +1,973 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, Func Info |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Dmitry Stogov <dmitry@php.net> |
+ | Xinchen Hui <laruence@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#include "php.h"
+#include "zend_compile.h"
+#include "zend_extensions.h"
+#include "zend_ssa.h"
+#include "zend_optimizer_internal.h"
+#include "zend_inference.h"
+#include "zend_call_graph.h"
+#include "zend_func_info.h"
+#include "zend_inference.h"
+#ifdef _WIN32
+#include "win32/ioutil.h"
+#endif
+
+typedef uint32_t (*info_func_t)(const zend_call_info *call_info, const zend_ssa *ssa);
+
+typedef struct _func_info_t {
+ const char *name;
+ int name_len;
+ uint32_t info;
+ info_func_t info_func;
+} func_info_t;
+
+#define F0(name, info) \
+ {name, sizeof(name)-1, (info), NULL}
+#define F1(name, info) \
+ {name, sizeof(name)-1, (MAY_BE_RC1 | (info)), NULL}
+#define FN(name, info) \
+ {name, sizeof(name)-1, (MAY_BE_RC1 | MAY_BE_RCN | (info)), NULL}
+#define FR(name, info) \
+ {name, sizeof(name)-1, (MAY_BE_REF | (info)), NULL}
+#define FX(name, info) \
+ {name, sizeof(name)-1, (MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | (info)), NULL}
+#define FC(name, callback) \
+ {name, sizeof(name)-1, 0, callback}
+
+static uint32_t zend_range_info(const zend_call_info *call_info, const zend_ssa *ssa)
+{
+ if (!call_info->send_unpack
+ && (call_info->num_args == 2 || call_info->num_args == 3)
+ && ssa
+ && !(ssa->cfg.flags & ZEND_SSA_TSSA)) {
+ zend_op_array *op_array = call_info->caller_op_array;
+ uint32_t t1 = _ssa_op1_info(op_array, ssa, call_info->arg_info[0].opline,
+ &ssa->ops[call_info->arg_info[0].opline - op_array->opcodes]);
+ uint32_t t2 = _ssa_op1_info(op_array, ssa, call_info->arg_info[1].opline,
+ &ssa->ops[call_info->arg_info[1].opline - op_array->opcodes]);
+ uint32_t t3 = 0;
+ uint32_t tmp = MAY_BE_RC1 | MAY_BE_ARRAY | MAY_BE_ARRAY_PACKED;
+
+ if (call_info->num_args == 3) {
+ t3 = _ssa_op1_info(op_array, ssa, call_info->arg_info[2].opline,
+ &ssa->ops[call_info->arg_info[2].opline - op_array->opcodes]);
+ }
+ if ((t1 & MAY_BE_STRING) && (t2 & MAY_BE_STRING)) {
+ tmp |= MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING;
+ }
+ if ((t1 & (MAY_BE_DOUBLE|MAY_BE_STRING))
+ || (t2 & (MAY_BE_DOUBLE|MAY_BE_STRING))
+ || (t3 & (MAY_BE_DOUBLE|MAY_BE_STRING))) {
+ tmp |= MAY_BE_ARRAY_OF_DOUBLE;
+ }
+ if ((t1 & (MAY_BE_ANY-(MAY_BE_STRING|MAY_BE_DOUBLE))) && (t2 & (MAY_BE_ANY-(MAY_BE_STRING|MAY_BE_DOUBLE)))) {
+ if ((t3 & MAY_BE_ANY) != MAY_BE_DOUBLE) {
+ tmp |= MAY_BE_ARRAY_OF_LONG;
+ }
+ }
+ return tmp;
+ } else {
+ /* May throw */
+ return MAY_BE_RC1 | MAY_BE_ARRAY | MAY_BE_ARRAY_PACKED | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING;
+ }
+}
+
+#define UNKNOWN_INFO (MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF)
+
+static const func_info_t func_infos[] = {
+ /* zend */
+ F1("zend_version", MAY_BE_STRING),
+ FN("func_get_arg", UNKNOWN_INFO),
+ FN("func_get_args", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY),
+ F1("get_class_vars", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF),
+ F1("get_class_methods", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("get_included_files", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ FN("set_error_handler", MAY_BE_NULL | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_OBJECT | MAY_BE_OBJECT),
+ F0("restore_error_handler", MAY_BE_TRUE),
+ F0("restore_exception_handler", MAY_BE_TRUE),
+ F1("get_declared_traits", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("get_declared_classes", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("get_declared_interfaces", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("get_defined_functions", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ARRAY),
+ F1("get_defined_vars", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF),
+ F1("get_resource_type", MAY_BE_STRING),
+ F1("get_defined_constants", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_RESOURCE | MAY_BE_ARRAY_OF_ARRAY),
+ F1("debug_backtrace", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ARRAY),
+ F1("get_loaded_extensions", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("get_extension_funcs", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+
+ /* ext/standard */
+ FN("constant", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING | MAY_BE_RESOURCE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY),
+ F1("bin2hex", MAY_BE_STRING),
+ F1("hex2bin", MAY_BE_FALSE | MAY_BE_STRING),
+#if HAVE_NANOSLEEP
+ F1("time_nanosleep", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG),
+#endif
+#if HAVE_STRPTIME
+ F1("strptime", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING),
+#endif
+ F1("wordwrap", MAY_BE_STRING),
+ F1("htmlspecialchars", MAY_BE_STRING),
+ F1("htmlentities", MAY_BE_STRING),
+ F1("get_html_translation_table", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING),
+ F1("sha1", MAY_BE_STRING),
+ F1("sha1_file", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("md5", MAY_BE_STRING),
+ F1("md5_file", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("iptcparse", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ARRAY),
+ F1("iptcembed", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING),
+ F1("getimagesize", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("getimagesizefromstring", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("image_type_to_mime_type", MAY_BE_STRING),
+ F1("image_type_to_extension", MAY_BE_FALSE | MAY_BE_STRING),
+ F0("phpinfo", MAY_BE_TRUE),
+ F1("phpversion", MAY_BE_FALSE | MAY_BE_STRING),
+ F0("phpcredits", MAY_BE_TRUE),
+ F1("php_sapi_name", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("php_uname", MAY_BE_STRING),
+ F1("php_ini_scanned_files", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("php_ini_loaded_file", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("strtok", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("strrev", MAY_BE_STRING),
+ F1("hebrev", MAY_BE_STRING),
+ F1("basename", MAY_BE_STRING),
+ F1("dirname", MAY_BE_STRING),
+ F1("pathinfo", MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING),
+ F1("stripslashes", MAY_BE_STRING),
+ F1("stripcslashes", MAY_BE_STRING),
+ F1("strstr", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("stristr", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("strrchr", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("str_shuffle", MAY_BE_STRING),
+ F1("str_word_count", MAY_BE_LONG | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("str_split", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("strpbrk", MAY_BE_FALSE | MAY_BE_STRING),
+ FN("substr_replace", MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_STRING),
+ F1("quotemeta", MAY_BE_STRING),
+ F1("ucwords", MAY_BE_STRING),
+ F1("addcslashes", MAY_BE_STRING),
+ FN("str_replace", MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY | MAY_BE_ARRAY_OF_OBJECT),
+ FN("str_ireplace", MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY | MAY_BE_ARRAY_OF_OBJECT),
+ F1("str_repeat", MAY_BE_STRING),
+ F1("count_chars", MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_LONG),
+ F1("chunk_split", MAY_BE_STRING),
+ F1("strip_tags", MAY_BE_STRING),
+ F1("explode", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("localeconv", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY),
+#if HAVE_NL_LANGINFO
+ F1("nl_langinfo", MAY_BE_FALSE | MAY_BE_STRING),
+#endif
+ F1("soundex", MAY_BE_STRING),
+ F1("chr", MAY_BE_STRING),
+ F1("str_getcsv", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_STRING),
+ F1("strchr", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("sprintf", MAY_BE_STRING),
+ F1("vsprintf", MAY_BE_STRING),
+ F1("sscanf", MAY_BE_NULL | MAY_BE_LONG | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY),
+ F1("fscanf", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY),
+ F1("parse_url", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_LONG),
+ F1("urlencode", MAY_BE_STRING),
+ F1("urldecode", MAY_BE_STRING),
+ F1("rawurlencode", MAY_BE_STRING),
+ F1("rawurldecode", MAY_BE_STRING),
+ F1("http_build_query", MAY_BE_STRING),
+#if defined(HAVE_SYMLINK) || defined(PHP_WIN32)
+ F1("readlink", MAY_BE_FALSE | MAY_BE_STRING),
+#endif
+ F1("exec", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("system", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("escapeshellcmd", MAY_BE_STRING),
+ F1("escapeshellarg", MAY_BE_STRING),
+ F0("passthru", MAY_BE_NULL | MAY_BE_FALSE),
+ F1("shell_exec", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING),
+#ifdef PHP_CAN_SUPPORT_PROC_OPEN
+ F1("proc_open", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ F1("proc_get_status", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING),
+#endif
+ F1("random_bytes", MAY_BE_STRING),
+#if HAVE_GETSERVBYPORT
+ F1("getservbyport", MAY_BE_FALSE | MAY_BE_STRING),
+#endif
+#if HAVE_GETPROTOBYNUMBER
+ F1("getprotobynumber", MAY_BE_FALSE | MAY_BE_STRING),
+#endif
+ F1("base64_decode", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("base64_encode", MAY_BE_STRING),
+ F1("password_hash", MAY_BE_STRING),
+ F1("password_get_info", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY),
+ F1("convert_uuencode", MAY_BE_STRING),
+ F1("convert_uudecode", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("pow", MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_OBJECT),
+ F1("decbin", MAY_BE_STRING),
+ F1("decoct", MAY_BE_STRING),
+ F1("dechex", MAY_BE_STRING),
+ F1("base_convert", MAY_BE_STRING),
+ F1("number_format", MAY_BE_STRING),
+#ifdef HAVE_INET_NTOP
+ F1("inet_ntop", MAY_BE_FALSE | MAY_BE_STRING),
+#endif
+#ifdef HAVE_INET_PTON
+ F1("inet_pton", MAY_BE_FALSE | MAY_BE_STRING),
+#endif
+ F1("long2ip", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("getenv", MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING),
+#ifdef HAVE_PUTENV
+#endif
+ F1("getopt", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY),
+#ifdef HAVE_GETLOADAVG
+ F1("sys_getloadavg", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_DOUBLE),
+#endif
+#ifdef HAVE_GETTIMEOFDAY
+ F1("microtime", MAY_BE_DOUBLE | MAY_BE_STRING),
+ F1("gettimeofday", MAY_BE_DOUBLE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG),
+#endif
+#ifdef HAVE_GETRUSAGE
+ F1("getrusage", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG),
+#endif
+#ifdef HAVE_GETTIMEOFDAY
+ F1("uniqid", MAY_BE_STRING),
+#endif
+ F1("quoted_printable_decode", MAY_BE_STRING),
+ F1("quoted_printable_encode", MAY_BE_STRING),
+ F1("get_current_user", MAY_BE_STRING),
+ F1("get_cfg_var", MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY),
+ F1("error_get_last", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING),
+ FN("call_user_func", UNKNOWN_INFO),
+ FN("call_user_func_array", UNKNOWN_INFO),
+ FN("call_user_method", UNKNOWN_INFO),
+ FN("call_user_method_array", UNKNOWN_INFO),
+ FN("forward_static_call", UNKNOWN_INFO),
+ FN("forward_static_call_array", UNKNOWN_INFO),
+ F1("serialize", MAY_BE_STRING),
+ FN("unserialize", UNKNOWN_INFO),
+ F1("var_export", MAY_BE_NULL | MAY_BE_STRING),
+ F1("print_r", MAY_BE_TRUE | MAY_BE_STRING),
+ F0("register_shutdown_function", MAY_BE_NULL | MAY_BE_FALSE),
+ F1("highlight_file", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING),
+ F1("show_source", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING),
+ F1("highlight_string", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING),
+ F1("php_strip_whitespace", MAY_BE_STRING),
+ F1("ini_get_all", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY),
+ F1("ini_alter", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("get_include_path", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("set_include_path", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("headers_list", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("parse_ini_file", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY),
+ F1("parse_ini_string", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY),
+#if ZEND_DEBUG
+ F1("config_get_hash", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY),
+#endif
+ F1("gethostbyaddr", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("gethostbyname", MAY_BE_STRING),
+ F1("gethostbynamel", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+#ifdef HAVE_GETHOSTNAME
+ F1("gethostname", MAY_BE_FALSE | MAY_BE_STRING),
+#endif
+#if defined(PHP_WIN32) || HAVE_DNS_SEARCH_FUNC
+# if defined(PHP_WIN32) || HAVE_FULL_DNS_FUNCS
+ F1("dns_get_record", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ARRAY),
+# endif
+#endif
+ F1("popen", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ F1("fgetc", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("fgets", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("fread", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("fopen", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ F1("fstat", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_LONG),
+ F1("tempnam", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("tmpfile", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ F1("file", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("file_get_contents", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("stream_context_create", MAY_BE_RESOURCE),
+ F1("stream_context_get_params", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY),
+ FN("stream_context_get_options", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY),
+ FN("stream_context_get_default", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ FN("stream_context_set_default", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ FN("stream_filter_prepend", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ FN("stream_filter_append", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ F1("stream_socket_client", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ F1("stream_socket_server", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ F1("stream_socket_accept", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ F1("stream_socket_get_name", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("stream_socket_recvfrom", MAY_BE_FALSE | MAY_BE_STRING),
+#if HAVE_SOCKETPAIR
+ F1("stream_socket_pair", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_RESOURCE),
+#endif
+ F1("stream_get_contents", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("fgetcsv", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_STRING),
+ F1("get_meta_tags", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING),
+ F1("stream_get_meta_data", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY),
+ F1("stream_get_line", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("stream_get_wrappers", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("stream_get_transports", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("stream_resolve_include_path", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("get_headers", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY),
+ F1("socket_get_status", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY),
+ F1("realpath", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("fsockopen", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ FN("pfsockopen", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ F1("pack", MAY_BE_STRING),
+ F1("unpack", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY),
+ F1("get_browser", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_OBJECT | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY),
+ F1("crypt", MAY_BE_STRING),
+ FN("opendir", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ F1("getcwd", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("readdir", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("dir", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("scandir", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+#ifdef HAVE_GLOB
+ F1("glob", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+#endif
+ F1("filetype", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("stat", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("lstat", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("realpath_cache_get", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ARRAY),
+#ifdef HAVE_SYSLOG_H
+ F0("syslog", MAY_BE_TRUE),
+ F0("closelog", MAY_BE_TRUE),
+#endif
+ F1("metaphone", MAY_BE_STRING),
+ F1("ob_get_flush", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("ob_get_clean", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("ob_get_status", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY),
+ F1("ob_list_handlers", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F0("array_walk", MAY_BE_TRUE),
+ F0("array_walk_recursive", MAY_BE_TRUE),
+ F0("arsort", MAY_BE_TRUE),
+ F0("asort", MAY_BE_TRUE),
+ F0("krsort", MAY_BE_TRUE),
+ F0("ksort", MAY_BE_TRUE),
+ F0("shuffle", MAY_BE_TRUE),
+ F0("sort", MAY_BE_TRUE),
+ F0("usort", MAY_BE_TRUE),
+ F0("uasort", MAY_BE_TRUE),
+ F0("uksort", MAY_BE_TRUE),
+ FN("end", UNKNOWN_INFO),
+ FN("prev", UNKNOWN_INFO),
+ FN("next", UNKNOWN_INFO),
+ FN("reset", UNKNOWN_INFO),
+ FN("current", UNKNOWN_INFO),
+ FN("min", UNKNOWN_INFO),
+ FN("max", UNKNOWN_INFO),
+ F1("compact", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_fill", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY),
+ F1("array_fill_keys", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ FC("range", zend_range_info),
+ FN("array_pop", UNKNOWN_INFO),
+ FN("array_shift", UNKNOWN_INFO),
+ F1("array_splice", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_slice", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_replace", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_replace_recursive", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ FN("array_keys", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING),
+ FN("array_values", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_count_values", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_LONG),
+ F1("array_column", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_reverse", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_reduce", UNKNOWN_INFO),
+ F1("array_flip", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("array_change_key_case", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ FN("array_rand", MAY_BE_LONG | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("array_intersect", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_intersect_key", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_intersect_ukey", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_uintersect", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_intersect_assoc", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_uintersect_assoc", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_intersect_uassoc", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_uintersect_uassoc", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_diff_key", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_diff_ukey", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_udiff", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_diff_assoc", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_udiff_assoc", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_diff_uassoc", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_udiff_uassoc", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_filter", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_chunk", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("array_combine", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("pos", UNKNOWN_INFO),
+ F1("assert_options", MAY_BE_NULL | MAY_BE_LONG | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_OBJECT | MAY_BE_OBJECT),
+ F1("str_rot13", MAY_BE_STRING),
+ F1("stream_get_filters", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("stream_bucket_make_writeable", MAY_BE_NULL | MAY_BE_OBJECT),
+ F1("stream_bucket_new", MAY_BE_OBJECT),
+ F1("sys_get_temp_dir", MAY_BE_STRING),
+
+ /* ext/date */
+ F1("date", MAY_BE_STRING),
+ F1("gmdate", MAY_BE_STRING),
+ F1("strftime", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("gmstrftime", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("localtime", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_LONG),
+ F1("getdate", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("date_create", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("date_create_immutable", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("date_create_from_format", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("date_create_immutable_from_format", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("date_parse", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY),
+ F1("date_parse_from_format", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY),
+ F1("date_get_last_errors", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_ARRAY),
+ F1("date_format", MAY_BE_STRING),
+ F1("date_timezone_get", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("date_diff", MAY_BE_OBJECT),
+ F1("timezone_open", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("timezone_name_get", MAY_BE_STRING),
+ F1("timezone_name_from_abbr", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("timezone_transitions_get", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY),
+ F1("timezone_location_get", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING),
+ F1("timezone_identifiers_list", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("timezone_abbreviations_list", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ARRAY),
+ F1("timezone_version_get", MAY_BE_STRING),
+ F1("date_interval_create_from_date_string", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("date_interval_format", MAY_BE_STRING),
+ F1("date_default_timezone_get", MAY_BE_STRING),
+ F1("date_sunrise", MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING),
+ F1("date_sunset", MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_DOUBLE | MAY_BE_STRING),
+ F1("date_sun_info", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE | MAY_BE_ARRAY_OF_LONG),
+
+ /* ext/preg */
+ FN("preg_replace", MAY_BE_NULL | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_STRING),
+ FN("preg_replace_callback", MAY_BE_NULL | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_STRING),
+ F1("preg_filter", MAY_BE_NULL | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_STRING),
+ F1("preg_split", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY),
+ F1("preg_grep", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+
+ /* ext/mysqli */
+ F1("mysqli_connect", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_OBJECT),
+ F0("mysqli_close", MAY_BE_TRUE),
+ F1("mysqli_connect_error", MAY_BE_NULL | MAY_BE_STRING),
+ F1("mysqli_get_client_stats", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING),
+ F1("mysqli_error_list", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ARRAY),
+ F1("mysqli_get_links_stats", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG),
+ F1("mysqli_query", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_OBJECT),
+ F1("mysqli_get_charset", MAY_BE_NULL | MAY_BE_OBJECT),
+ F1("mysqli_fetch_array", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY),
+ F1("mysqli_fetch_assoc", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY),
+ F1("mysqli_fetch_all", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY),
+ F1("mysqli_fetch_object", MAY_BE_NULL | MAY_BE_OBJECT),
+ F1("mysqli_affected_rows", MAY_BE_LONG | MAY_BE_STRING),
+ F1("mysqli_character_set_name", MAY_BE_STRING),
+ F0("mysqli_debug", MAY_BE_TRUE),
+ F1("mysqli_error", MAY_BE_STRING),
+ F1("mysqli_reap_async_query", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_OBJECT),
+ F1("mysqli_stmt_get_result", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("mysqli_get_warnings", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("mysqli_stmt_error_list", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ARRAY),
+ F1("mysqli_stmt_get_warnings", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("mysqli_fetch_field", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("mysqli_fetch_fields", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_OBJECT),
+ F1("mysqli_fetch_field_direct", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("mysqli_fetch_lengths", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_LONG),
+ F1("mysqli_fetch_row", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY),
+ F1("mysqli_get_client_info", MAY_BE_NULL | MAY_BE_STRING),
+ F1("mysqli_get_host_info", MAY_BE_STRING),
+ F1("mysqli_get_server_info", MAY_BE_STRING),
+ F1("mysqli_info", MAY_BE_NULL | MAY_BE_STRING),
+ F1("mysqli_init", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("mysqli_insert_id", MAY_BE_LONG | MAY_BE_STRING),
+ F1("mysqli_num_rows", MAY_BE_LONG | MAY_BE_STRING),
+ F1("mysqli_prepare", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("mysqli_real_escape_string", MAY_BE_STRING),
+ F1("mysqli_stmt_affected_rows", MAY_BE_LONG | MAY_BE_STRING),
+ F1("mysqli_stmt_insert_id", MAY_BE_LONG | MAY_BE_STRING),
+ F1("mysqli_stmt_num_rows", MAY_BE_LONG | MAY_BE_STRING),
+ F1("mysqli_sqlstate", MAY_BE_STRING),
+ F0("mysqli_ssl_set", MAY_BE_TRUE),
+ F1("mysqli_stat", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("mysqli_stmt_error", MAY_BE_STRING),
+ F1("mysqli_stmt_init", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("mysqli_stmt_result_metadata", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("mysqli_stmt_sqlstate", MAY_BE_STRING),
+ F1("mysqli_store_result", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("mysqli_use_result", MAY_BE_FALSE | MAY_BE_OBJECT),
+
+ /* ext/curl */
+ F1("curl_init", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("curl_copy_handle", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("curl_version", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY),
+ F1("curl_error", MAY_BE_STRING),
+ F1("curl_strerror", MAY_BE_NULL | MAY_BE_STRING),
+ F1("curl_multi_strerror", MAY_BE_NULL | MAY_BE_STRING),
+ F1("curl_escape", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("curl_unescape", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("curl_multi_init", MAY_BE_OBJECT),
+ F1("curl_multi_info_read", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_OBJECT),
+ F1("curl_share_init", MAY_BE_OBJECT),
+ F1("curl_file_create", MAY_BE_OBJECT),
+
+ /* ext/mbstring */
+ F1("mb_convert_case", MAY_BE_STRING),
+ F1("mb_strtoupper", MAY_BE_STRING),
+ F1("mb_strtolower", MAY_BE_STRING),
+ F1("mb_language", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING),
+ F1("mb_internal_encoding", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING),
+ F1("mb_http_input", MAY_BE_FALSE | MAY_BE_STRING| MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("mb_http_output", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING),
+ F1("mb_detect_order", MAY_BE_TRUE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("mb_substitute_character", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_LONG | MAY_BE_STRING),
+ F1("mb_output_handler", MAY_BE_STRING),
+ F1("mb_preferred_mime_name", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("mb_strstr", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("mb_strrchr", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("mb_stristr", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("mb_strrichr", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("mb_substr", MAY_BE_STRING),
+ F1("mb_strcut", MAY_BE_STRING),
+ F1("mb_strimwidth", MAY_BE_STRING),
+ F1("mb_convert_encoding", MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY),
+ F1("mb_detect_encoding", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("mb_list_encodings", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("mb_encoding_aliases", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("mb_convert_kana", MAY_BE_STRING),
+ F1("mb_encode_mimeheader", MAY_BE_STRING),
+ F1("mb_decode_mimeheader", MAY_BE_STRING),
+ F1("mb_convert_variables", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("mb_encode_numericentity", MAY_BE_STRING),
+ F1("mb_decode_numericentity", MAY_BE_STRING),
+ F1("mb_get_info", MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY),
+
+ F1("mb_regex_encoding", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING),
+ F1("mb_regex_set_options", MAY_BE_STRING),
+ F1("mb_ereg_replace", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING),
+ F1("mb_eregi_replace", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING),
+ F1("mb_ereg_replace_callback", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING),
+ F1("mb_split", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("mb_ereg_search_pos", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_LONG),
+ F1("mb_ereg_search_regs", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_STRING),
+ F1("mb_ereg_search_getregs", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_STRING),
+
+ /* ext/iconv */
+ F1("iconv", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("iconv_get_encoding", MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING),
+ F1("iconv_substr", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("iconv_mime_encode", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("iconv_mime_decode", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("iconv_mime_decode_headers", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_ARRAY),
+
+ /* ext/json */
+ F1("json_encode", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("json_decode", MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY),
+ F1("json_last_error_msg", MAY_BE_STRING),
+
+ /* ext/xml */
+ F1("xml_error_string", MAY_BE_NULL | MAY_BE_STRING),
+ F1("xml_parser_get_option", MAY_BE_LONG | MAY_BE_STRING),
+ F1("utf8_encode", MAY_BE_STRING),
+ F1("utf8_decode", MAY_BE_STRING),
+
+ /* ext/zlib */
+ F1("gzgetc", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("gzgets", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("gzread", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("gzopen", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_RESOURCE),
+ F1("gzfile", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("gzcompress", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("gzuncompress", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("gzdeflate", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("gzinflate", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("gzencode", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("gzdecode", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("zlib_encode", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("zlib_decode", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("zlib_get_coding_type", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("ob_gzhandler", MAY_BE_FALSE | MAY_BE_STRING),
+
+ /* ext/hash */
+ F1("hash", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("hash_file", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("hash_hmac", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("hash_hmac_algos", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("hash_hmac_file", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("hash_hkdf", MAY_BE_STRING),
+ F1("hash_init", MAY_BE_OBJECT),
+ F1("hash_final", MAY_BE_STRING),
+ F1("hash_copy", MAY_BE_OBJECT),
+ F1("hash_algos", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("hash_pbkdf2", MAY_BE_STRING),
+ F1("mhash_keygen_s2k", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("mhash_get_hash_name", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("mhash", MAY_BE_FALSE | MAY_BE_FALSE | MAY_BE_STRING),
+
+ /* ext/sodium */
+ F1("sodium_crypto_shorthash", MAY_BE_STRING),
+ F1("sodium_crypto_secretbox", MAY_BE_STRING),
+ F1("sodium_crypto_secretbox_open", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("sodium_crypto_generichash", MAY_BE_STRING),
+ F1("sodium_crypto_generichash_init", MAY_BE_STRING),
+ F0("sodium_crypto_generichash_update", MAY_BE_TRUE),
+ F1("sodium_crypto_generichash_final", MAY_BE_STRING),
+ F1("sodium_crypto_box_keypair", MAY_BE_STRING),
+ F1("sodium_crypto_box_seed_keypair", MAY_BE_STRING),
+ F1("sodium_crypto_box_secretkey", MAY_BE_STRING),
+ F1("sodium_crypto_box_publickey", MAY_BE_STRING),
+ F1("sodium_crypto_box", MAY_BE_STRING),
+ F1("sodium_crypto_box_open", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("sodium_crypto_box_seal", MAY_BE_STRING),
+ F1("sodium_crypto_box_seal_open", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("sodium_crypto_sign_keypair", MAY_BE_STRING),
+ F1("sodium_crypto_sign_seed_keypair", MAY_BE_STRING),
+ F1("sodium_crypto_sign_secretkey", MAY_BE_STRING),
+ F1("sodium_crypto_sign_publickey", MAY_BE_STRING),
+ F1("sodium_crypto_sign", MAY_BE_STRING),
+ F1("sodium_crypto_sign_open", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("sodium_crypto_sign_detached", MAY_BE_STRING),
+ F1("sodium_crypto_stream", MAY_BE_STRING),
+ F1("sodium_crypto_stream_xor", MAY_BE_STRING),
+ F1("sodium_crypto_pwhash", MAY_BE_STRING),
+ F1("sodium_crypto_pwhash_str", MAY_BE_STRING),
+ F1("sodium_crypto_aead_aes256gcm_encrypt", MAY_BE_STRING),
+ F1("sodium_crypto_aead_aes256gcm_decrypt", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("sodium_bin2hex", MAY_BE_STRING),
+ F1("sodium_hex2bin", MAY_BE_STRING),
+ F1("sodium_crypto_scalarmult", MAY_BE_STRING),
+ F1("sodium_crypto_kx_seed_keypair", MAY_BE_STRING),
+ F1("sodium_crypto_kx_keypair", MAY_BE_STRING),
+ F1("sodium_crypto_kx_secretkey", MAY_BE_STRING),
+ F1("sodium_crypto_kx_publickey", MAY_BE_STRING),
+ F1("sodium_crypto_kx_client_session_keys", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("sodium_crypto_kx_server_session_keys", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("sodium_crypto_auth", MAY_BE_STRING),
+ F1("sodium_crypto_aead_aes256gcm_keygen", MAY_BE_STRING),
+ F1("sodium_crypto_auth_keygen", MAY_BE_STRING),
+ F1("sodium_crypto_generichash_keygen", MAY_BE_STRING),
+ F1("sodium_crypto_kdf_keygen", MAY_BE_STRING),
+ F1("sodium_crypto_secretbox_keygen", MAY_BE_STRING),
+ F1("sodium_crypto_shorthash_keygen", MAY_BE_STRING),
+ F1("sodium_crypto_stream_keygen", MAY_BE_STRING),
+ F1("sodium_crypto_kdf_derive_from_key", MAY_BE_STRING),
+ F1("sodium_pad", MAY_BE_STRING),
+ F1("sodium_unpad", MAY_BE_STRING),
+
+ F1("sodium_crypto_box_keypair_from_secretkey_and_publickey", MAY_BE_STRING),
+ F1("sodium_crypto_box_publickey_from_secretkey", MAY_BE_STRING),
+ F1("sodium_crypto_sign_keypair_from_secretkey_and_publickey", MAY_BE_STRING),
+ F1("sodium_crypto_sign_publickey_from_secretkey", MAY_BE_STRING),
+ F1("sodium_crypto_pwhash_scryptsalsa208sha256", MAY_BE_STRING),
+ F1("sodium_crypto_pwhash_scryptsalsa208sha256_str", MAY_BE_STRING),
+ F1("sodium_crypto_sign_ed25519_sk_to_curve25519", MAY_BE_STRING),
+ F1("sodium_crypto_sign_ed25519_pk_to_curve25519", MAY_BE_STRING),
+ F1("sodium_crypto_aead_chacha20poly1305_encrypt", MAY_BE_STRING),
+ F1("sodium_crypto_aead_chacha20poly1305_decrypt", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("sodium_crypto_aead_chacha20poly1305_ietf_encrypt", MAY_BE_STRING),
+ F1("sodium_crypto_aead_chacha20poly1305_ietf_decrypt", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("sodium_crypto_aead_xchacha20poly1305_ietf_encrypt", MAY_BE_STRING),
+ F1("sodium_crypto_aead_xchacha20poly1305_ietf_decrypt", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("sodium_crypto_aead_chacha20poly1305_keygen", MAY_BE_STRING),
+ F1("sodium_crypto_aead_chacha20poly1305_ietf_keygen", MAY_BE_STRING),
+ F1("sodium_crypto_aead_xchacha20poly1305_ietf_keygen", MAY_BE_STRING),
+
+ /* ext/session */
+ F1("session_get_cookie_params", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY),
+ F1("session_name", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("session_module_name", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("session_save_path", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("session_create_id", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("session_cache_limiter", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("session_encode", MAY_BE_FALSE | MAY_BE_STRING),
+
+ /* ext/pgsql */
+ F1("pg_connect", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ FN("pg_pconnect", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ F1("pg_dbname", MAY_BE_STRING),
+ F1("pg_last_error", MAY_BE_STRING),
+ F1("pg_options", MAY_BE_STRING),
+ F1("pg_port", MAY_BE_STRING),
+ F1("pg_tty", MAY_BE_STRING),
+ F1("pg_host", MAY_BE_STRING),
+ F1("pg_version", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_NULL),
+ F1("pg_parameter_status", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("pg_query", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ F1("pg_query_params", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ F1("pg_prepare", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ F1("pg_execute", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ FN("pg_last_notice", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY ),
+ F1("pg_field_table", MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_STRING),
+ F1("pg_field_name", MAY_BE_STRING),
+ F1("pg_field_type", MAY_BE_STRING),
+ F1("pg_field_type_oid", MAY_BE_LONG | MAY_BE_STRING),
+ F1("pg_fetch_result", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING),
+ F1("pg_fetch_row", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_STRING),
+ F1("pg_fetch_assoc", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_STRING),
+ F1("pg_fetch_array", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_STRING),
+ F1("pg_fetch_object", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("pg_fetch_all", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ARRAY),
+ F1("pg_fetch_all_columns", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_NULL | MAY_BE_ARRAY_OF_STRING),
+ F1("pg_last_oid", MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_STRING),
+ F1("pg_lo_create", MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_STRING),
+ F1("pg_lo_open", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ F1("pg_lo_read", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("pg_lo_import", MAY_BE_FALSE | MAY_BE_LONG | MAY_BE_STRING),
+ F1("pg_copy_to", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+ F1("pg_escape_string", MAY_BE_STRING),
+ F1("pg_escape_bytea", MAY_BE_STRING),
+ F1("pg_unescape_bytea", MAY_BE_STRING),
+ F1("pg_escape_literal", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("pg_escape_identifier", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("pg_result_error", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("pg_result_error_field", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING),
+ F1("pg_get_result", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ F1("pg_result_status", MAY_BE_LONG | MAY_BE_STRING),
+ F1("pg_get_notify", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY),
+ F1("pg_socket", MAY_BE_FALSE | MAY_BE_RESOURCE),
+ F1("pg_meta_data", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ARRAY),
+ F1("pg_convert", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY),
+ F1("pg_insert", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_RESOURCE | MAY_BE_STRING),
+ F1("pg_update", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING),
+ F1("pg_delete", MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_STRING),
+ F1("pg_select", MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ARRAY),
+
+ /* ext/bcmath */
+ F1("bcadd", MAY_BE_STRING),
+ F1("bcsub", MAY_BE_STRING),
+ F1("bcmul", MAY_BE_STRING),
+ F1("bcdiv", MAY_BE_STRING),
+ F1("bcmod", MAY_BE_STRING),
+ F1("bcpowmod", MAY_BE_STRING),
+ F1("bcpow", MAY_BE_STRING),
+ F1("bcsqrt", MAY_BE_STRING),
+
+ /* ext/exif */
+ F1("exif_tagname", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("exif_read_data", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_ANY),
+ F1("exif_thumbnail", MAY_BE_FALSE | MAY_BE_STRING),
+
+ /* ext/filter */
+ FN("filter_input", UNKNOWN_INFO),
+ FN("filter_var", UNKNOWN_INFO),
+ F1("filter_input_array", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY),
+ F1("filter_var_array", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY),
+ F1("filter_list", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING),
+
+ /* ext/gettext */
+ F1("textdomain", MAY_BE_STRING),
+ F1("gettext", MAY_BE_STRING),
+ F1("_", MAY_BE_STRING),
+ F1("dgettext", MAY_BE_STRING),
+ F1("dcgettext", MAY_BE_STRING),
+ F1("bindtextdomain", MAY_BE_FALSE | MAY_BE_STRING),
+#if HAVE_NGETTEXT
+ F1("ngettext", MAY_BE_STRING),
+#endif
+#if HAVE_DNGETTEXT
+ F1("dcngettext", MAY_BE_STRING),
+#endif
+#if HAVE_BIND_TEXTDOMAIN_CODESET
+ F1("bind_textdomain_codeset", MAY_BE_FALSE | MAY_BE_STRING),
+#endif
+
+ /* ext/fileinfo */
+ F1("finfo_open", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("finfo_file", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("finfo_buffer", MAY_BE_FALSE | MAY_BE_STRING),
+ F1("mime_content_type", MAY_BE_FALSE | MAY_BE_STRING),
+
+ /* ext/gd */
+ F1("gd_info", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING | MAY_BE_ARRAY_OF_FALSE | MAY_BE_ARRAY_OF_TRUE),
+ F1("imagecreatetruecolor", MAY_BE_FALSE | MAY_BE_OBJECT),
+#ifdef PHP_WIN32
+ F1("imagegrabwindow", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("imagegrabscreen", MAY_BE_FALSE | MAY_BE_OBJECT),
+#endif
+ F1("imagerotate", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("imagecreate", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("imagecreatefromstring", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("imagecreatefromgif", MAY_BE_FALSE | MAY_BE_OBJECT),
+#ifdef HAVE_GD_JPG
+ F1("imagecreatefromjpeg", MAY_BE_FALSE | MAY_BE_OBJECT),
+#endif
+#ifdef HAVE_GD_PNG
+ F1("imagecreatefrompng", MAY_BE_FALSE | MAY_BE_OBJECT),
+#endif
+#ifdef HAVE_GD_WEBP
+ F1("imagecreatefromwebp", MAY_BE_FALSE | MAY_BE_OBJECT),
+#endif
+ F1("imagecreatefromxbm", MAY_BE_FALSE | MAY_BE_OBJECT),
+#if defined(HAVE_GD_XPM)
+ F1("imagecreatefromxpm", MAY_BE_FALSE | MAY_BE_OBJECT),
+#endif
+ F1("imagecreatefromwbmp", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("imagecreatefromgd", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("imagecreatefromgd2", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("imagecreatefromgd2part", MAY_BE_FALSE | MAY_BE_OBJECT),
+#if defined(HAVE_GD_BMP)
+ F1("imagecreatefrombmp", MAY_BE_FALSE | MAY_BE_OBJECT),
+#endif
+ F0("imagecolorset", MAY_BE_NULL | MAY_BE_FALSE),
+ F1("imagecolorsforindex", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_LONG),
+ F1("imagegetclip", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_LONG),
+ F1("imageftbbox", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_LONG),
+ F1("imagefttext", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_LONG),
+ F1("imagettfbbox", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_LONG),
+ F1("imagettftext", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_LONG),
+ F1("imagecrop", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("imagecropauto", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("imagescale", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("imageaffine", MAY_BE_FALSE | MAY_BE_OBJECT),
+ F1("imageaffinematrixget", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_DOUBLE),
+ F1("imageaffinematrixconcat", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_DOUBLE),
+ F1("imageresolution", MAY_BE_TRUE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_LONG),
+
+ /* ext/spl */
+ F1("class_implements", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING),
+ F1("class_parents", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING),
+ F1("class_uses", MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING),
+ F1("iterator_to_array", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
+ F1("spl_classes", MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_OF_STRING),
+ F1("spl_object_hash", MAY_BE_STRING),
+
+};
+
+static HashTable func_info;
+ZEND_API int zend_func_info_rid = -1;
+
+static uint32_t get_internal_func_info(
+ const zend_call_info *call_info, const zend_ssa *ssa, zend_string *lcname) {
+ if (call_info->callee_func->common.scope) {
+ /* This is a method, not a function. */
+ return 0;
+ }
+
+ zval *zv = zend_hash_find_ex(&func_info, lcname, 1);
+ if (!zv) {
+ return 0;
+ }
+
+ func_info_t *info = Z_PTR_P(zv);
+ if (info->info_func) {
+ return info->info_func(call_info, ssa);
+ } else {
+ return info->info;
+ }
+}
+
+ZEND_API uint32_t zend_get_func_info(
+ const zend_call_info *call_info, const zend_ssa *ssa,
+ zend_class_entry **ce, bool *ce_is_instanceof)
+{
+ uint32_t ret = 0;
+ const zend_function *callee_func = call_info->callee_func;
+ *ce = NULL;
+ *ce_is_instanceof = 0;
+
+ if (callee_func->type == ZEND_INTERNAL_FUNCTION) {
+ zend_string *lcname = Z_STR_P(CRT_CONSTANT_EX(call_info->caller_op_array, call_info->caller_init_opline, call_info->caller_init_opline->op2));
+
+ uint32_t internal_ret = get_internal_func_info(call_info, ssa, lcname);
+#if !ZEND_DEBUG
+ if (internal_ret) {
+ return internal_ret;
+ }
+#endif
+
+ if (callee_func->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+ ret = zend_fetch_arg_info_type(NULL, callee_func->common.arg_info - 1, ce);
+ *ce_is_instanceof = 1;
+ } else {
+#if 0
+ fprintf(stderr, "Unknown internal function '%s'\n", func->common.function_name);
+#endif
+ ret = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF
+ | MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ if (callee_func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) {
+ ret |= MAY_BE_REF;
+ }
+
+#if ZEND_DEBUG
+ if (internal_ret) {
+ /* Check whether the func_info information is a subset of the information we can
+ * compute from the specified return type, otherwise it contains redundant types. */
+ if (internal_ret & ~ret) {
+ fprintf(stderr, "Inaccurate func info for %s()\n", ZSTR_VAL(lcname));
+ }
+ /* Check whether the func info is completely redundant with arginfo.
+ * Ignore UNKNOWN_INFO for now. */
+ if (internal_ret == ret && (internal_ret & MAY_BE_ANY) != MAY_BE_ANY) {
+ fprintf(stderr, "Useless func info for %s()\n", ZSTR_VAL(lcname));
+ }
+ /* If the return type is not mixed, check that the types match exactly if we exclude
+ * RC and array information. */
+ uint32_t ret_any = ret & MAY_BE_ANY, internal_ret_any = internal_ret & MAY_BE_ANY;
+ if (ret_any != MAY_BE_ANY) {
+ uint32_t diff = internal_ret_any ^ ret_any;
+ /* Func info may contain "true" types as well as isolated "null" and "false". */
+ if (diff && !(diff == MAY_BE_FALSE && (ret & MAY_BE_FALSE))
+ && (internal_ret_any & ~(MAY_BE_NULL|MAY_BE_FALSE))) {
+ fprintf(stderr, "Incorrect func info for %s()\n", ZSTR_VAL(lcname));
+ }
+ }
+ return internal_ret;
+ }
+#endif
+ } else {
+ // FIXME: the order of functions matters!!!
+ zend_func_info *info = ZEND_FUNC_INFO((zend_op_array*)callee_func);
+ if (info) {
+ ret = info->return_info.type;
+ *ce = info->return_info.ce;
+ *ce_is_instanceof = info->return_info.is_instanceof;
+ }
+ if (!ret) {
+ ret = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF
+ | MAY_BE_RC1 | MAY_BE_RCN;
+ /* For generators RETURN_REFERENCE refers to the yielded values. */
+ if ((callee_func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
+ && !(callee_func->common.fn_flags & ZEND_ACC_GENERATOR)) {
+ ret |= MAY_BE_REF;
+ }
+ }
+ }
+ return ret;
+}
+
+int zend_func_info_startup(void)
+{
+ size_t i;
+
+ if (zend_func_info_rid == -1) {
+ zend_func_info_rid = zend_get_resource_handle("Zend Optimizer");
+ if (zend_func_info_rid < 0) {
+ return FAILURE;
+ }
+
+ zend_hash_init(&func_info, sizeof(func_infos)/sizeof(func_info_t), NULL, NULL, 1);
+ for (i = 0; i < sizeof(func_infos)/sizeof(func_info_t); i++) {
+ zend_string *key = zend_string_init_interned(func_infos[i].name, func_infos[i].name_len, 1);
+
+ if (zend_hash_add_ptr(&func_info, key, (void**)&func_infos[i]) == NULL) {
+ fprintf(stderr, "ERROR: Duplicate function info for \"%s\"\n", func_infos[i].name);
+ }
+ zend_string_release_ex(key, 1);
+ }
+ }
+
+ return SUCCESS;
+}
+
+int zend_func_info_shutdown(void)
+{
+ if (zend_func_info_rid != -1) {
+ zend_hash_destroy(&func_info);
+ zend_func_info_rid = -1;
+ }
+ return SUCCESS;
+}
diff --git a/Zend/Optimizer/zend_func_info.h b/Zend/Optimizer/zend_func_info.h
new file mode 100644
index 0000000000..53ad99c22e
--- /dev/null
+++ b/Zend/Optimizer/zend_func_info.h
@@ -0,0 +1,68 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, Func Info |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef ZEND_FUNC_INFO_H
+#define ZEND_FUNC_INFO_H
+
+#include "zend_ssa.h"
+
+/* func/cfg flags */
+#define ZEND_FUNC_INDIRECT_VAR_ACCESS (1<<0) /* accesses variables by name */
+#define ZEND_FUNC_HAS_CALLS (1<<1)
+#define ZEND_FUNC_VARARG (1<<2) /* uses func_get_args() */
+#define ZEND_FUNC_NO_LOOPS (1<<3)
+#define ZEND_FUNC_IRREDUCIBLE (1<<4)
+#define ZEND_FUNC_FREE_LOOP_VAR (1<<5)
+#define ZEND_FUNC_RECURSIVE (1<<7)
+#define ZEND_FUNC_RECURSIVE_DIRECTLY (1<<8)
+#define ZEND_FUNC_RECURSIVE_INDIRECTLY (1<<9)
+#define ZEND_FUNC_HAS_EXTENDED_FCALL (1<<10)
+#define ZEND_FUNC_HAS_EXTENDED_STMT (1<<11)
+#define ZEND_SSA_TSSA (1<<12) /* used by tracing JIT */
+
+#define ZEND_FUNC_JIT_ON_FIRST_EXEC (1<<13) /* used by JIT */
+#define ZEND_FUNC_JIT_ON_PROF_REQUEST (1<<14) /* used by JIT */
+#define ZEND_FUNC_JIT_ON_HOT_COUNTERS (1<<15) /* used by JIT */
+#define ZEND_FUNC_JIT_ON_HOT_TRACE (1<<16) /* used by JIT */
+
+
+typedef struct _zend_func_info zend_func_info;
+typedef struct _zend_call_info zend_call_info;
+
+#define ZEND_FUNC_INFO(op_array) \
+ ((zend_func_info*)((op_array)->reserved[zend_func_info_rid]))
+
+#define ZEND_SET_FUNC_INFO(op_array, info) do { \
+ zend_func_info** pinfo = (zend_func_info**)&(op_array)->reserved[zend_func_info_rid]; \
+ *pinfo = info; \
+ } while (0)
+
+BEGIN_EXTERN_C()
+
+extern ZEND_API int zend_func_info_rid;
+
+ZEND_API uint32_t zend_get_func_info(
+ const zend_call_info *call_info, const zend_ssa *ssa,
+ zend_class_entry **ce, bool *ce_is_instanceof);
+
+int zend_func_info_startup(void);
+int zend_func_info_shutdown(void);
+
+END_EXTERN_C()
+
+#endif /* ZEND_FUNC_INFO_H */
diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c
new file mode 100644
index 0000000000..ba51283beb
--- /dev/null
+++ b/Zend/Optimizer/zend_inference.c
@@ -0,0 +1,4680 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, e-SSA based Type & Range Inference |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#include "php.h"
+#include "zend_compile.h"
+#include "zend_generators.h"
+#include "zend_inference.h"
+#include "zend_func_info.h"
+#include "zend_call_graph.h"
+#include "zend_worklist.h"
+
+/* The used range inference algorithm is described in:
+ * V. Campos, R. Rodrigues, I. de Assis Costa and F. Pereira.
+ * "Speed and Precision in Range Analysis", SBLP'12.
+ *
+ * There are a couple degrees of freedom, we use:
+ * * Propagation on SCCs.
+ * * e-SSA for live range splitting.
+ * * Only intra-procedural inference.
+ * * Widening with warmup passes, but without jump sets.
+ */
+
+/* Whether to handle symbolic range constraints */
+#define SYM_RANGE
+
+/* Whether to handle negative range constraints */
+/* Negative range inference is buggy, so disabled for now */
+#undef NEG_RANGE
+
+/* Number of warmup passes to use prior to widening */
+#define RANGE_WARMUP_PASSES 16
+
+/* Logging for range inference in general */
+#if 0
+#define LOG_SSA_RANGE(...) fprintf(stderr, __VA_ARGS__)
+#else
+#define LOG_SSA_RANGE(...)
+#endif
+
+/* Logging for negative range constraints */
+#if 0
+#define LOG_NEG_RANGE(...) fprintf(stderr, __VA_ARGS__)
+#else
+#define LOG_NEG_RANGE(...)
+#endif
+
+/* Pop elements in unspecified order from worklist until it is empty */
+#define WHILE_WORKLIST(worklist, len, i) do { \
+ bool _done = 0; \
+ while (!_done) { \
+ _done = 1; \
+ ZEND_BITSET_FOREACH(worklist, len, i) { \
+ zend_bitset_excl(worklist, i); \
+ _done = 0;
+
+#define WHILE_WORKLIST_END() \
+ } ZEND_BITSET_FOREACH_END(); \
+ } \
+} while (0)
+
+#define CHECK_SCC_VAR(var2) \
+ do { \
+ if (!ssa->vars[var2].no_val) { \
+ if (dfs[var2] < 0) { \
+ zend_ssa_check_scc_var(op_array, ssa, var2, index, dfs, root, stack); \
+ } \
+ if (ssa->vars[var2].scc < 0 && dfs[root[var]] >= dfs[root[var2]]) { \
+ root[var] = root[var2]; \
+ } \
+ } \
+ } while (0)
+
+#define CHECK_SCC_ENTRY(var2) \
+ do { \
+ if (ssa->vars[var2].scc != ssa->vars[var].scc) { \
+ ssa->vars[var2].scc_entry = 1; \
+ } \
+ } while (0)
+
+#define ADD_SCC_VAR(_var) \
+ do { \
+ if (ssa->vars[_var].scc == scc) { \
+ zend_bitset_incl(worklist, _var); \
+ } \
+ } while (0)
+
+#define ADD_SCC_VAR_1(_var) \
+ do { \
+ if (ssa->vars[_var].scc == scc && \
+ !zend_bitset_in(visited, _var)) { \
+ zend_bitset_incl(worklist, _var); \
+ } \
+ } while (0)
+
+#define FOR_EACH_DEFINED_VAR(line, MACRO) \
+ do { \
+ if (ssa->ops[line].op1_def >= 0) { \
+ MACRO(ssa->ops[line].op1_def); \
+ } \
+ if (ssa->ops[line].op2_def >= 0) { \
+ MACRO(ssa->ops[line].op2_def); \
+ } \
+ if (ssa->ops[line].result_def >= 0) { \
+ MACRO(ssa->ops[line].result_def); \
+ } \
+ if (op_array->opcodes[line].opcode == ZEND_OP_DATA) { \
+ if (ssa->ops[line-1].op1_def >= 0) { \
+ MACRO(ssa->ops[line-1].op1_def); \
+ } \
+ if (ssa->ops[line-1].op2_def >= 0) { \
+ MACRO(ssa->ops[line-1].op2_def); \
+ } \
+ if (ssa->ops[line-1].result_def >= 0) { \
+ MACRO(ssa->ops[line-1].result_def); \
+ } \
+ } else if ((uint32_t)line+1 < op_array->last && \
+ op_array->opcodes[line+1].opcode == ZEND_OP_DATA) { \
+ if (ssa->ops[line+1].op1_def >= 0) { \
+ MACRO(ssa->ops[line+1].op1_def); \
+ } \
+ if (ssa->ops[line+1].op2_def >= 0) { \
+ MACRO(ssa->ops[line+1].op2_def); \
+ } \
+ if (ssa->ops[line+1].result_def >= 0) { \
+ MACRO(ssa->ops[line+1].result_def); \
+ } \
+ } \
+ } while (0)
+
+
+#define FOR_EACH_VAR_USAGE(_var, MACRO) \
+ do { \
+ zend_ssa_phi *p = ssa->vars[_var].phi_use_chain; \
+ int use = ssa->vars[_var].use_chain; \
+ while (use >= 0) { \
+ FOR_EACH_DEFINED_VAR(use, MACRO); \
+ use = zend_ssa_next_use(ssa->ops, _var, use); \
+ } \
+ p = ssa->vars[_var].phi_use_chain; \
+ while (p) { \
+ MACRO(p->ssa_var); \
+ p = zend_ssa_next_use_phi(ssa, _var, p); \
+ } \
+ } while (0)
+
+static inline bool add_will_overflow(zend_long a, zend_long b) {
+ return (b > 0 && a > ZEND_LONG_MAX - b)
+ || (b < 0 && a < ZEND_LONG_MIN - b);
+}
+#if 0
+static inline bool sub_will_overflow(zend_long a, zend_long b) {
+ return (b > 0 && a < ZEND_LONG_MIN + b)
+ || (b < 0 && a > ZEND_LONG_MAX + b);
+}
+#endif
+
+static void zend_ssa_check_scc_var(const zend_op_array *op_array, zend_ssa *ssa, int var, int *index, int *dfs, int *root, zend_worklist_stack *stack) /* {{{ */
+{
+#ifdef SYM_RANGE
+ zend_ssa_phi *p;
+#endif
+
+ dfs[var] = *index;
+ (*index)++;
+ root[var] = var;
+
+ FOR_EACH_VAR_USAGE(var, CHECK_SCC_VAR);
+
+#ifdef SYM_RANGE
+ /* Process symbolic control-flow constraints */
+ p = ssa->vars[var].sym_use_chain;
+ while (p) {
+ CHECK_SCC_VAR(p->ssa_var);
+ p = p->sym_use_chain;
+ }
+#endif
+
+ if (root[var] == var) {
+ ssa->vars[var].scc = ssa->sccs;
+ while (stack->len > 0) {
+ int var2 = zend_worklist_stack_peek(stack);
+ if (dfs[var2] <= dfs[var]) {
+ break;
+ }
+ zend_worklist_stack_pop(stack);
+ ssa->vars[var2].scc = ssa->sccs;
+ }
+ ssa->sccs++;
+ } else {
+ zend_worklist_stack_push(stack, var);
+ }
+}
+/* }}} */
+
+ZEND_API int zend_ssa_find_sccs(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
+{
+ int index = 0, *dfs, *root;
+ zend_worklist_stack stack;
+ int j;
+ ALLOCA_FLAG(dfs_use_heap)
+ ALLOCA_FLAG(root_use_heap)
+ ALLOCA_FLAG(stack_use_heap)
+
+ dfs = do_alloca(sizeof(int) * ssa->vars_count, dfs_use_heap);
+ memset(dfs, -1, sizeof(int) * ssa->vars_count);
+ root = do_alloca(sizeof(int) * ssa->vars_count, root_use_heap);
+ ZEND_WORKLIST_STACK_ALLOCA(&stack, ssa->vars_count, stack_use_heap);
+
+ /* Find SCCs using Tarjan's algorithm. */
+ for (j = 0; j < ssa->vars_count; j++) {
+ if (!ssa->vars[j].no_val && dfs[j] < 0) {
+ zend_ssa_check_scc_var(op_array, ssa, j, &index, dfs, root, &stack);
+ }
+ }
+
+ /* Revert SCC order. This results in a topological order. */
+ for (j = 0; j < ssa->vars_count; j++) {
+ if (ssa->vars[j].scc >= 0) {
+ ssa->vars[j].scc = ssa->sccs - (ssa->vars[j].scc + 1);
+ }
+ }
+
+ for (j = 0; j < ssa->vars_count; j++) {
+ if (ssa->vars[j].scc >= 0) {
+ int var = j;
+ if (root[j] == j) {
+ ssa->vars[j].scc_entry = 1;
+ }
+ FOR_EACH_VAR_USAGE(var, CHECK_SCC_ENTRY);
+ }
+ }
+
+ ZEND_WORKLIST_STACK_FREE_ALLOCA(&stack, stack_use_heap);
+ free_alloca(root, root_use_heap);
+ free_alloca(dfs, dfs_use_heap);
+
+ return SUCCESS;
+}
+/* }}} */
+
+ZEND_API int zend_ssa_find_false_dependencies(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
+{
+ zend_ssa_var *ssa_vars = ssa->vars;
+ zend_ssa_op *ssa_ops = ssa->ops;
+ int ssa_vars_count = ssa->vars_count;
+ zend_bitset worklist;
+ int i, j, use;
+ zend_ssa_phi *p;
+ ALLOCA_FLAG(use_heap);
+
+ if (!op_array->function_name || !ssa->vars || !ssa->ops) {
+ return SUCCESS;
+ }
+
+ worklist = do_alloca(sizeof(zend_ulong) * zend_bitset_len(ssa_vars_count), use_heap);
+ memset(worklist, 0, sizeof(zend_ulong) * zend_bitset_len(ssa_vars_count));
+
+ for (i = 0; i < ssa_vars_count; i++) {
+ ssa_vars[i].no_val = 1; /* mark as unused */
+ use = ssa->vars[i].use_chain;
+ while (use >= 0) {
+ if (!zend_ssa_is_no_val_use(&op_array->opcodes[use], &ssa->ops[use], i)) {
+ ssa_vars[i].no_val = 0; /* used directly */
+ zend_bitset_incl(worklist, i);
+ break;
+ }
+ use = zend_ssa_next_use(ssa_ops, i, use);
+ }
+ }
+
+ WHILE_WORKLIST(worklist, zend_bitset_len(ssa_vars_count), i) {
+ if (ssa_vars[i].definition_phi) {
+ /* mark all possible sources as used */
+ p = ssa_vars[i].definition_phi;
+ if (p->pi >= 0) {
+ if (ssa_vars[p->sources[0]].no_val) {
+ ssa_vars[p->sources[0]].no_val = 0; /* used indirectly */
+ zend_bitset_incl(worklist, p->sources[0]);
+ }
+ } else {
+ for (j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) {
+ 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]);
+ }
+ }
+ }
+ }
+ } WHILE_WORKLIST_END();
+
+ free_alloca(worklist, use_heap);
+
+ return SUCCESS;
+}
+/* }}} */
+
+/* From "Hacker's Delight" */
+zend_ulong minOR(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d)
+{
+ zend_ulong m, temp;
+
+ m = Z_UL(1) << (sizeof(zend_ulong) * 8 - 1);
+ while (m != 0) {
+ if (~a & c & m) {
+ temp = (a | m) & -m;
+ if (temp <= b) {
+ a = temp;
+ break;
+ }
+ } else if (a & ~c & m) {
+ temp = (c | m) & -m;
+ if (temp <= d) {
+ c = temp;
+ break;
+ }
+ }
+ m = m >> 1;
+ }
+ return a | c;
+}
+
+zend_ulong maxOR(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d)
+{
+ zend_ulong m, temp;
+
+ m = Z_UL(1) << (sizeof(zend_ulong) * 8 - 1);
+ while (m != 0) {
+ if (b & d & m) {
+ temp = (b - m) | (m - 1);
+ if (temp >= a) {
+ b = temp;
+ break;
+ }
+ temp = (d - m) | (m - 1);
+ if (temp >= c) {
+ d = temp;
+ break;
+ }
+ }
+ m = m >> 1;
+ }
+ return b | d;
+}
+
+zend_ulong minAND(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d)
+{
+ zend_ulong m, temp;
+
+ m = Z_UL(1) << (sizeof(zend_ulong) * 8 - 1);
+ while (m != 0) {
+ if (~a & ~c & m) {
+ temp = (a | m) & -m;
+ if (temp <= b) {
+ a = temp;
+ break;
+ }
+ temp = (c | m) & -m;
+ if (temp <= d) {
+ c = temp;
+ break;
+ }
+ }
+ m = m >> 1;
+ }
+ return a & c;
+}
+
+zend_ulong maxAND(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d)
+{
+ zend_ulong m, temp;
+
+ m = Z_UL(1) << (sizeof(zend_ulong) * 8 - 1);
+ while (m != 0) {
+ if (b & ~d & m) {
+ temp = (b | ~m) | (m - 1);
+ if (temp >= a) {
+ b = temp;
+ break;
+ }
+ } else if (~b & d & m) {
+ temp = (d | ~m) | (m - 1);
+ if (temp >= c) {
+ d = temp;
+ break;
+ }
+ }
+ m = m >> 1;
+ }
+ return b & d;
+}
+
+zend_ulong minXOR(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d)
+{
+ return minAND(a, b, ~d, ~c) | minAND(~b, ~a, c, d);
+}
+
+zend_ulong maxXOR(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d)
+{
+ return maxOR(0, maxAND(a, b, ~d, ~c), 0, maxAND(~b, ~a, c, d));
+}
+
+/* Based on "Hacker's Delight" */
+
+/*
+0: + + + + 0 0 0 0 => 0 0 + min/max
+2: + + - + 0 0 1 0 => 1 0 ? min(a,b,c,-1)/max(a,b,0,d)
+3: + + - - 0 0 1 1 => 1 1 - min/max
+8: - + + + 1 0 0 0 => 1 0 ? min(a,-1,b,d)/max(0,b,c,d)
+a: - + - + 1 0 1 0 => 1 0 ? MIN(a,c)/max(0,b,0,d)
+b: - + - - 1 0 1 1 => 1 1 - c/-1
+c: - - + + 1 1 0 0 => 1 1 - min/max
+e: - - - + 1 1 1 0 => 1 1 - a/-1
+f - - - - 1 1 1 1 => 1 1 - min/max
+*/
+static void zend_ssa_range_or(zend_long a, zend_long b, zend_long c, zend_long d, zend_ssa_range *tmp)
+{
+ int x = ((a < 0) ? 8 : 0) |
+ ((b < 0) ? 4 : 0) |
+ ((c < 0) ? 2 : 0) |
+ ((d < 0) ? 2 : 0);
+ switch (x) {
+ case 0x0:
+ case 0x3:
+ case 0xc:
+ case 0xf:
+ tmp->min = minOR(a, b, c, d);
+ tmp->max = maxOR(a, b, c, d);
+ break;
+ case 0x2:
+ tmp->min = minOR(a, b, c, -1);
+ tmp->max = maxOR(a, b, 0, d);
+ break;
+ case 0x8:
+ tmp->min = minOR(a, -1, c, d);
+ tmp->max = maxOR(0, b, c, d);
+ break;
+ case 0xa:
+ tmp->min = MIN(a, c);
+ tmp->max = maxOR(0, b, 0, d);
+ break;
+ case 0xb:
+ tmp->min = c;
+ tmp->max = -1;
+ break;
+ case 0xe:
+ tmp->min = a;
+ tmp->max = -1;
+ break;
+ }
+}
+
+/*
+0: + + + + 0 0 0 0 => 0 0 + min/max
+2: + + - + 0 0 1 0 => 0 0 + 0/b
+3: + + - - 0 0 1 1 => 0 0 + min/max
+8: - + + + 1 0 0 0 => 0 0 + 0/d
+a: - + - + 1 0 1 0 => 1 0 ? min(a,-1,c,-1)/NAX(b,d)
+b: - + - - 1 0 1 1 => 1 0 ? min(a,-1,c,d)/max(0,b,c,d)
+c: - - + + 1 1 0 0 => 1 1 - min/max
+e: - - - + 1 1 1 0 => 1 0 ? min(a,b,c,-1)/max(a,b,0,d)
+f - - - - 1 1 1 1 => 1 1 - min/max
+*/
+static void zend_ssa_range_and(zend_long a, zend_long b, zend_long c, zend_long d, zend_ssa_range *tmp)
+{
+ int x = ((a < 0) ? 8 : 0) |
+ ((b < 0) ? 4 : 0) |
+ ((c < 0) ? 2 : 0) |
+ ((d < 0) ? 2 : 0);
+ switch (x) {
+ case 0x0:
+ case 0x3:
+ case 0xc:
+ case 0xf:
+ tmp->min = minAND(a, b, c, d);
+ tmp->max = maxAND(a, b, c, d);
+ break;
+ case 0x2:
+ tmp->min = 0;
+ tmp->max = b;
+ break;
+ case 0x8:
+ tmp->min = 0;
+ tmp->max = d;
+ break;
+ case 0xa:
+ tmp->min = minAND(a, -1, c, -1);
+ tmp->max = MAX(b, d);
+ break;
+ case 0xb:
+ tmp->min = minAND(a, -1, c, d);
+ tmp->max = maxAND(0, b, c, d);
+ break;
+ case 0xe:
+ tmp->min = minAND(a, b, c, -1);
+ tmp->max = maxAND(a, b, 0, d);
+ break;
+ }
+}
+
+static inline bool zend_abs_range(
+ zend_long min, zend_long max, zend_long *abs_min, zend_long *abs_max) {
+ if (min == ZEND_LONG_MIN) {
+ /* Cannot take absolute value of LONG_MIN */
+ return 0;
+ }
+
+ if (min >= 0) {
+ *abs_min = min;
+ *abs_max = max;
+ } else if (max <= 0) {
+ *abs_min = -max;
+ *abs_max = -min;
+ } else {
+ /* Range crossing zero */
+ *abs_min = 0;
+ *abs_max = MAX(max, -min);
+ }
+
+ return 1;
+}
+
+static inline zend_long safe_shift_left(zend_long n, zend_long s) {
+ return (zend_long) ((zend_ulong) n << (zend_ulong) s);
+}
+
+static inline bool shift_left_overflows(zend_long n, zend_long s) {
+ /* This considers shifts that shift in the sign bit to be overflowing as well */
+ if (n >= 0) {
+ return s >= SIZEOF_ZEND_LONG * 8 - 1 || safe_shift_left(n, s) < n;
+ } else {
+ return s >= SIZEOF_ZEND_LONG * 8 || safe_shift_left(n, s) > n;
+ }
+}
+
+/* If b does not divide a exactly, return the two adjacent values between which the real result
+ * lies. */
+static void float_div(zend_long a, zend_long b, zend_long *r1, zend_long *r2) {
+ *r1 = *r2 = a / b;
+ if (a % b != 0) {
+ if (*r2 < 0) {
+ (*r2)--;
+ } else {
+ (*r2)++;
+ }
+ }
+}
+
+static int zend_inference_calc_binary_op_range(
+ const zend_op_array *op_array, zend_ssa *ssa,
+ zend_op *opline, zend_ssa_op *ssa_op, zend_uchar opcode, zend_ssa_range *tmp) {
+ zend_long op1_min, op2_min, op1_max, op2_max, t1, t2, t3, t4;
+
+ switch (opcode) {
+ case ZEND_ADD:
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+ if (OP1_RANGE_UNDERFLOW() ||
+ OP2_RANGE_UNDERFLOW() ||
+ zend_add_will_overflow(op1_min, op2_min)) {
+ tmp->underflow = 1;
+ tmp->min = ZEND_LONG_MIN;
+ } else {
+ tmp->min = op1_min + op2_min;
+ }
+ if (OP1_RANGE_OVERFLOW() ||
+ OP2_RANGE_OVERFLOW() ||
+ zend_add_will_overflow(op1_max, op2_max)) {
+ tmp->overflow = 1;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ tmp->max = op1_max + op2_max;
+ }
+ return 1;
+ }
+ break;
+ case ZEND_SUB:
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+ if (OP1_RANGE_UNDERFLOW() ||
+ OP2_RANGE_OVERFLOW() ||
+ zend_sub_will_overflow(op1_min, op2_max)) {
+ tmp->underflow = 1;
+ tmp->min = ZEND_LONG_MIN;
+ } else {
+ tmp->min = op1_min - op2_max;
+ }
+ if (OP1_RANGE_OVERFLOW() ||
+ OP2_RANGE_UNDERFLOW() ||
+ zend_sub_will_overflow(op1_max, op2_min)) {
+ tmp->overflow = 1;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ tmp->max = op1_max - op2_min;
+ }
+ return 1;
+ }
+ break;
+ case ZEND_MUL:
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ double dummy;
+ zend_long t1_overflow, t2_overflow, t3_overflow, t4_overflow;
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+ /* Suppress uninit variable warnings, these will only be used if the overflow
+ * flags are all false. */
+ t1 = t2 = t3 = t4 = 0;
+ ZEND_SIGNED_MULTIPLY_LONG(op1_min, op2_min, t1, dummy, t1_overflow);
+ ZEND_SIGNED_MULTIPLY_LONG(op1_min, op2_max, t2, dummy, t2_overflow);
+ ZEND_SIGNED_MULTIPLY_LONG(op1_max, op2_min, t3, dummy, t3_overflow);
+ ZEND_SIGNED_MULTIPLY_LONG(op1_max, op2_max, t4, dummy, t4_overflow);
+ (void) dummy;
+
+ // FIXME: more careful overflow checks?
+ if (OP1_RANGE_UNDERFLOW() || OP2_RANGE_UNDERFLOW() ||
+ OP1_RANGE_OVERFLOW() || OP2_RANGE_OVERFLOW() ||
+ t1_overflow || t2_overflow || t3_overflow || t4_overflow
+ ) {
+ tmp->underflow = 1;
+ tmp->overflow = 1;
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ tmp->min = MIN(MIN(t1, t2), MIN(t3, t4));
+ tmp->max = MAX(MAX(t1, t2), MAX(t3, t4));
+ }
+ return 1;
+ }
+ break;
+ case ZEND_DIV:
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+ if (op2_min <= 0 && op2_max >= 0) {
+ /* If op2 crosses zero, then floating point values close to zero might be
+ * possible, which will result in arbitrarily large results. As such, we can't
+ * do anything useful in that case. */
+ break;
+ }
+ if (op1_min == ZEND_LONG_MIN && op2_max == -1) {
+ /* Avoid ill-defined division, which may trigger SIGFPE. */
+ break;
+ }
+
+ zend_long t1_, t2_, t3_, t4_;
+ float_div(op1_min, op2_min, &t1, &t1_);
+ float_div(op1_min, op2_max, &t2, &t2_);
+ float_div(op1_max, op2_min, &t3, &t3_);
+ float_div(op1_max, op2_max, &t4, &t4_);
+
+ /* The only case in which division can "overflow" either a division by an absolute
+ * value smaller than one, or LONG_MIN / -1 in particular. Both cases have already
+ * been excluded above. */
+ if (OP1_RANGE_UNDERFLOW() ||
+ OP2_RANGE_UNDERFLOW() ||
+ OP1_RANGE_OVERFLOW() ||
+ OP2_RANGE_OVERFLOW()) {
+ tmp->underflow = 1;
+ tmp->overflow = 1;
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ tmp->min = MIN(MIN(MIN(t1, t2), MIN(t3, t4)), MIN(MIN(t1_, t2_), MIN(t3_, t4_)));
+ tmp->max = MAX(MAX(MAX(t1, t2), MAX(t3, t4)), MAX(MAX(t1_, t2_), MAX(t3_, t4_)));
+ }
+ return 1;
+ }
+ break;
+ case ZEND_MOD:
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ if (OP1_RANGE_UNDERFLOW() ||
+ OP2_RANGE_UNDERFLOW() ||
+ OP1_RANGE_OVERFLOW() ||
+ OP2_RANGE_OVERFLOW()) {
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ zend_long op2_abs_min, op2_abs_max;
+
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+ if (!zend_abs_range(op2_min, op2_max, &op2_abs_min, &op2_abs_max)) {
+ break;
+ }
+
+ if (op2_abs_max == 0) {
+ /* Always modulus by zero, nothing we can do */
+ break;
+ }
+ if (op2_abs_min == 0) {
+ /* Ignore the modulus by zero case, which will throw */
+ op2_abs_min++;
+ }
+
+ if (op1_min >= 0) {
+ tmp->min = op1_max < op2_abs_min ? op1_min : 0;
+ tmp->max = MIN(op1_max, op2_abs_max - 1);
+ } else if (op1_max <= 0) {
+ tmp->min = MAX(op1_min, -op2_abs_max + 1);
+ tmp->max = op1_min > -op2_abs_min ? op1_max : 0;
+ } else {
+ tmp->min = MAX(op1_min, -op2_abs_max + 1);
+ tmp->max = MIN(op1_max, op2_abs_max - 1);
+ }
+ }
+ return 1;
+ }
+ break;
+ case ZEND_SL:
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ if (OP1_RANGE_UNDERFLOW() ||
+ OP2_RANGE_UNDERFLOW() ||
+ OP1_RANGE_OVERFLOW() ||
+ OP2_RANGE_OVERFLOW()) {
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+
+ /* Shifts by negative numbers will throw, ignore them */
+ if (op2_min < 0) {
+ op2_min = 0;
+ }
+ if (op2_max < 0) {
+ op2_max = 0;
+ }
+
+ if (shift_left_overflows(op1_min, op2_max)
+ || shift_left_overflows(op1_max, op2_max)) {
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ t1 = safe_shift_left(op1_min, op2_min);
+ t2 = safe_shift_left(op1_min, op2_max);
+ t3 = safe_shift_left(op1_max, op2_min);
+ t4 = safe_shift_left(op1_max, op2_max);
+ tmp->min = MIN(MIN(t1, t2), MIN(t3, t4));
+ tmp->max = MAX(MAX(t1, t2), MAX(t3, t4));
+ }
+ }
+ return 1;
+ }
+ break;
+ case ZEND_SR:
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ if (OP1_RANGE_UNDERFLOW() ||
+ OP2_RANGE_UNDERFLOW() ||
+ OP1_RANGE_OVERFLOW() ||
+ OP2_RANGE_OVERFLOW()) {
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+
+ /* Shifts by negative numbers will throw, ignore them */
+ if (op2_min < 0) {
+ op2_min = 0;
+ }
+ if (op2_max < 0) {
+ op2_max = 0;
+ }
+
+ /* Shifts by more than the integer size will be 0 or -1 */
+ if (op2_min >= SIZEOF_ZEND_LONG * 8) {
+ op2_min = SIZEOF_ZEND_LONG * 8 - 1;
+ }
+ if (op2_max >= SIZEOF_ZEND_LONG * 8) {
+ op2_max = SIZEOF_ZEND_LONG * 8 - 1;
+ }
+
+ t1 = op1_min >> op2_min;
+ t2 = op1_min >> op2_max;
+ t3 = op1_max >> op2_min;
+ t4 = op1_max >> op2_max;
+ tmp->min = MIN(MIN(t1, t2), MIN(t3, t4));
+ tmp->max = MAX(MAX(t1, t2), MAX(t3, t4));
+ }
+ return 1;
+ }
+ break;
+ case ZEND_BW_OR:
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ if (OP1_RANGE_UNDERFLOW() ||
+ OP2_RANGE_UNDERFLOW() ||
+ OP1_RANGE_OVERFLOW() ||
+ OP2_RANGE_OVERFLOW()) {
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+ zend_ssa_range_or(op1_min, op1_max, op2_min, op2_max, tmp);
+ }
+ return 1;
+ }
+ break;
+ case ZEND_BW_AND:
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ if (OP1_RANGE_UNDERFLOW() ||
+ OP2_RANGE_UNDERFLOW() ||
+ OP1_RANGE_OVERFLOW() ||
+ OP2_RANGE_OVERFLOW()) {
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+ zend_ssa_range_and(op1_min, op1_max, op2_min, op2_max, tmp);
+ }
+ return 1;
+ }
+ break;
+ case ZEND_BW_XOR:
+ // TODO
+ break;
+ EMPTY_SWITCH_DEFAULT_CASE()
+ }
+ return 0;
+}
+
+int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int var, int widening, int narrowing, zend_ssa_range *tmp)
+{
+ uint32_t line;
+ zend_op *opline;
+ zend_ssa_op *ssa_op;
+
+ if (ssa->vars[var].definition_phi) {
+ zend_ssa_phi *p = ssa->vars[var].definition_phi;
+ int i;
+
+ tmp->underflow = 0;
+ tmp->min = ZEND_LONG_MAX;
+ tmp->max = ZEND_LONG_MIN;
+ tmp->overflow = 0;
+ if (p->pi >= 0 && p->has_range_constraint) {
+ zend_ssa_range_constraint *constraint = &p->constraint.range;
+ if (constraint->negative) {
+ if (ssa->var_info[p->sources[0]].has_range) {
+ *tmp = ssa->var_info[p->sources[0]].range;
+ } else if (narrowing) {
+ tmp->underflow = 1;
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ tmp->overflow = 1;
+ }
+
+#ifdef NEG_RANGE
+ if (constraint->min_ssa_var < 0 &&
+ constraint->max_ssa_var < 0 &&
+ ssa->var_info[p->ssa_var].has_range) {
+ LOG_NEG_RANGE("%s() #%d [%ld..%ld] -> [%ld..%ld]?\n",
+ ZSTR_VAL(op_array->function_name),
+ p->ssa_var,
+ ssa->var_info[p->ssa_var].range.min,
+ ssa->var_info[p->ssa_var].range.max,
+ tmp->min,
+ tmp->max);
+ if (constraint->negative == NEG_USE_LT &&
+ tmp->max >= constraint->range.min) {
+ tmp->overflow = 0;
+ tmp->max = constraint->range.min - 1;
+ LOG_NEG_RANGE(" => [%ld..%ld]\n", tmp->min, tmp->max);
+ } else if (constraint->negative == NEG_USE_GT &&
+ tmp->min <= constraint->range.max) {
+ tmp->underflow = 0;
+ tmp->min = constraint->range.max + 1;
+ LOG_NEG_RANGE(" => [%ld..%ld]\n", tmp->min, tmp->max);
+ }
+ }
+#endif
+ } else if (ssa->var_info[p->sources[0]].has_range) {
+ /* intersection */
+ *tmp = ssa->var_info[p->sources[0]].range;
+ if (constraint->min_ssa_var < 0) {
+ tmp->underflow = constraint->range.underflow && tmp->underflow;
+ tmp->min = MAX(constraint->range.min, tmp->min);
+#ifdef SYM_RANGE
+ } else if (narrowing && ssa->var_info[constraint->min_ssa_var].has_range) {
+ tmp->underflow = ssa->var_info[constraint->min_ssa_var].range.underflow && tmp->underflow;
+ if (!add_will_overflow(ssa->var_info[constraint->min_ssa_var].range.min, constraint->range.min)) {
+ tmp->min = MAX(ssa->var_info[constraint->min_ssa_var].range.min + constraint->range.min, tmp->min);
+ }
+#endif
+ }
+ if (constraint->max_ssa_var < 0) {
+ tmp->max = MIN(constraint->range.max, tmp->max);
+ tmp->overflow = constraint->range.overflow && tmp->overflow;
+#ifdef SYM_RANGE
+ } else if (narrowing && ssa->var_info[constraint->max_ssa_var].has_range) {
+ if (!add_will_overflow(ssa->var_info[constraint->max_ssa_var].range.max, constraint->range.max)) {
+ tmp->max = MIN(ssa->var_info[constraint->max_ssa_var].range.max + constraint->range.max, tmp->max);
+ }
+ tmp->overflow = ssa->var_info[constraint->max_ssa_var].range.overflow && tmp->overflow;
+#endif
+ }
+ } else if (narrowing) {
+ if (constraint->min_ssa_var < 0) {
+ tmp->underflow = constraint->range.underflow;
+ tmp->min = constraint->range.min;
+#ifdef SYM_RANGE
+ } else if (narrowing && ssa->var_info[constraint->min_ssa_var].has_range) {
+ if (add_will_overflow(ssa->var_info[constraint->min_ssa_var].range.min, constraint->range.min)) {
+ tmp->underflow = 1;
+ tmp->min = ZEND_LONG_MIN;
+ } else {
+ tmp->underflow = ssa->var_info[constraint->min_ssa_var].range.underflow;
+ tmp->min = ssa->var_info[constraint->min_ssa_var].range.min + constraint->range.min;
+ }
+#endif
+ } else {
+ tmp->underflow = 1;
+ tmp->min = ZEND_LONG_MIN;
+ }
+ if (constraint->max_ssa_var < 0) {
+ tmp->max = constraint->range.max;
+ tmp->overflow = constraint->range.overflow;
+#ifdef SYM_RANGE
+ } else if (narrowing && ssa->var_info[constraint->max_ssa_var].has_range) {
+ if (add_will_overflow(ssa->var_info[constraint->max_ssa_var].range.max, constraint->range.max)) {
+ tmp->overflow = 1;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ tmp->max = ssa->var_info[constraint->max_ssa_var].range.max + constraint->range.max;
+ tmp->overflow = ssa->var_info[constraint->max_ssa_var].range.overflow;
+ }
+#endif
+ } else {
+ tmp->max = ZEND_LONG_MAX;
+ tmp->overflow = 1;
+ }
+ }
+ } else {
+ for (i = 0; i < ssa->cfg.blocks[p->block].predecessors_count; i++) {
+ 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);
+ tmp->max = MAX(tmp->max, ssa->var_info[p->sources[i]].range.max);
+ tmp->overflow |= ssa->var_info[p->sources[i]].range.overflow;
+ } else if (narrowing) {
+ tmp->underflow = 1;
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ tmp->overflow = 1;
+ }
+ }
+ }
+ return (tmp->min <= tmp->max);
+ } else if (ssa->vars[var].definition < 0) {
+ if (var < op_array->last_var &&
+ op_array->function_name) {
+
+ tmp->min = 0;
+ tmp->max = 0;
+ tmp->underflow = 0;
+ tmp->overflow = 0;
+ return 1;
+ }
+ return 0;
+ }
+ line = ssa->vars[var].definition;
+ opline = op_array->opcodes + line;
+ ssa_op = &ssa->ops[line];
+
+ return zend_inference_propagate_range(op_array, ssa, opline, ssa_op, var, tmp);
+}
+
+ZEND_API int zend_inference_propagate_range(const zend_op_array *op_array, zend_ssa *ssa, zend_op *opline, zend_ssa_op* ssa_op, int var, zend_ssa_range *tmp)
+{
+ zend_long op1_min, op2_min, op1_max, op2_max;
+
+ tmp->underflow = 0;
+ tmp->overflow = 0;
+ switch (opline->opcode) {
+ case ZEND_ADD:
+ case ZEND_SUB:
+ case ZEND_MUL:
+ case ZEND_DIV:
+ case ZEND_MOD:
+ case ZEND_SL:
+ case ZEND_SR:
+ case ZEND_BW_OR:
+ case ZEND_BW_AND:
+ case ZEND_BW_XOR:
+ if (ssa_op->result_def == var) {
+ return zend_inference_calc_binary_op_range(
+ op_array, ssa, opline, ssa_op, opline->opcode, tmp);
+ }
+ break;
+
+ case ZEND_BW_NOT:
+ if (ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE()) {
+ if (OP1_RANGE_UNDERFLOW() ||
+ OP1_RANGE_OVERFLOW()) {
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ op1_min = OP1_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ tmp->min = ~op1_max;
+ tmp->max = ~op1_min;
+ }
+ return 1;
+ }
+ }
+ break;
+ case ZEND_CAST:
+ if (ssa_op->op1_def == var) {
+ if (ssa_op->op1_def >= 0) {
+ if (OP1_HAS_RANGE()) {
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ return 1;
+ }
+ }
+ } else if (ssa_op->result_def == var) {
+ if (opline->extended_value == IS_LONG) {
+ if (OP1_HAS_RANGE()) {
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ return 1;
+ } else {
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ return 1;
+ }
+ }
+ }
+ break;
+ case ZEND_BOOL:
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ if (ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ tmp->min = (op1_min > 0 || op1_max < 0);
+ tmp->max = (op1_min != 0 || op1_max != 0);
+ return 1;
+ } else {
+ tmp->min = 0;
+ tmp->max = 1;
+ return 1;
+ }
+ }
+ break;
+ case ZEND_BOOL_NOT:
+ if (ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ tmp->min = (op1_min == 0 && op1_max == 0);
+ tmp->max = (op1_min <= 0 && op1_max >= 0);
+ return 1;
+ } else {
+ tmp->min = 0;
+ tmp->max = 1;
+ return 1;
+ }
+ }
+ break;
+ case ZEND_BOOL_XOR:
+ if (ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+ op1_min = (op1_min > 0 || op1_max < 0);
+ op1_max = (op1_min != 0 || op1_max != 0);
+ op2_min = (op2_min > 0 || op2_max < 0);
+ op2_max = (op2_min != 0 || op2_max != 0);
+ tmp->min = 0;
+ tmp->max = 1;
+ if (op1_min == op1_max && op2_min == op2_max) {
+ if (op1_min == op2_min) {
+ tmp->max = 0;
+ } else {
+ tmp->min = 1;
+ }
+ }
+ return 1;
+ } else {
+ tmp->min = 0;
+ tmp->max = 1;
+ return 1;
+ }
+ }
+ break;
+ case ZEND_IS_IDENTICAL:
+ case ZEND_IS_EQUAL:
+ if (ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+
+ tmp->min = (op1_min == op1_max &&
+ op2_min == op2_max &&
+ op1_min == op2_max);
+ tmp->max = (op1_min <= op2_max && op1_max >= op2_min);
+ return 1;
+ } else {
+ tmp->min = 0;
+ tmp->max = 1;
+ return 1;
+ }
+ }
+ break;
+ case ZEND_IS_NOT_IDENTICAL:
+ case ZEND_IS_NOT_EQUAL:
+ if (ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+
+ tmp->min = (op1_min > op2_max || op1_max < op2_min);
+ tmp->max = (op1_min != op1_max ||
+ op2_min != op2_max ||
+ op1_min != op2_max);
+ return 1;
+ } else {
+ tmp->min = 0;
+ tmp->max = 1;
+ return 1;
+ }
+ }
+ break;
+ case ZEND_IS_SMALLER:
+ if (ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+
+ tmp->min = op1_max < op2_min;
+ tmp->max = op1_min < op2_max;
+ return 1;
+ } else {
+ tmp->min = 0;
+ tmp->max = 1;
+ return 1;
+ }
+ }
+ break;
+ case ZEND_IS_SMALLER_OR_EQUAL:
+ if (ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+
+ tmp->min = op1_max <= op2_min;
+ tmp->max = op1_min <= op2_max;
+ return 1;
+ } else {
+ tmp->min = 0;
+ tmp->max = 1;
+ return 1;
+ }
+ }
+ break;
+ case ZEND_QM_ASSIGN:
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ case ZEND_COPY_TMP:
+ if (ssa_op->op1_def == var) {
+ if (ssa_op->op1_def >= 0) {
+ if (OP1_HAS_RANGE()) {
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ return 1;
+ }
+ }
+ }
+ if (ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE()) {
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ return 1;
+ }
+ }
+ break;
+ case ZEND_ASSERT_CHECK:
+ if (ssa_op->result_def == var) {
+ tmp->min = 0;
+ tmp->max = 1;
+ return 1;
+ }
+ break;
+ case ZEND_SEND_VAR:
+ if (ssa_op->op1_def == var) {
+ if (ssa_op->op1_def >= 0) {
+ if (OP1_HAS_RANGE()) {
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ return 1;
+ }
+ }
+ }
+ break;
+ case ZEND_PRE_INC:
+ if (ssa_op->op1_def == var || ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE()) {
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ if (tmp->max < ZEND_LONG_MAX) {
+ tmp->max++;
+ } else {
+ tmp->overflow = 1;
+ }
+ if (tmp->min < ZEND_LONG_MAX && !tmp->underflow) {
+ tmp->min++;
+ }
+ return 1;
+ }
+ }
+ break;
+ case ZEND_PRE_DEC:
+ if (ssa_op->op1_def == var || ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE()) {
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ if (tmp->min > ZEND_LONG_MIN) {
+ tmp->min--;
+ } else {
+ tmp->underflow = 1;
+ }
+ if (tmp->max > ZEND_LONG_MIN && !tmp->overflow) {
+ tmp->max--;
+ }
+ return 1;
+ }
+ }
+ break;
+ case ZEND_POST_INC:
+ if (ssa_op->op1_def == var || ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE()) {
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ if (ssa_op->result_def == var) {
+ return 1;
+ }
+ if (tmp->max < ZEND_LONG_MAX) {
+ tmp->max++;
+ } else {
+ tmp->overflow = 1;
+ }
+ if (tmp->min < ZEND_LONG_MAX && !tmp->underflow) {
+ tmp->min++;
+ }
+ return 1;
+ }
+ }
+ break;
+ case ZEND_POST_DEC:
+ if (ssa_op->op1_def == var || ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE()) {
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ if (ssa_op->result_def == var) {
+ return 1;
+ }
+ if (tmp->min > ZEND_LONG_MIN) {
+ tmp->min--;
+ } else {
+ tmp->underflow = 1;
+ }
+ if (tmp->max > ZEND_LONG_MIN && !tmp->overflow) {
+ tmp->max--;
+ }
+ return 1;
+ }
+ }
+ break;
+ case ZEND_UNSET_DIM:
+ case ZEND_UNSET_OBJ:
+ if (ssa_op->op1_def == var) {
+ /* If op1 is scalar, UNSET_DIM and UNSET_OBJ have no effect, so we can keep
+ * the previous ranges. */
+ if (OP1_HAS_RANGE()) {
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ return 1;
+ }
+ }
+ break;
+ case ZEND_ASSIGN:
+ if (ssa_op->op1_def == var || ssa_op->op2_def == var || ssa_op->result_def == var) {
+ if (OP2_HAS_RANGE()) {
+ tmp->min = OP2_MIN_RANGE();
+ tmp->max = OP2_MAX_RANGE();
+ tmp->underflow = OP2_RANGE_UNDERFLOW();
+ tmp->overflow = OP2_RANGE_OVERFLOW();
+ return 1;
+ }
+ }
+ break;
+ case ZEND_ASSIGN_DIM:
+ case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_STATIC_PROP:
+ case ZEND_ASSIGN_DIM_OP:
+ case ZEND_ASSIGN_OBJ_OP:
+ case ZEND_ASSIGN_STATIC_PROP_OP:
+ if ((ssa_op+1)->op1_def == var) {
+ opline++;
+ ssa_op++;
+ if (OP1_HAS_RANGE()) {
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ }
+ return 1;
+ }
+ break;
+ case ZEND_ASSIGN_OP:
+ if (opline->extended_value != ZEND_CONCAT
+ && opline->extended_value != ZEND_POW) {
+ if (ssa_op->op1_def == var || ssa_op->result_def == var) {
+ return zend_inference_calc_binary_op_range(
+ op_array, ssa, opline, ssa_op,
+ opline->extended_value, tmp);
+ }
+ }
+ break;
+ case ZEND_OP_DATA:
+ if (ssa_op->op1_def == var) {
+ if ((opline-1)->opcode == ZEND_ASSIGN_DIM ||
+ (opline-1)->opcode == ZEND_ASSIGN_OBJ ||
+ (opline-1)->opcode == ZEND_ASSIGN_STATIC_PROP ||
+ (opline-1)->opcode == ZEND_ASSIGN_DIM_OP ||
+ (opline-1)->opcode == ZEND_ASSIGN_OBJ_OP ||
+ (opline-1)->opcode == ZEND_ASSIGN_STATIC_PROP_OP) {
+ if (OP1_HAS_RANGE()) {
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ return 1;
+ }
+ }
+ break;
+ }
+ break;
+ case ZEND_RECV:
+ case ZEND_RECV_INIT:
+ if (ssa_op->result_def == var) {
+ if (op_array->arg_info &&
+ opline->op1.num <= op_array->num_args) {
+ zend_type type = op_array->arg_info[opline->op1.num-1].type;
+ uint32_t mask = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type);
+ if (mask == MAY_BE_LONG) {
+ tmp->underflow = 0;
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ tmp->overflow = 0;
+ return 1;
+ } else if (mask == MAY_BE_BOOL) {
+ tmp->underflow = 0;
+ tmp->min = 0;
+ tmp->max = 1;
+ tmp->overflow = 0;
+ return 1;
+ }
+ }
+ }
+ break;
+ case ZEND_STRLEN:
+ if (ssa_op->result_def == var) {
+#if SIZEOF_ZEND_LONG == 4
+ /* The length of a string is a non-negative integer. However, on 32-bit
+ * platforms overflows into negative lengths may occur, so it's better
+ * to not assume any particular range. */
+ tmp->min = ZEND_LONG_MIN;
+#else
+ tmp->min = 0;
+#endif
+ tmp->max = ZEND_LONG_MAX;
+ return 1;
+ }
+ break;
+ case ZEND_FUNC_NUM_ARGS:
+ tmp->min = 0;
+ tmp->max = ZEND_LONG_MAX;
+ return 1;
+ case ZEND_COUNT:
+ /* count() on Countable objects may return negative numbers */
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ return 1;
+ case ZEND_DO_FCALL:
+ case ZEND_DO_ICALL:
+ case ZEND_DO_UCALL:
+ case ZEND_DO_FCALL_BY_NAME:
+ if (ssa_op->result_def == var) {
+ zend_func_info *func_info = ZEND_FUNC_INFO(op_array);
+ zend_call_info *call_info;
+ if (!func_info || !func_info->call_map) {
+ break;
+ }
+
+ call_info = func_info->call_map[opline - op_array->opcodes];
+ if (!call_info) {
+ break;
+ }
+ if (call_info->callee_func->type == ZEND_USER_FUNCTION) {
+ func_info = ZEND_FUNC_INFO(&call_info->callee_func->op_array);
+ if (func_info && func_info->return_info.has_range) {
+ *tmp = func_info->return_info.range;
+ return 1;
+ }
+ }
+//TODO: we can't use type inference for internal functions at this point ???
+#if 0
+ uint32_t type;
+
+ type = zend_get_func_info(call_info, ssa);
+ if (!(type & (MAY_BE_ANY - (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)))) {
+ tmp->underflow = 0;
+ tmp->min = 0;
+ tmp->max = 0;
+ tmp->overflow = 0;
+ if (type & MAY_BE_LONG) {
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else if (type & MAY_BE_TRUE) {
+ if (!(type & (MAY_BE_NULL|MAY_BE_FALSE))) {
+ tmp->min = 1;
+ }
+ tmp->max = 1;
+ }
+ return 1;
+ }
+#endif
+ }
+ break;
+ // FIXME: support for more opcodes
+ default:
+ break;
+ }
+ return 0;
+}
+
+void zend_inference_init_range(const zend_op_array *op_array, zend_ssa *ssa, int var, bool underflow, zend_long min, zend_long max, bool overflow)
+{
+ if (underflow) {
+ min = ZEND_LONG_MIN;
+ }
+ if (overflow) {
+ max = ZEND_LONG_MAX;
+ }
+ ssa->var_info[var].has_range = 1;
+ ssa->var_info[var].range.underflow = underflow;
+ ssa->var_info[var].range.min = min;
+ ssa->var_info[var].range.max = max;
+ ssa->var_info[var].range.overflow = overflow;
+ LOG_SSA_RANGE(" change range (init SCC %2d) %2d [%s%ld..%ld%s]\n", ssa->vars[var].scc, var, (underflow?"-- ":""), min, max, (overflow?" ++":""));
+}
+
+int zend_inference_widening_meet(zend_ssa_var_info *var_info, zend_ssa_range *r)
+{
+ if (!var_info->has_range) {
+ var_info->has_range = 1;
+ } else {
+ if (r->underflow ||
+ var_info->range.underflow ||
+ r->min < var_info->range.min) {
+ r->underflow = 1;
+ r->min = ZEND_LONG_MIN;
+ }
+ if (r->overflow ||
+ var_info->range.overflow ||
+ r->max > var_info->range.max) {
+ r->overflow = 1;
+ r->max = ZEND_LONG_MAX;
+ }
+ if (var_info->range.min == r->min &&
+ var_info->range.max == r->max &&
+ var_info->range.underflow == r->underflow &&
+ var_info->range.overflow == r->overflow) {
+ return 0;
+ }
+ }
+ var_info->range = *r;
+ return 1;
+}
+
+static int zend_ssa_range_widening(const zend_op_array *op_array, zend_ssa *ssa, int var, int scc)
+{
+ zend_ssa_range tmp;
+
+ if (zend_inference_calc_range(op_array, ssa, var, 1, 0, &tmp)) {
+ if (zend_inference_widening_meet(&ssa->var_info[var], &tmp)) {
+ LOG_SSA_RANGE(" change range (widening SCC %2d) %2d [%s%ld..%ld%s]\n", scc, var, (tmp.underflow?"-- ":""), tmp.min, tmp.max, (tmp.overflow?" ++":""));
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int zend_inference_narrowing_meet(zend_ssa_var_info *var_info, zend_ssa_range *r)
+{
+ if (!var_info->has_range) {
+ var_info->has_range = 1;
+ } else {
+ if (!r->underflow &&
+ !var_info->range.underflow &&
+ var_info->range.min < r->min) {
+ r->min = var_info->range.min;
+ }
+ if (!r->overflow &&
+ !var_info->range.overflow &&
+ var_info->range.max > r->max) {
+ r->max = var_info->range.max;
+ }
+ if (r->underflow) {
+ r->min = ZEND_LONG_MIN;
+ }
+ if (r->overflow) {
+ r->max = ZEND_LONG_MAX;
+ }
+ if (var_info->range.min == r->min &&
+ var_info->range.max == r->max &&
+ var_info->range.underflow == r->underflow &&
+ var_info->range.overflow == r->overflow) {
+ return 0;
+ }
+ }
+ var_info->range = *r;
+ return 1;
+}
+
+static int zend_ssa_range_narrowing(const zend_op_array *op_array, zend_ssa *ssa, int var, int scc)
+{
+ zend_ssa_range tmp;
+
+ if (zend_inference_calc_range(op_array, ssa, var, 0, 1, &tmp)) {
+ if (zend_inference_narrowing_meet(&ssa->var_info[var], &tmp)) {
+ LOG_SSA_RANGE(" change range (narrowing SCC %2d) %2d [%s%ld..%ld%s]\n", scc, var, (tmp.underflow?"-- ":""), tmp.min, tmp.max, (tmp.overflow?" ++":""));
+ return 1;
+ }
+ }
+ return 0;
+}
+
+#ifdef NEG_RANGE
+# define CHECK_INNER_CYCLE(var2) \
+ do { \
+ if (ssa->vars[var2].scc == ssa->vars[var].scc && \
+ !ssa->vars[var2].scc_entry && \
+ !zend_bitset_in(visited, var2) && \
+ zend_check_inner_cycles(op_array, ssa, worklist, visited, var2)) { \
+ return 1; \
+ } \
+ } while (0)
+
+static int zend_check_inner_cycles(const zend_op_array *op_array, zend_ssa *ssa, zend_bitset worklist, zend_bitset visited, int var)
+{
+ if (zend_bitset_in(worklist, var)) {
+ return 1;
+ }
+ zend_bitset_incl(worklist, var);
+ FOR_EACH_VAR_USAGE(var, CHECK_INNER_CYCLE);
+ zend_bitset_incl(visited, var);
+ return 0;
+}
+#endif
+
+static void zend_infer_ranges_warmup(const zend_op_array *op_array, zend_ssa *ssa, int *scc_var, int *next_scc_var, int scc)
+{
+ int worklist_len = zend_bitset_len(ssa->vars_count);
+ int j, n;
+ zend_ssa_range tmp;
+ ALLOCA_FLAG(use_heap)
+ zend_bitset worklist = do_alloca(sizeof(zend_ulong) * worklist_len * 2, use_heap);
+ zend_bitset visited = worklist + worklist_len;
+#ifdef NEG_RANGE
+ int has_inner_cycles = 0;
+
+ memset(worklist, 0, sizeof(zend_ulong) * worklist_len);
+ memset(visited, 0, sizeof(zend_ulong) * worklist_len);
+ j = scc_var[scc];
+ while (j >= 0) {
+ if (!zend_bitset_in(visited, j) &&
+ zend_check_inner_cycles(op_array, ssa, worklist, visited, j)) {
+ has_inner_cycles = 1;
+ break;
+ }
+ j = next_scc_var[j];
+ }
+#endif
+
+ memset(worklist, 0, sizeof(zend_ulong) * worklist_len);
+
+ for (n = 0; n < RANGE_WARMUP_PASSES; n++) {
+ j= scc_var[scc];
+ while (j >= 0) {
+ if (ssa->vars[j].scc_entry) {
+ zend_bitset_incl(worklist, j);
+ }
+ j = next_scc_var[j];
+ }
+
+ memset(visited, 0, sizeof(zend_ulong) * worklist_len);
+
+ WHILE_WORKLIST(worklist, worklist_len, j) {
+ if (zend_inference_calc_range(op_array, ssa, j, 0, 0, &tmp)) {
+#ifdef NEG_RANGE
+ if (!has_inner_cycles &&
+ ssa->var_info[j].has_range &&
+ ssa->vars[j].definition_phi &&
+ ssa->vars[j].definition_phi->pi >= 0 &&
+ ssa->vars[j].definition_phi->has_range_constraint &&
+ ssa->vars[j].definition_phi->constraint.range.negative &&
+ ssa->vars[j].definition_phi->constraint.range.min_ssa_var < 0 &&
+ ssa->vars[j].definition_phi->constraint.range.max_ssa_var < 0) {
+ zend_ssa_range_constraint *constraint =
+ &ssa->vars[j].definition_phi->constraint.range;
+ if (tmp.min == ssa->var_info[j].range.min &&
+ tmp.max == ssa->var_info[j].range.max) {
+ if (constraint->negative == NEG_INIT) {
+ LOG_NEG_RANGE("#%d INVARIANT\n", j);
+ constraint->negative = NEG_INVARIANT;
+ }
+ } else if (tmp.min == ssa->var_info[j].range.min &&
+ tmp.max == ssa->var_info[j].range.max + 1 &&
+ tmp.max < constraint->range.min) {
+ if (constraint->negative == NEG_INIT ||
+ constraint->negative == NEG_INVARIANT) {
+ LOG_NEG_RANGE("#%d LT\n", j);
+ constraint->negative = NEG_USE_LT;
+//???NEG
+ } else if (constraint->negative == NEG_USE_GT) {
+ LOG_NEG_RANGE("#%d UNKNOWN\n", j);
+ constraint->negative = NEG_UNKNOWN;
+ }
+ } else if (tmp.max == ssa->var_info[j].range.max &&
+ tmp.min == ssa->var_info[j].range.min - 1 &&
+ tmp.min > constraint->range.max) {
+ if (constraint->negative == NEG_INIT ||
+ constraint->negative == NEG_INVARIANT) {
+ LOG_NEG_RANGE("#%d GT\n", j);
+ constraint->negative = NEG_USE_GT;
+//???NEG
+ } else if (constraint->negative == NEG_USE_LT) {
+ LOG_NEG_RANGE("#%d UNKNOWN\n", j);
+ constraint->negative = NEG_UNKNOWN;
+ }
+ } else {
+ LOG_NEG_RANGE("#%d UNKNOWN\n", j);
+ constraint->negative = NEG_UNKNOWN;
+ }
+ }
+#endif
+ if (zend_inference_narrowing_meet(&ssa->var_info[j], &tmp)) {
+ LOG_SSA_RANGE(" change range (warmup %2d SCC %2d) %2d [%s%ld..%ld%s]\n", n, scc, j, (tmp.underflow?"-- ":""), tmp.min, tmp.max, (tmp.overflow?" ++":""));
+ zend_bitset_incl(visited, j);
+ FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR_1);
+ }
+ }
+ } WHILE_WORKLIST_END();
+ }
+ free_alloca(worklist, use_heap);
+}
+
+static int zend_infer_ranges(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
+{
+ int worklist_len = zend_bitset_len(ssa->vars_count);
+ zend_bitset worklist;
+ int *next_scc_var;
+ int *scc_var;
+ zend_ssa_phi *p;
+ zend_ssa_range tmp;
+ int scc, j;
+ ALLOCA_FLAG(use_heap);
+
+ worklist = do_alloca(
+ ZEND_MM_ALIGNED_SIZE(sizeof(zend_ulong) * worklist_len) +
+ ZEND_MM_ALIGNED_SIZE(sizeof(int) * ssa->vars_count) +
+ sizeof(int) * ssa->sccs, use_heap);
+ next_scc_var = (int*)((char*)worklist + ZEND_MM_ALIGNED_SIZE(sizeof(zend_ulong) * worklist_len));
+ scc_var = (int*)((char*)next_scc_var + ZEND_MM_ALIGNED_SIZE(sizeof(int) * ssa->vars_count));
+
+ LOG_SSA_RANGE("Range Inference\n");
+
+ /* Create linked lists of SSA variables for each SCC */
+ memset(scc_var, -1, sizeof(int) * ssa->sccs);
+ for (j = 0; j < ssa->vars_count; j++) {
+ if (ssa->vars[j].scc >= 0) {
+ next_scc_var[j] = scc_var[ssa->vars[j].scc];
+ scc_var[ssa->vars[j].scc] = j;
+ }
+ }
+
+ for (scc = 0; scc < ssa->sccs; scc++) {
+ j = scc_var[scc];
+ if (next_scc_var[j] < 0) {
+ /* SCC with a single element */
+ if (zend_inference_calc_range(op_array, ssa, j, 0, 1, &tmp)) {
+ zend_inference_init_range(op_array, ssa, j, tmp.underflow, tmp.min, tmp.max, tmp.overflow);
+ } else {
+ zend_inference_init_range(op_array, ssa, j, 1, ZEND_LONG_MIN, ZEND_LONG_MAX, 1);
+ }
+ } else {
+ /* Find SCC entry points */
+ memset(worklist, 0, sizeof(zend_ulong) * worklist_len);
+ do {
+ if (ssa->vars[j].scc_entry) {
+ zend_bitset_incl(worklist, j);
+ }
+ j = next_scc_var[j];
+ } while (j >= 0);
+
+#if RANGE_WARMUP_PASSES > 0
+ zend_infer_ranges_warmup(op_array, ssa, scc_var, next_scc_var, scc);
+ j = scc_var[scc];
+ do {
+ zend_bitset_incl(worklist, j);
+ j = next_scc_var[j];
+ } while (j >= 0);
+#endif
+
+ /* widening */
+ WHILE_WORKLIST(worklist, worklist_len, j) {
+ if (zend_ssa_range_widening(op_array, ssa, j, scc)) {
+ FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR);
+ }
+ } WHILE_WORKLIST_END();
+
+ /* initialize missing ranges */
+ for (j = scc_var[scc]; j >= 0; j = next_scc_var[j]) {
+ if (!ssa->var_info[j].has_range) {
+ zend_inference_init_range(op_array, ssa, j, 1, ZEND_LONG_MIN, ZEND_LONG_MAX, 1);
+ FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR);
+ }
+ }
+
+ /* widening (second round) */
+ WHILE_WORKLIST(worklist, worklist_len, j) {
+ if (zend_ssa_range_widening(op_array, ssa, j, scc)) {
+ FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR);
+ }
+ } WHILE_WORKLIST_END();
+
+ /* Add all SCC entry variables into worklist for narrowing */
+ for (j = scc_var[scc]; j >= 0; j = next_scc_var[j]) {
+ if (ssa->vars[j].definition_phi
+ && ssa->vars[j].definition_phi->pi < 0) {
+ /* narrowing Phi functions first */
+ zend_ssa_range_narrowing(op_array, ssa, j, scc);
+ }
+ zend_bitset_incl(worklist, j);
+ }
+
+ /* narrowing */
+ WHILE_WORKLIST(worklist, worklist_len, j) {
+ if (zend_ssa_range_narrowing(op_array, ssa, j, scc)) {
+ FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR);
+#ifdef SYM_RANGE
+ /* Process symbolic control-flow constraints */
+ p = ssa->vars[j].sym_use_chain;
+ while (p) {
+ ADD_SCC_VAR(p->ssa_var);
+ p = p->sym_use_chain;
+ }
+#endif
+ }
+ } WHILE_WORKLIST_END();
+ }
+ }
+
+ free_alloca(worklist, use_heap);
+
+ return SUCCESS;
+}
+/* }}} */
+
+static uint32_t get_ssa_alias_types(zend_ssa_alias_kind alias) {
+ if (alias == HTTP_RESPONSE_HEADER_ALIAS) {
+ return MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_RC1 | MAY_BE_RCN;
+ } else {
+ return MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ }
+}
+
+#define UPDATE_SSA_TYPE(_type, _var) \
+ do { \
+ uint32_t __type = (_type) & ~MAY_BE_GUARD; \
+ int __var = (_var); \
+ if (__type & MAY_BE_REF) { \
+ __type |= MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; \
+ } \
+ if (__var >= 0) { \
+ zend_ssa_var *__ssa_var = &ssa_vars[__var]; \
+ if (__ssa_var->var < op_array->last_var) { \
+ if (__type & (MAY_BE_REF|MAY_BE_RCN)) { \
+ __type |= MAY_BE_RC1 | MAY_BE_RCN; \
+ } \
+ if ((__type & MAY_BE_RC1) && (__type & MAY_BE_STRING)) {\
+ /* TODO: support for array keys and ($str . "")*/ \
+ __type |= MAY_BE_RCN; \
+ } \
+ if (__ssa_var->alias) { \
+ __type |= get_ssa_alias_types(__ssa_var->alias); \
+ } \
+ } \
+ if (ssa_var_info[__var].type != __type) { \
+ if (ssa_var_info[__var].type & ~__type) { \
+ emit_type_narrowing_warning(op_array, ssa, __var); \
+ return FAILURE; \
+ } \
+ ssa_var_info[__var].type = __type; \
+ if (update_worklist) { \
+ add_usages(op_array, ssa, worklist, __var); \
+ } \
+ } \
+ /*zend_bitset_excl(worklist, var);*/ \
+ } \
+ } while (0)
+
+#define UPDATE_SSA_OBJ_TYPE(_ce, _is_instanceof, var) \
+ do { \
+ if (var >= 0) { \
+ if (ssa_var_info[var].ce != (_ce) || \
+ ssa_var_info[var].is_instanceof != (_is_instanceof)) { \
+ ssa_var_info[var].ce = (_ce); \
+ ssa_var_info[var].is_instanceof = (_is_instanceof); \
+ if (update_worklist) { \
+ add_usages(op_array, ssa, worklist, var); \
+ } \
+ } \
+ /*zend_bitset_excl(worklist, var);*/ \
+ } \
+ } while (0)
+
+#define COPY_SSA_OBJ_TYPE(from_var, to_var) do { \
+ if ((from_var) >= 0 && (ssa_var_info[(from_var)].type & MAY_BE_OBJECT) \
+ && ssa_var_info[(from_var)].ce) { \
+ UPDATE_SSA_OBJ_TYPE(ssa_var_info[(from_var)].ce, \
+ ssa_var_info[(from_var)].is_instanceof, (to_var)); \
+ } else { \
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, (to_var)); \
+ } \
+} while (0)
+
+static void add_usages(const zend_op_array *op_array, zend_ssa *ssa, zend_bitset worklist, int var)
+{
+ if (ssa->vars[var].phi_use_chain) {
+ zend_ssa_phi *p = ssa->vars[var].phi_use_chain;
+ do {
+ zend_bitset_incl(worklist, p->ssa_var);
+ p = zend_ssa_next_use_phi(ssa, var, p);
+ } while (p);
+ }
+ if (ssa->vars[var].use_chain >= 0) {
+ int use = ssa->vars[var].use_chain;
+ zend_ssa_op *op;
+
+ do {
+ op = ssa->ops + use;
+ if (op->result_def >= 0) {
+ zend_bitset_incl(worklist, op->result_def);
+ }
+ if (op->op1_def >= 0) {
+ zend_bitset_incl(worklist, op->op1_def);
+ }
+ if (op->op2_def >= 0) {
+ zend_bitset_incl(worklist, op->op2_def);
+ }
+ if (op_array->opcodes[use].opcode == ZEND_OP_DATA) {
+ op--;
+ if (op->result_def >= 0) {
+ zend_bitset_incl(worklist, op->result_def);
+ }
+ if (op->op1_def >= 0) {
+ zend_bitset_incl(worklist, op->op1_def);
+ }
+ if (op->op2_def >= 0) {
+ zend_bitset_incl(worklist, op->op2_def);
+ }
+ }
+ use = zend_ssa_next_use(ssa->ops, var, use);
+ } while (use >= 0);
+ }
+}
+
+static void emit_type_narrowing_warning(const zend_op_array *op_array, zend_ssa *ssa, int var)
+{
+ int def_op_num = ssa->vars[var].definition;
+ const zend_op *def_opline = def_op_num >= 0 ? &op_array->opcodes[def_op_num] : NULL;
+ const char *def_op_name = def_opline ? zend_get_opcode_name(def_opline->opcode) : "PHI";
+ zend_error(E_WARNING, "Narrowing occurred during type inference of %s. Please file a bug report on bugs.php.net", def_op_name);
+}
+
+ZEND_API uint32_t zend_array_element_type(uint32_t t1, zend_uchar op_type, int write, int insert)
+{
+ uint32_t tmp = 0;
+
+ if (t1 & MAY_BE_OBJECT) {
+ if (!write) {
+ /* can't be REF because of ZVAL_COPY_DEREF() usage */
+ tmp |= MAY_BE_ANY | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ } else {
+ tmp |= MAY_BE_ANY | MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ }
+ if (write) {
+ tmp |= MAY_BE_INDIRECT;
+ }
+ }
+ if (t1 & MAY_BE_ARRAY) {
+ if (insert) {
+ tmp |= MAY_BE_NULL;
+ } else {
+ tmp |= MAY_BE_NULL | ((t1 & MAY_BE_ARRAY_OF_ANY) >> MAY_BE_ARRAY_SHIFT);
+ if (tmp & MAY_BE_ARRAY) {
+ tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ }
+ if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ if (!write) {
+ /* can't be REF because of ZVAL_COPY_DEREF() usage */
+ tmp |= MAY_BE_RCN;
+ if ((op_type & (IS_VAR|IS_TMP_VAR)) && (t1 & MAY_BE_RC1)) {
+ tmp |= MAY_BE_RC1;
+ }
+ } else if (t1 & MAY_BE_ARRAY_OF_REF) {
+ tmp |= MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN;
+ } else {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ }
+ }
+ if (write) {
+ tmp |= MAY_BE_INDIRECT;
+ }
+ }
+ if (t1 & MAY_BE_STRING) {
+ tmp |= MAY_BE_STRING | MAY_BE_RC1;
+ if (write) {
+ tmp |= MAY_BE_NULL;
+ }
+ }
+ if (t1 & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
+ tmp |= MAY_BE_NULL;
+ if (write) {
+ tmp |= MAY_BE_INDIRECT;
+ }
+ }
+ if (t1 & (MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_RESOURCE)) {
+ if (!write) {
+ tmp |= MAY_BE_NULL;
+ }
+ }
+ return tmp;
+}
+
+static uint32_t assign_dim_result_type(
+ uint32_t arr_type, uint32_t dim_type, uint32_t value_type, zend_uchar dim_op_type) {
+ uint32_t tmp = arr_type & ~(MAY_BE_RC1|MAY_BE_RCN);
+
+ if (arr_type & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
+ tmp &= ~(MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE);
+ tmp |= MAY_BE_ARRAY|MAY_BE_RC1;
+ }
+ if (tmp & (MAY_BE_ARRAY|MAY_BE_STRING)) {
+ tmp |= MAY_BE_RC1;
+ }
+ if (tmp & (MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ if (tmp & MAY_BE_ARRAY) {
+ /* Only add key type if we have a value type. We want to maintain the invariant that a
+ * key type exists iff a value type exists even in dead code that may use empty types. */
+ if (value_type & (MAY_BE_ANY|MAY_BE_UNDEF)) {
+ if (value_type & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_ARRAY_OF_NULL;
+ }
+ if (dim_op_type == IS_UNUSED) {
+ tmp |= MAY_BE_ARRAY_KEY_LONG;
+ } else {
+ if (dim_type & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) {
+ tmp |= MAY_BE_ARRAY_KEY_LONG;
+ }
+ if (dim_type & MAY_BE_STRING) {
+ tmp |= MAY_BE_ARRAY_KEY_STRING;
+ if (dim_op_type != IS_CONST) {
+ // FIXME: numeric string
+ tmp |= MAY_BE_ARRAY_KEY_LONG;
+ }
+ }
+ if (dim_type & (MAY_BE_UNDEF|MAY_BE_NULL)) {
+ tmp |= MAY_BE_ARRAY_KEY_STRING;
+ }
+ }
+ }
+ /* Only add value type if we have a key type. It might be that the key type is illegal
+ * for arrays. */
+ if (tmp & MAY_BE_ARRAY_KEY_ANY) {
+ tmp |= (value_type & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT;
+ }
+ }
+ return tmp;
+}
+
+/* For binary ops that have compound assignment operators */
+static uint32_t binary_op_result_type(
+ zend_ssa *ssa, zend_uchar opcode, uint32_t t1, uint32_t t2, int result_var,
+ zend_long optimization_level) {
+ uint32_t tmp = 0;
+ uint32_t t1_type = (t1 & MAY_BE_ANY) | (t1 & MAY_BE_UNDEF ? MAY_BE_NULL : 0);
+ uint32_t t2_type = (t2 & MAY_BE_ANY) | (t2 & MAY_BE_UNDEF ? MAY_BE_NULL : 0);
+
+ if (!(ZEND_OPTIMIZER_IGNORE_OVERLOADING & optimization_level)) {
+ /* Handle potentially overloaded operators.
+ * This could be made more precise by checking the class type, if known. */
+ if ((t1_type & MAY_BE_OBJECT) || (t2_type & MAY_BE_OBJECT)) {
+ /* This is somewhat GMP specific. */
+ tmp |= MAY_BE_OBJECT | MAY_BE_FALSE | MAY_BE_RC1;
+ }
+ }
+
+ switch (opcode) {
+ case ZEND_ADD:
+ if (t1_type == MAY_BE_LONG && t2_type == MAY_BE_LONG) {
+ if (result_var < 0 ||
+ !ssa->var_info[result_var].has_range ||
+ ssa->var_info[result_var].range.underflow ||
+ ssa->var_info[result_var].range.overflow) {
+ /* may overflow */
+ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
+ } else {
+ tmp |= MAY_BE_LONG;
+ }
+ } else if (t1_type == MAY_BE_DOUBLE || t2_type == MAY_BE_DOUBLE) {
+ tmp |= MAY_BE_DOUBLE;
+ } else if (t1_type == MAY_BE_ARRAY && t2_type == MAY_BE_ARRAY) {
+ tmp |= MAY_BE_ARRAY | MAY_BE_RC1;
+ tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
+ tmp |= t2 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
+ } else {
+ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
+ if ((t1_type & MAY_BE_ARRAY) && (t2_type & MAY_BE_ARRAY)) {
+ tmp |= MAY_BE_ARRAY | MAY_BE_RC1;
+ tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
+ tmp |= t2 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
+ }
+ }
+ break;
+ case ZEND_SUB:
+ case ZEND_MUL:
+ if (t1_type == MAY_BE_LONG && t2_type == MAY_BE_LONG) {
+ if (result_var < 0 ||
+ !ssa->var_info[result_var].has_range ||
+ ssa->var_info[result_var].range.underflow ||
+ ssa->var_info[result_var].range.overflow) {
+ /* may overflow */
+ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
+ } else {
+ tmp |= MAY_BE_LONG;
+ }
+ } else if (t1_type == MAY_BE_DOUBLE || t2_type == MAY_BE_DOUBLE) {
+ tmp |= MAY_BE_DOUBLE;
+ } else {
+ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
+ }
+ break;
+ case ZEND_DIV:
+ case ZEND_POW:
+ if (t1_type == MAY_BE_DOUBLE || t2_type == MAY_BE_DOUBLE) {
+ tmp |= MAY_BE_DOUBLE;
+ } else {
+ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
+ }
+ /* Division by zero results in Inf/-Inf/Nan (double), so it doesn't need any special
+ * handling */
+ break;
+ case ZEND_MOD:
+ tmp |= MAY_BE_LONG;
+ /* Division by zero results in an exception, so it doesn't need any special handling */
+ break;
+ case ZEND_BW_OR:
+ case ZEND_BW_AND:
+ case ZEND_BW_XOR:
+ if ((t1_type & MAY_BE_STRING) && (t2_type & MAY_BE_STRING)) {
+ tmp |= MAY_BE_STRING | MAY_BE_RC1;
+ }
+ if ((t1_type & ~MAY_BE_STRING) || (t2_type & ~MAY_BE_STRING)) {
+ tmp |= MAY_BE_LONG;
+ }
+ break;
+ case ZEND_SL:
+ case ZEND_SR:
+ tmp |= MAY_BE_LONG;
+ break;
+ case ZEND_CONCAT:
+ case ZEND_FAST_CONCAT:
+ /* TODO: +MAY_BE_OBJECT ??? */
+ tmp = MAY_BE_STRING | MAY_BE_RC1 | MAY_BE_RCN;
+ break;
+ EMPTY_SWITCH_DEFAULT_CASE()
+ }
+ return tmp;
+}
+
+static inline zend_class_entry *get_class_entry(const zend_script *script, zend_string *lcname) {
+ zend_class_entry *ce = script ? zend_hash_find_ptr(&script->class_table, lcname) : NULL;
+ if (ce) {
+ return ce;
+ }
+
+ ce = zend_hash_find_ptr(CG(class_table), lcname);
+ if (ce && ce->type == ZEND_INTERNAL_CLASS) {
+ return ce;
+ }
+
+ return NULL;
+}
+
+static uint32_t zend_convert_type_declaration_mask(uint32_t type_mask) {
+ uint32_t result_mask = type_mask & MAY_BE_ANY;
+ if (type_mask & MAY_BE_VOID) {
+ result_mask |= MAY_BE_NULL;
+ }
+ if (type_mask & MAY_BE_CALLABLE) {
+ result_mask |= MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ }
+ if (type_mask & MAY_BE_ITERABLE) {
+ result_mask |= MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ }
+ if (type_mask & MAY_BE_STATIC) {
+ result_mask |= MAY_BE_OBJECT;
+ }
+ if (type_mask & MAY_BE_ARRAY) {
+ result_mask |= MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ }
+ return result_mask;
+}
+
+ZEND_API uint32_t zend_fetch_arg_info_type(const zend_script *script, zend_arg_info *arg_info, zend_class_entry **pce)
+{
+ uint32_t tmp;
+
+ *pce = NULL;
+ if (!ZEND_TYPE_IS_SET(arg_info->type)) {
+ return MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF|MAY_BE_RC1|MAY_BE_RCN;
+ }
+
+ tmp = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(arg_info->type));
+ if (ZEND_TYPE_HAS_CLASS(arg_info->type)) {
+ tmp |= MAY_BE_OBJECT;
+ /* As we only have space to store one CE, we use a plain object type for class unions. */
+ if (ZEND_TYPE_HAS_NAME(arg_info->type)) {
+ zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(arg_info->type));
+ *pce = get_class_entry(script, lcname);
+ zend_string_release_ex(lcname, 0);
+ }
+ }
+ if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ return tmp;
+}
+
+static zend_property_info *lookup_prop_info(zend_class_entry *ce, zend_string *name, zend_class_entry *scope) {
+ zend_property_info *prop_info;
+
+ /* If the class is linked, reuse the precise runtime logic. */
+ if ((ce->ce_flags & ZEND_ACC_LINKED)
+ && (!scope || (scope->ce_flags & ZEND_ACC_LINKED))) {
+ zend_class_entry *prev_scope = EG(fake_scope);
+ EG(fake_scope) = scope;
+ prop_info = zend_get_property_info(ce, name, 1);
+ EG(fake_scope) = prev_scope;
+ if (prop_info && prop_info != ZEND_WRONG_PROPERTY_INFO) {
+ return prop_info;
+ }
+ return NULL;
+ }
+
+ /* Otherwise, handle only some safe cases */
+ prop_info = zend_hash_find_ptr(&ce->properties_info, name);
+ if (prop_info &&
+ ((prop_info->ce == scope) ||
+ (!scope && (prop_info->flags & ZEND_ACC_PUBLIC)))
+ ) {
+ return prop_info;
+ }
+ return NULL;
+}
+
+static zend_property_info *zend_fetch_prop_info(const zend_op_array *op_array, zend_ssa *ssa, zend_op *opline, zend_ssa_op *ssa_op)
+{
+ zend_property_info *prop_info = NULL;
+ if (opline->op2_type == IS_CONST) {
+ zend_class_entry *ce = NULL;
+
+ if (opline->op1_type == IS_UNUSED) {
+ ce = op_array->scope;
+ } else if (ssa_op->op1_use >= 0) {
+ ce = ssa->var_info[ssa_op->op1_use].ce;
+ }
+ if (ce) {
+ prop_info = lookup_prop_info(ce,
+ Z_STR_P(CRT_CONSTANT(opline->op2)),
+ op_array->scope);
+ if (prop_info && (prop_info->flags & ZEND_ACC_STATIC)) {
+ prop_info = NULL;
+ }
+ }
+ }
+ return prop_info;
+}
+
+static zend_property_info *zend_fetch_static_prop_info(const zend_script *script, const zend_op_array *op_array, zend_ssa *ssa, zend_op *opline)
+{
+ zend_property_info *prop_info = NULL;
+ if (opline->op1_type == IS_CONST) {
+ zend_class_entry *ce = NULL;
+ if (opline->op2_type == IS_UNUSED) {
+ int fetch_type = opline->op2.num & ZEND_FETCH_CLASS_MASK;
+ switch (fetch_type) {
+ case ZEND_FETCH_CLASS_SELF:
+ case ZEND_FETCH_CLASS_STATIC:
+ /* We enforce that static property types cannot change during inheritance, so
+ * handling static the same way as self here is legal. */
+ ce = op_array->scope;
+ break;
+ case ZEND_FETCH_CLASS_PARENT:
+ if (op_array->scope && (op_array->scope->ce_flags & ZEND_ACC_LINKED)) {
+ ce = op_array->scope->parent;
+ }
+ break;
+ }
+ } else if (opline->op2_type == IS_CONST) {
+ zval *zv = CRT_CONSTANT(opline->op2);
+ ce = get_class_entry(script, Z_STR_P(zv + 1));
+ }
+
+ if (ce) {
+ zval *zv = CRT_CONSTANT(opline->op1);
+ prop_info = lookup_prop_info(ce, Z_STR_P(zv), op_array->scope);
+ if (prop_info && !(prop_info->flags & ZEND_ACC_STATIC)) {
+ prop_info = NULL;
+ }
+ }
+ }
+ return prop_info;
+}
+
+static uint32_t zend_fetch_prop_type(const zend_script *script, zend_property_info *prop_info, zend_class_entry **pce)
+{
+ if (pce) {
+ *pce = NULL;
+ }
+ if (prop_info && ZEND_TYPE_IS_SET(prop_info->type)) {
+ uint32_t type = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(prop_info->type));
+
+ if (type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ type |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ if (ZEND_TYPE_HAS_CLASS(prop_info->type)) {
+ type |= MAY_BE_OBJECT;
+ if (pce) {
+ if (ZEND_TYPE_HAS_CE(prop_info->type)) {
+ *pce = ZEND_TYPE_CE(prop_info->type);
+ } else if (ZEND_TYPE_HAS_NAME(prop_info->type)) {
+ zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(prop_info->type));
+ *pce = get_class_entry(script, lcname);
+ zend_string_release(lcname);
+ }
+ }
+ }
+ return type;
+ }
+ return MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_RC1 | MAY_BE_RCN;
+}
+
+static zend_always_inline int _zend_update_type_info(
+ const zend_op_array *op_array,
+ zend_ssa *ssa,
+ const zend_script *script,
+ zend_bitset worklist,
+ zend_op *opline,
+ zend_ssa_op *ssa_op,
+ const zend_op **ssa_opcodes,
+ zend_long optimization_level,
+ bool update_worklist)
+{
+ uint32_t t1, t2;
+ uint32_t tmp, orig;
+ zend_ssa_var *ssa_vars = ssa->vars;
+ zend_ssa_var_info *ssa_var_info = ssa->var_info;
+ zend_class_entry *ce;
+ int j;
+
+ if (opline->opcode == ZEND_OP_DATA) {
+ opline--;
+ ssa_op--;
+ }
+
+ t1 = OP1_INFO();
+ t2 = OP2_INFO();
+
+ /* If one of the operands cannot have any type, this means the operand derives from
+ * unreachable code. Propagate the empty result early, so that that the following
+ * code may assume that operands have at least one type. */
+ if (!(t1 & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_CLASS))
+ || !(t2 & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_CLASS))) {
+ tmp = 0;
+ if (ssa_op->result_def >= 0) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ if (ssa_op->op1_def >= 0) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ if (ssa_op->op2_def >= 0) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
+ }
+ return 1;
+ }
+
+ switch (opline->opcode) {
+ case ZEND_ADD:
+ case ZEND_SUB:
+ case ZEND_MUL:
+ case ZEND_DIV:
+ case ZEND_POW:
+ case ZEND_MOD:
+ case ZEND_BW_OR:
+ case ZEND_BW_AND:
+ case ZEND_BW_XOR:
+ case ZEND_SL:
+ case ZEND_SR:
+ case ZEND_CONCAT:
+ tmp = binary_op_result_type(ssa, opline->opcode, t1, t2, ssa_op->result_def, optimization_level);
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ break;
+ case ZEND_BW_NOT:
+ tmp = 0;
+ if (t1 & MAY_BE_STRING) {
+ tmp |= MAY_BE_STRING | MAY_BE_RC1;
+ }
+ if (t1 & (MAY_BE_ANY-MAY_BE_STRING)) {
+ tmp |= MAY_BE_LONG;
+ }
+ if (!(ZEND_OPTIMIZER_IGNORE_OVERLOADING & optimization_level)) {
+ if (t1 & MAY_BE_OBJECT) {
+ /* Potentially overloaded operator. */
+ tmp |= MAY_BE_OBJECT | MAY_BE_RC1;
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ break;
+ case ZEND_BEGIN_SILENCE:
+ UPDATE_SSA_TYPE(MAY_BE_LONG, ssa_op->result_def);
+ break;
+ case ZEND_BOOL_NOT:
+ case ZEND_BOOL_XOR:
+ case ZEND_IS_IDENTICAL:
+ case ZEND_IS_NOT_IDENTICAL:
+ case ZEND_IS_EQUAL:
+ case ZEND_IS_NOT_EQUAL:
+ case ZEND_IS_SMALLER:
+ case ZEND_IS_SMALLER_OR_EQUAL:
+ case ZEND_INSTANCEOF:
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ case ZEND_CASE:
+ case ZEND_CASE_STRICT:
+ case ZEND_BOOL:
+ case ZEND_ISSET_ISEMPTY_CV:
+ case ZEND_ISSET_ISEMPTY_VAR:
+ case ZEND_ISSET_ISEMPTY_DIM_OBJ:
+ case ZEND_ISSET_ISEMPTY_PROP_OBJ:
+ case ZEND_ISSET_ISEMPTY_STATIC_PROP:
+ case ZEND_ASSERT_CHECK:
+ case ZEND_IN_ARRAY:
+ case ZEND_ARRAY_KEY_EXISTS:
+ UPDATE_SSA_TYPE(MAY_BE_FALSE|MAY_BE_TRUE, ssa_op->result_def);
+ break;
+ case ZEND_CAST:
+ if (ssa_op->op1_def >= 0) {
+ tmp = t1;
+ if ((t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) &&
+ (opline->extended_value == IS_ARRAY ||
+ opline->extended_value == IS_OBJECT)) {
+ tmp |= MAY_BE_RCN;
+ } else if ((t1 & MAY_BE_STRING) &&
+ opline->extended_value == IS_STRING) {
+ tmp |= MAY_BE_RCN;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ tmp = 1 << opline->extended_value;
+ if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ if ((tmp & MAY_BE_ANY) == (t1 & MAY_BE_ANY)) {
+ tmp |= (t1 & MAY_BE_RC1) | MAY_BE_RCN;
+ } else if ((opline->extended_value == IS_ARRAY ||
+ opline->extended_value == IS_OBJECT) &&
+ (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT))) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ } else if (opline->extended_value == IS_STRING &&
+ (t1 & (MAY_BE_STRING|MAY_BE_OBJECT))) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ } else {
+ tmp |= MAY_BE_RC1;
+ }
+ }
+ if (opline->extended_value == IS_ARRAY) {
+ if (t1 & MAY_BE_ARRAY) {
+ tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF);
+ }
+ if (t1 & MAY_BE_OBJECT) {
+ tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ } else {
+ tmp |= ((t1 & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT) | ((t1 & MAY_BE_ANY) ? MAY_BE_ARRAY_PACKED : 0);
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ break;
+ case ZEND_QM_ASSIGN:
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ case ZEND_COPY_TMP:
+ if (ssa_op->op1_def >= 0) {
+ tmp = t1;
+ if (t1 & (MAY_BE_RC1|MAY_BE_REF)) {
+ tmp |= MAY_BE_RCN;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ tmp = t1 & ~(MAY_BE_UNDEF|MAY_BE_REF);
+ if (t1 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
+ tmp |= (t1 & (MAY_BE_RC1|MAY_BE_RCN));
+ if (opline->op1_type == IS_CV) {
+ tmp |= MAY_BE_RCN;
+ }
+ }
+ if (opline->opcode != ZEND_QM_ASSIGN) {
+ /* COALESCE and JMP_SET result can't be null */
+ tmp &= ~MAY_BE_NULL;
+ if (opline->opcode == ZEND_JMP_SET) {
+ /* JMP_SET result can't be false either */
+ tmp &= ~MAY_BE_FALSE;
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->result_def);
+ break;
+ case ZEND_JMP_NULL:
+ if (opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_EXPR) {
+ tmp = MAY_BE_NULL;
+ } else if (opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_ISSET) {
+ tmp = MAY_BE_FALSE;
+ } else {
+ ZEND_ASSERT(opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_EMPTY);
+ tmp = MAY_BE_TRUE;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ break;
+ case ZEND_ASSIGN_OP:
+ case ZEND_ASSIGN_DIM_OP:
+ case ZEND_ASSIGN_OBJ_OP:
+ case ZEND_ASSIGN_STATIC_PROP_OP:
+ {
+ zend_property_info *prop_info = NULL;
+ orig = 0;
+ tmp = 0;
+ if (opline->opcode == ZEND_ASSIGN_OBJ_OP) {
+ prop_info = zend_fetch_prop_info(op_array, ssa, opline, ssa_op);
+ orig = t1;
+ t1 = zend_fetch_prop_type(script, prop_info, &ce);
+ t2 = OP1_DATA_INFO();
+ } else if (opline->opcode == ZEND_ASSIGN_DIM_OP) {
+ if (t1 & MAY_BE_ARRAY_OF_REF) {
+ tmp |= MAY_BE_REF;
+ }
+ orig = t1;
+ t1 = zend_array_element_type(t1, opline->op1_type, 1, 0);
+ t2 = OP1_DATA_INFO();
+ } else if (opline->opcode == ZEND_ASSIGN_STATIC_PROP_OP) {
+ prop_info = zend_fetch_static_prop_info(script, op_array, ssa, opline);
+ t1 = zend_fetch_prop_type(script, prop_info, &ce);
+ t2 = OP1_DATA_INFO();
+ } else {
+ if (t1 & MAY_BE_REF) {
+ tmp |= MAY_BE_REF;
+ }
+ }
+
+ tmp |= binary_op_result_type(
+ ssa, opline->extended_value, t1, t2,
+ opline->opcode == ZEND_ASSIGN_OP ? ssa_op->op1_def : -1, optimization_level);
+ if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY)) {
+ tmp |= MAY_BE_RC1;
+ }
+ if (tmp & (MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+
+ if (opline->opcode == ZEND_ASSIGN_DIM_OP) {
+ if (opline->op1_type == IS_CV) {
+ orig = assign_dim_result_type(orig, OP2_INFO(), tmp, opline->op2_type);
+ UPDATE_SSA_TYPE(orig, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ } else if (opline->opcode == ZEND_ASSIGN_OBJ_OP) {
+ if (opline->op1_type == IS_CV) {
+ orig = (orig & (MAY_BE_REF|MAY_BE_OBJECT))|MAY_BE_RC1|MAY_BE_RCN;
+ UPDATE_SSA_TYPE(orig, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ } else if (opline->opcode == ZEND_ASSIGN_STATIC_PROP) {
+ /* Nothing to do */
+ } else {
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ if (ssa_op->result_def >= 0) {
+ ce = NULL;
+ if (opline->opcode == ZEND_ASSIGN_DIM_OP) {
+ if (opline->op2_type == IS_UNUSED) {
+ /* When appending to an array and the LONG_MAX key is already used
+ * null will be returned. */
+ tmp |= MAY_BE_NULL;
+ }
+ if (t2 & (MAY_BE_ARRAY | MAY_BE_OBJECT)) {
+ /* Arrays and objects cannot be used as keys. */
+ tmp |= MAY_BE_NULL;
+ }
+ if (t1 & (MAY_BE_ANY - (MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY))) {
+ /* null and false are implicitly converted to array, anything else
+ * results in a null return value. */
+ tmp |= MAY_BE_NULL;
+ }
+ } else if (opline->opcode == ZEND_ASSIGN_OBJ_OP) {
+ /* The return value must also satisfy the property type */
+ if (prop_info) {
+ tmp &= zend_fetch_prop_type(script, prop_info, NULL);
+ }
+ } else if (opline->opcode == ZEND_ASSIGN_STATIC_PROP_OP) {
+ /* The return value must also satisfy the property type */
+ if (prop_info) {
+ tmp &= zend_fetch_prop_type(script, prop_info, NULL);
+ }
+ }
+ tmp &= ~MAY_BE_REF;
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
+ }
+ }
+ break;
+ }
+ case ZEND_PRE_INC:
+ case ZEND_PRE_DEC:
+ tmp = 0;
+ if (t1 & MAY_BE_REF) {
+ tmp |= MAY_BE_REF;
+ }
+ if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
+ tmp |= MAY_BE_RC1;
+ if (ssa_op->result_def >= 0) {
+ tmp |= MAY_BE_RCN;
+ }
+ }
+ if ((t1 & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG) {
+ if (!ssa_var_info[ssa_op->op1_use].has_range ||
+ (opline->opcode == ZEND_PRE_DEC &&
+ (ssa_var_info[ssa_op->op1_use].range.underflow ||
+ ssa_var_info[ssa_op->op1_use].range.min == ZEND_LONG_MIN)) ||
+ (opline->opcode == ZEND_PRE_INC &&
+ (ssa_var_info[ssa_op->op1_use].range.overflow ||
+ ssa_var_info[ssa_op->op1_use].range.max == ZEND_LONG_MAX))) {
+ /* may overflow */
+ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
+ } else {
+ tmp |= MAY_BE_LONG;
+ }
+ } else {
+ if (t1 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
+ if (opline->opcode == ZEND_PRE_INC) {
+ tmp |= MAY_BE_LONG;
+ } else {
+ tmp |= MAY_BE_NULL;
+ }
+ }
+ if (t1 & MAY_BE_LONG) {
+ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
+ }
+ if (t1 & MAY_BE_DOUBLE) {
+ tmp |= MAY_BE_DOUBLE;
+ }
+ if (t1 & MAY_BE_STRING) {
+ tmp |= MAY_BE_STRING | MAY_BE_LONG | MAY_BE_DOUBLE;
+ }
+ tmp |= t1 & (MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_RESOURCE | MAY_BE_ARRAY | MAY_BE_OBJECT | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_KEY_ANY);
+ }
+ if (ssa_op->op1_def >= 0) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ if (ssa_op->result_def >= 0) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ break;
+ case ZEND_POST_INC:
+ case ZEND_POST_DEC:
+ if (ssa_op->result_def >= 0) {
+ tmp = 0;
+ if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
+ tmp |= MAY_BE_RC1|MAY_BE_RCN;
+ }
+ tmp |= t1 & ~(MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_RCN);
+ if (t1 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ tmp = 0;
+ if (t1 & MAY_BE_REF) {
+ tmp |= MAY_BE_REF;
+ }
+ if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
+ tmp |= MAY_BE_RC1;
+ }
+ if ((t1 & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG) {
+ if (!ssa_var_info[ssa_op->op1_use].has_range ||
+ (opline->opcode == ZEND_POST_DEC &&
+ (ssa_var_info[ssa_op->op1_use].range.underflow ||
+ ssa_var_info[ssa_op->op1_use].range.min == ZEND_LONG_MIN)) ||
+ (opline->opcode == ZEND_POST_INC &&
+ (ssa_var_info[ssa_op->op1_use].range.overflow ||
+ ssa_var_info[ssa_op->op1_use].range.max == ZEND_LONG_MAX))) {
+ /* may overflow */
+ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
+ } else {
+ tmp |= MAY_BE_LONG;
+ }
+ } else {
+ if (t1 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
+ if (opline->opcode == ZEND_POST_INC) {
+ tmp |= MAY_BE_LONG;
+ } else {
+ tmp |= MAY_BE_NULL;
+ }
+ }
+ if (t1 & MAY_BE_LONG) {
+ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
+ }
+ if (t1 & MAY_BE_DOUBLE) {
+ tmp |= MAY_BE_DOUBLE;
+ }
+ if (t1 & MAY_BE_STRING) {
+ tmp |= MAY_BE_STRING | MAY_BE_LONG | MAY_BE_DOUBLE;
+ }
+ tmp |= t1 & (MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_RESOURCE | MAY_BE_ARRAY | MAY_BE_OBJECT | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_KEY_ANY);
+ }
+ if (ssa_op->op1_def >= 0) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_ASSIGN_DIM:
+ if (opline->op1_type == IS_CV) {
+ tmp = assign_dim_result_type(t1, t2, OP1_DATA_INFO(), opline->op2_type);
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ if (ssa_op->result_def >= 0) {
+ tmp = 0;
+ if (t1 & MAY_BE_STRING) {
+ tmp |= MAY_BE_STRING;
+ }
+ if (t1 & ((MAY_BE_ANY|MAY_BE_UNDEF) - MAY_BE_STRING)) {
+ tmp |= (OP1_DATA_INFO() & (MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF));
+
+ if (opline->op2_type == IS_UNUSED) {
+ /* When appending to an array and the LONG_MAX key is already used
+ * null will be returned. */
+ tmp |= MAY_BE_NULL;
+ }
+ if (t2 & (MAY_BE_ARRAY | MAY_BE_OBJECT)) {
+ /* Arrays and objects cannot be used as keys. */
+ tmp |= MAY_BE_NULL;
+ }
+ if (t1 & (MAY_BE_ANY - (MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY))) {
+ /* undef, null and false are implicitly converted to array, anything else
+ * results in a null return value. */
+ tmp |= MAY_BE_NULL;
+ }
+ }
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ if (t1 & MAY_BE_OBJECT) {
+ tmp |= MAY_BE_REF;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ if ((ssa_op+1)->op1_def >= 0) {
+ opline++;
+ ssa_op++;
+ tmp = OP1_INFO();
+ if (tmp & (MAY_BE_ANY | MAY_BE_REF)) {
+ if (tmp & MAY_BE_RC1) {
+ tmp |= MAY_BE_RCN;
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_ASSIGN_OBJ:
+ if (opline->op1_type == IS_CV) {
+ tmp = (t1 & (MAY_BE_REF|MAY_BE_OBJECT))|MAY_BE_RC1|MAY_BE_RCN;
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ if (ssa_op->result_def >= 0) {
+ // TODO: If there is no __set we might do better
+ tmp = zend_fetch_prop_type(script,
+ zend_fetch_prop_info(op_array, ssa, opline, ssa_op), &ce);
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
+ }
+ }
+ if ((ssa_op+1)->op1_def >= 0) {
+ opline++;
+ ssa_op++;
+ tmp = OP1_INFO();
+ if (tmp & MAY_BE_RC1) {
+ tmp |= MAY_BE_RCN;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_ASSIGN_STATIC_PROP:
+ if (ssa_op->result_def >= 0) {
+ tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_RC1 | MAY_BE_RCN;
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ if ((ssa_op+1)->op1_def >= 0) {
+ opline++;
+ ssa_op++;
+ tmp = OP1_INFO();
+ if (tmp & MAY_BE_RC1) {
+ tmp |= MAY_BE_RCN;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_PRE_INC_OBJ:
+ case ZEND_PRE_DEC_OBJ:
+ case ZEND_POST_INC_OBJ:
+ case ZEND_POST_DEC_OBJ:
+ if (opline->op1_type == IS_CV) {
+ tmp = (t1 & (MAY_BE_REF|MAY_BE_OBJECT))|MAY_BE_RC1|MAY_BE_RCN;
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ if (ssa_op->result_def >= 0) {
+ // TODO: ???
+ tmp = MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ break;
+ case ZEND_ASSIGN:
+ if (ssa_op->op2_def >= 0) {
+ tmp = t2;
+ if (tmp & MAY_BE_RC1) {
+ tmp |= MAY_BE_RCN;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
+ }
+ tmp = t2 & ~(MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN);
+ if (t2 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ if (t1 & MAY_BE_REF) {
+ tmp |= MAY_BE_REF;
+ }
+ if (t2 & MAY_BE_REF) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ } else if (opline->op2_type & (IS_TMP_VAR|IS_VAR)) {
+ tmp |= t2 & (MAY_BE_RC1|MAY_BE_RCN);
+ } else if (t2 & (MAY_BE_RC1|MAY_BE_RCN)) {
+ tmp |= MAY_BE_RCN;
+ }
+ if (RETURN_VALUE_USED(opline) && (tmp & MAY_BE_RC1)) {
+ tmp |= MAY_BE_RCN;
+ }
+ if (ssa_op->op1_def >= 0) {
+ if (ssa_var_info[ssa_op->op1_def].use_as_double) {
+ tmp &= ~MAY_BE_LONG;
+ tmp |= MAY_BE_DOUBLE;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op2_use, ssa_op->op1_def);
+ }
+ if (ssa_op->result_def >= 0) {
+ UPDATE_SSA_TYPE(tmp & ~MAY_BE_REF, ssa_op->result_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op2_use, ssa_op->result_def);
+ }
+ break;
+ case ZEND_ASSIGN_REF:
+// TODO: ???
+ if (opline->op2_type == IS_CV) {
+ tmp = (MAY_BE_REF | t2) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN);
+ if (t2 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
+ }
+ if (opline->op2_type == IS_VAR && opline->extended_value == ZEND_RETURNS_FUNCTION) {
+ tmp = (MAY_BE_REF | MAY_BE_RCN | MAY_BE_RC1 | t2) & ~MAY_BE_UNDEF;
+ } else {
+ tmp = (MAY_BE_REF | t2) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN);
+ }
+ if (t2 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ if (ssa_op->result_def >= 0) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ break;
+ case ZEND_ASSIGN_OBJ_REF:
+ if (opline->op1_type == IS_CV) {
+ tmp = t1;
+ if (tmp & MAY_BE_OBJECT) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+
+ t2 = OP1_DATA_INFO();
+ if ((opline+1)->op1_type == IS_VAR && (opline->extended_value & ZEND_RETURNS_FUNCTION)) {
+ tmp = (MAY_BE_REF | MAY_BE_RCN | MAY_BE_RC1 | t2) & ~MAY_BE_UNDEF;
+ } else {
+ tmp = (MAY_BE_REF | t2) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN);
+ }
+ if (t2 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ if (ssa_op->result_def >= 0) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ if ((opline+1)->op1_type == IS_CV) {
+ opline++;
+ ssa_op++;
+ tmp = (MAY_BE_REF | t2) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN);
+ if (t2 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_ASSIGN_STATIC_PROP_REF:
+ if ((opline+1)->op1_type == IS_CV) {
+ opline++;
+ ssa_op++;
+ UPDATE_SSA_TYPE(MAY_BE_REF, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_BIND_GLOBAL:
+ tmp = MAY_BE_REF | MAY_BE_ANY
+ | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ break;
+ case ZEND_BIND_STATIC:
+ tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF
+ | ((opline->extended_value & ZEND_BIND_REF) ? MAY_BE_REF : (MAY_BE_RC1 | MAY_BE_RCN));
+ if (opline->extended_value & ZEND_BIND_IMPLICIT) {
+ tmp |= MAY_BE_UNDEF;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ break;
+ case ZEND_SEND_VAR:
+ if (ssa_op->op1_def >= 0) {
+ tmp = t1;
+ if (t1 & (MAY_BE_RC1|MAY_BE_REF)) {
+ tmp |= MAY_BE_RCN;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_BIND_LEXICAL:
+ if (ssa_op->op2_def >= 0) {
+ if (opline->extended_value & ZEND_BIND_REF) {
+ tmp = t2 | MAY_BE_REF;
+ } else {
+ tmp = t2 & ~(MAY_BE_RC1|MAY_BE_RCN);
+ if (t2 & (MAY_BE_RC1|MAY_BE_RCN)) {
+ tmp |= MAY_BE_RCN;
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op2_use, ssa_op->op2_def);
+ }
+ break;
+ case ZEND_YIELD:
+ if (ssa_op->op1_def >= 0) {
+ if (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) {
+ tmp = t1 | MAY_BE_REF;
+ } else {
+ tmp = t1 & ~(MAY_BE_RC1|MAY_BE_RCN);
+ if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
+ tmp |= MAY_BE_RCN;
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ if (ssa_op->result_def >= 0) {
+ tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF
+ | MAY_BE_RC1 | MAY_BE_RCN;
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ break;
+ case ZEND_SEND_VAR_EX:
+ case ZEND_SEND_FUNC_ARG:
+ if (ssa_op->op1_def >= 0) {
+ tmp = (t1 & MAY_BE_UNDEF)|MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_SEND_REF:
+ if (ssa_op->op1_def >= 0) {
+ tmp = MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_SEND_UNPACK:
+ if (ssa_op->op1_def >= 0) {
+ tmp = t1;
+ if (t1 & MAY_BE_ARRAY) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ if (t1 & MAY_BE_ARRAY_OF_ANY) {
+ /* SEND_UNPACK may acquire references into the array */
+ tmp |= MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ }
+ }
+ if (t1 & MAY_BE_OBJECT) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_FAST_CONCAT:
+ case ZEND_ROPE_INIT:
+ case ZEND_ROPE_ADD:
+ case ZEND_ROPE_END:
+ UPDATE_SSA_TYPE(MAY_BE_STRING|MAY_BE_RC1|MAY_BE_RCN, ssa_op->result_def);
+ break;
+ case ZEND_RECV:
+ case ZEND_RECV_INIT:
+ case ZEND_RECV_VARIADIC:
+ {
+ /* Typehinting */
+ zend_arg_info *arg_info = &op_array->arg_info[opline->op1.num-1];
+
+ ce = NULL;
+ tmp = zend_fetch_arg_info_type(script, arg_info, &ce);
+ if (ZEND_ARG_SEND_MODE(arg_info)) {
+ tmp |= MAY_BE_REF;
+ }
+
+ if (opline->opcode == ZEND_RECV_VARIADIC) {
+ uint32_t elem_type = tmp & MAY_BE_REF
+ ? MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF
+ : (tmp & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT;
+ tmp = MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|elem_type;
+ ce = NULL;
+ }
+
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
+ } else {
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
+ }
+ break;
+ }
+ case ZEND_DECLARE_ANON_CLASS:
+ UPDATE_SSA_TYPE(MAY_BE_CLASS, ssa_op->result_def);
+ if (script && (ce = zend_hash_find_ptr(&script->class_table, Z_STR_P(CRT_CONSTANT(opline->op1)))) != NULL) {
+ UPDATE_SSA_OBJ_TYPE(ce, 0, ssa_op->result_def);
+ }
+ break;
+ case ZEND_FETCH_CLASS:
+ UPDATE_SSA_TYPE(MAY_BE_CLASS, ssa_op->result_def);
+ if (opline->op2_type == IS_UNUSED) {
+ switch (opline->op1.num & ZEND_FETCH_CLASS_MASK) {
+ case ZEND_FETCH_CLASS_SELF:
+ if (op_array->scope) {
+ UPDATE_SSA_OBJ_TYPE(op_array->scope, 0, ssa_op->result_def);
+ } else {
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
+ }
+ break;
+ case ZEND_FETCH_CLASS_PARENT:
+ if (op_array->scope && op_array->scope->parent && (op_array->scope->ce_flags & ZEND_ACC_LINKED)) {
+ UPDATE_SSA_OBJ_TYPE(op_array->scope->parent, 0, ssa_op->result_def);
+ } else {
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
+ }
+ break;
+ case ZEND_FETCH_CLASS_STATIC:
+ default:
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
+ break;
+ }
+ } else if (opline->op2_type == IS_CONST) {
+ zval *zv = CRT_CONSTANT(opline->op2);
+ if (Z_TYPE_P(zv) == IS_STRING) {
+ ce = get_class_entry(script, Z_STR_P(zv+1));
+ UPDATE_SSA_OBJ_TYPE(ce, 0, ssa_op->result_def);
+ } else {
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
+ }
+ } else {
+ COPY_SSA_OBJ_TYPE(ssa_op->op2_use, ssa_op->result_def);
+ }
+ break;
+ case ZEND_NEW:
+ tmp = MAY_BE_RC1|MAY_BE_RCN|MAY_BE_OBJECT;
+ if (opline->op1_type == IS_CONST &&
+ (ce = get_class_entry(script, Z_STR_P(CRT_CONSTANT(opline->op1)+1))) != NULL) {
+ UPDATE_SSA_OBJ_TYPE(ce, 0, ssa_op->result_def);
+ } else if ((t1 & MAY_BE_CLASS) && ssa_op->op1_use >= 0 && ssa_var_info[ssa_op->op1_use].ce) {
+ UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_op->op1_use].ce, ssa_var_info[ssa_op->op1_use].is_instanceof, ssa_op->result_def);
+ } else {
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ break;
+ case ZEND_CLONE:
+ UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_OBJECT, ssa_op->result_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->result_def);
+ break;
+ case ZEND_INIT_ARRAY:
+ case ZEND_ADD_ARRAY_ELEMENT:
+ if (ssa_op->op1_def >= 0) {
+ if (opline->extended_value & ZEND_ARRAY_ELEMENT_REF) {
+ tmp = (MAY_BE_REF | t1) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN);
+ if (t1 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ } else if ((t1 & (MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN)) == MAY_BE_REF) {
+ tmp = (MAY_BE_REF | t1) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN);
+ if (t1 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ } else if (t1 & MAY_BE_REF) {
+ tmp = (MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | t1);
+ } else {
+ tmp = t1;
+ if (t1 & MAY_BE_RC1) {
+ tmp |= MAY_BE_RCN;
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ if (ssa_op->result_def >= 0) {
+ tmp = MAY_BE_RC1|MAY_BE_ARRAY;
+ if (ssa_op->result_use >= 0) {
+ tmp |= ssa_var_info[ssa_op->result_use].type;
+ }
+ if (opline->op1_type != IS_UNUSED) {
+ tmp |= (t1 & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT;
+ if (t1 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_ARRAY_OF_NULL;
+ }
+ if (opline->extended_value & ZEND_ARRAY_ELEMENT_REF) {
+ tmp |= MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ }
+ if (opline->op2_type == IS_UNUSED) {
+ tmp |= MAY_BE_ARRAY_KEY_LONG;
+ } else {
+ if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_DOUBLE|MAY_BE_RESOURCE)) {
+ tmp |= MAY_BE_ARRAY_KEY_LONG;
+ }
+ if (t2 & (MAY_BE_STRING)) {
+ tmp |= MAY_BE_ARRAY_KEY_STRING;
+ if (opline->op2_type != IS_CONST) {
+ // FIXME: numeric string
+ tmp |= MAY_BE_ARRAY_KEY_LONG;
+ }
+ }
+ if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
+ tmp |= MAY_BE_ARRAY_KEY_STRING;
+ }
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ break;
+ case ZEND_ADD_ARRAY_UNPACK:
+ tmp = ssa_var_info[ssa_op->result_use].type;
+ ZEND_ASSERT(tmp & MAY_BE_ARRAY);
+ /* Ignore string keys as they will throw. */
+ if (t1 & MAY_BE_ARRAY_KEY_LONG) {
+ tmp |= MAY_BE_ARRAY_KEY_LONG | (t1 & (MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF));
+ }
+ if (t1 & MAY_BE_OBJECT) {
+ tmp |= MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ break;
+ case ZEND_UNSET_CV:
+ tmp = MAY_BE_UNDEF;
+ if (!op_array->function_name) {
+ /* In global scope, we know nothing */
+ tmp |= MAY_BE_REF;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ break;
+ case ZEND_UNSET_DIM:
+ case ZEND_UNSET_OBJ:
+ if (ssa_op->op1_def >= 0) {
+ UPDATE_SSA_TYPE(t1, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_FE_RESET_R:
+ case ZEND_FE_RESET_RW:
+ if (ssa_op->op1_def >= 0) {
+ tmp = t1;
+ if (opline->opcode == ZEND_FE_RESET_RW) {
+ tmp |= MAY_BE_REF;
+ } else if (t1 & MAY_BE_RC1) {
+ tmp |= MAY_BE_RCN;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ if (opline->opcode == ZEND_FE_RESET_RW) {
+//???
+ tmp = MAY_BE_REF | (t1 & (MAY_BE_ARRAY | MAY_BE_OBJECT));
+ } else {
+ tmp = MAY_BE_RC1 | MAY_BE_RCN | (t1 & (MAY_BE_ARRAY | MAY_BE_OBJECT | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF));
+ }
+ /* The result is set to UNDEF for invalid foreach inputs. */
+ if ((t1 & (MAY_BE_ANY | MAY_BE_UNDEF)) & ~(MAY_BE_ARRAY | MAY_BE_OBJECT)) {
+ tmp |= MAY_BE_UNDEF;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->result_def);
+ break;
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ tmp = t2 & MAY_BE_REF;
+ if (t1 & MAY_BE_OBJECT) {
+ if (opline->opcode == ZEND_FE_FETCH_RW) {
+ tmp |= MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ } else {
+ tmp |= MAY_BE_REF | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ }
+ }
+ if (t1 & MAY_BE_ARRAY) {
+ if (opline->opcode == ZEND_FE_FETCH_RW) {
+ tmp |= MAY_BE_REF | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ } else {
+ tmp |= ((t1 & MAY_BE_ARRAY_OF_ANY) >> MAY_BE_ARRAY_SHIFT);
+ if (tmp & MAY_BE_ARRAY) {
+ tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ }
+ if (t1 & MAY_BE_ARRAY_OF_REF) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ } else if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
+ if (ssa_op->result_def >= 0) {
+ tmp = (ssa_op->result_use >= 0) ? RES_USE_INFO() : 0;
+ if (t1 & MAY_BE_OBJECT) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ }
+ if (t1 & MAY_BE_ARRAY) {
+ if (t1 & MAY_BE_ARRAY_KEY_LONG) {
+ tmp |= MAY_BE_LONG;
+ }
+ if (t1 & MAY_BE_ARRAY_KEY_STRING) {
+ tmp |= MAY_BE_STRING | MAY_BE_RCN;
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ break;
+ case ZEND_FETCH_DIM_R:
+ case ZEND_FETCH_DIM_IS:
+ case ZEND_FETCH_DIM_RW:
+ case ZEND_FETCH_DIM_W:
+ case ZEND_FETCH_DIM_UNSET:
+ case ZEND_FETCH_DIM_FUNC_ARG:
+ case ZEND_FETCH_LIST_R:
+ case ZEND_FETCH_LIST_W:
+ if (ssa_op->op1_def >= 0) {
+ uint32_t key_type = 0;
+ tmp = t1 & ~(MAY_BE_RC1|MAY_BE_RCN);
+ if (opline->opcode == ZEND_FETCH_DIM_W ||
+ opline->opcode == ZEND_FETCH_DIM_RW ||
+ opline->opcode == ZEND_FETCH_DIM_FUNC_ARG ||
+ opline->opcode == ZEND_FETCH_LIST_W) {
+ if (t1 & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
+ if (opline->opcode != ZEND_FETCH_DIM_FUNC_ARG) {
+ tmp &= ~(MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE);
+ }
+ tmp |= MAY_BE_ARRAY | MAY_BE_RC1;
+ }
+ if (t1 & (MAY_BE_STRING|MAY_BE_ARRAY)) {
+ tmp |= MAY_BE_RC1;
+ if (opline->opcode == ZEND_FETCH_DIM_FUNC_ARG) {
+ tmp |= t1 & MAY_BE_RCN;
+ }
+ }
+ if (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ tmp |= t1 & (MAY_BE_RC1|MAY_BE_RCN);
+ }
+ if (opline->op2_type == IS_UNUSED) {
+ key_type |= MAY_BE_ARRAY_KEY_LONG;
+ } else {
+ if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) {
+ key_type |= MAY_BE_ARRAY_KEY_LONG;
+ }
+ if (t2 & MAY_BE_STRING) {
+ key_type |= MAY_BE_ARRAY_KEY_STRING;
+ if (opline->op2_type != IS_CONST) {
+ // FIXME: numeric string
+ key_type |= MAY_BE_ARRAY_KEY_LONG;
+ }
+ }
+ if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
+ key_type |= MAY_BE_ARRAY_KEY_STRING;
+ }
+ }
+ } else if (opline->opcode == ZEND_FETCH_DIM_UNSET) {
+ if (t1 & MAY_BE_ARRAY) {
+ tmp |= MAY_BE_RC1;
+ }
+ if (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ tmp |= t1 & (MAY_BE_RC1|MAY_BE_RCN);
+ }
+ }
+ if (opline->opcode == ZEND_FETCH_DIM_RW
+ || opline->opcode == ZEND_FETCH_DIM_W
+ || opline->opcode == ZEND_FETCH_DIM_FUNC_ARG
+ || opline->opcode == ZEND_FETCH_LIST_W) {
+ j = ssa_vars[ssa_op->result_def].use_chain;
+ while (j >= 0) {
+ zend_uchar opcode;
+
+ if (!ssa_opcodes) {
+ ZEND_ASSERT(j == (opline - op_array->opcodes) + 1 && "Use must be in next opline");
+ opcode = op_array->opcodes[j].opcode;
+ } else {
+ ZEND_ASSERT(ssa_opcodes[j] == opline + 1 && "Use must be in next opline");
+ opcode = ssa_opcodes[j]->opcode;
+ }
+ switch (opcode) {
+ case ZEND_FETCH_DIM_W:
+ case ZEND_FETCH_DIM_RW:
+ case ZEND_FETCH_DIM_FUNC_ARG:
+ case ZEND_FETCH_LIST_W:
+ case ZEND_ASSIGN_DIM:
+ case ZEND_ASSIGN_DIM_OP:
+ tmp |= key_type | MAY_BE_ARRAY | MAY_BE_ARRAY_OF_ARRAY;
+ break;
+ case ZEND_SEND_VAR_EX:
+ case ZEND_SEND_FUNC_ARG:
+ case ZEND_SEND_VAR_NO_REF:
+ case ZEND_SEND_VAR_NO_REF_EX:
+ case ZEND_SEND_REF:
+ case ZEND_ASSIGN_REF:
+ case ZEND_YIELD:
+ case ZEND_INIT_ARRAY:
+ case ZEND_ADD_ARRAY_ELEMENT:
+ case ZEND_RETURN_BY_REF:
+ case ZEND_VERIFY_RETURN_TYPE:
+ case ZEND_MAKE_REF:
+ case ZEND_FE_RESET_RW:
+ tmp |= key_type | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ break;
+ case ZEND_PRE_INC:
+ case ZEND_PRE_DEC:
+ case ZEND_POST_INC:
+ case ZEND_POST_DEC:
+ if (tmp & MAY_BE_ARRAY_OF_LONG) {
+ /* may overflow */
+ tmp |= key_type | MAY_BE_ARRAY_OF_DOUBLE;
+ } else if (!(tmp & (MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_DOUBLE))) {
+ tmp |= key_type | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE;
+ }
+ break;
+ case ZEND_FETCH_OBJ_W:
+ case ZEND_FETCH_OBJ_RW:
+ case ZEND_FETCH_OBJ_FUNC_ARG:
+ case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_OBJ_OP:
+ case ZEND_ASSIGN_OBJ_REF:
+ case ZEND_PRE_INC_OBJ:
+ case ZEND_PRE_DEC_OBJ:
+ case ZEND_POST_INC_OBJ:
+ case ZEND_POST_DEC_OBJ:
+ /* These will result in an error exception, unless the element
+ * is already an object. */
+ break;
+ case ZEND_SEND_VAR:
+ /* This can occur if a DIM_FETCH_FUNC_ARG with UNUSED op2 is left
+ * behind, because it can't be converted to DIM_FETCH_R. */
+ break;
+ EMPTY_SWITCH_DEFAULT_CASE()
+ }
+ j = zend_ssa_next_use(ssa->ops, ssa_op->result_def, j);
+ ZEND_ASSERT(j < 0 && "There should only be one use");
+ }
+ }
+ if ((tmp & MAY_BE_ARRAY) && (tmp & MAY_BE_ARRAY_KEY_ANY)) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ } else {
+ /* invalid key type */
+ tmp = (tmp & (MAY_BE_RC1|MAY_BE_RCN)) | (t1 & ~(MAY_BE_RC1|MAY_BE_RCN));
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ /* FETCH_LIST on a string behaves like FETCH_R on null */
+ tmp = zend_array_element_type(
+ opline->opcode != ZEND_FETCH_LIST_R ? t1 : ((t1 & ~MAY_BE_STRING) | MAY_BE_NULL),
+ opline->op1_type,
+ opline->result_type == IS_VAR,
+ opline->op2_type == IS_UNUSED);
+ if (opline->opcode == ZEND_FETCH_DIM_IS && (t1 & MAY_BE_STRING)) {
+ tmp |= MAY_BE_NULL;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ break;
+ case ZEND_FETCH_THIS:
+ UPDATE_SSA_OBJ_TYPE(op_array->scope, 1, ssa_op->result_def);
+ UPDATE_SSA_TYPE(MAY_BE_RCN|MAY_BE_OBJECT, ssa_op->result_def);
+ break;
+ case ZEND_FETCH_OBJ_R:
+ case ZEND_FETCH_OBJ_IS:
+ case ZEND_FETCH_OBJ_RW:
+ case ZEND_FETCH_OBJ_W:
+ case ZEND_FETCH_OBJ_UNSET:
+ case ZEND_FETCH_OBJ_FUNC_ARG:
+ if (ssa_op->result_def >= 0) {
+ zend_property_info *prop_info = zend_fetch_prop_info(op_array, ssa, opline, ssa_op);
+
+ tmp = zend_fetch_prop_type(script, prop_info, &ce);
+ if (opline->result_type != IS_TMP_VAR) {
+ tmp |= MAY_BE_REF | MAY_BE_INDIRECT;
+ } else if (!(opline->op1_type & (IS_VAR|IS_TMP_VAR)) || !(t1 & MAY_BE_RC1)) {
+ zend_class_entry *ce = NULL;
+
+ if (opline->op1_type == IS_UNUSED) {
+ ce = op_array->scope;
+ } else if (ssa_op->op1_use >= 0 && !ssa->var_info[ssa_op->op1_use].is_instanceof) {
+ ce = ssa->var_info[ssa_op->op1_use].ce;
+ }
+ if (prop_info) {
+ /* FETCH_OBJ_R/IS for plain property increments reference counter,
+ so it can't be 1 */
+ if (ce && !ce->create_object) {
+ tmp &= ~MAY_BE_RC1;
+ }
+ } else {
+ if (ce && !ce->create_object && !ce->__get) {
+ tmp &= ~MAY_BE_RC1;
+ }
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
+ }
+ }
+ break;
+ case ZEND_FETCH_STATIC_PROP_R:
+ case ZEND_FETCH_STATIC_PROP_IS:
+ case ZEND_FETCH_STATIC_PROP_RW:
+ case ZEND_FETCH_STATIC_PROP_W:
+ case ZEND_FETCH_STATIC_PROP_UNSET:
+ case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
+ tmp = zend_fetch_prop_type(script,
+ zend_fetch_static_prop_info(script, op_array, ssa, opline), &ce);
+ if (opline->result_type != IS_TMP_VAR) {
+ tmp |= MAY_BE_REF | MAY_BE_INDIRECT;
+ } else {
+ tmp &= ~MAY_BE_RC1;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
+ }
+ break;
+ case ZEND_DO_FCALL:
+ case ZEND_DO_ICALL:
+ case ZEND_DO_UCALL:
+ case ZEND_DO_FCALL_BY_NAME:
+ if (ssa_op->result_def >= 0) {
+ zend_func_info *func_info = ZEND_FUNC_INFO(op_array);
+ zend_call_info *call_info;
+
+ if (!func_info || !func_info->call_map) {
+ goto unknown_opcode;
+ }
+ call_info = func_info->call_map[opline - op_array->opcodes];
+ if (!call_info) {
+ goto unknown_opcode;
+ }
+
+ zend_class_entry *ce;
+ bool ce_is_instanceof;
+ tmp = zend_get_func_info(call_info, ssa, &ce, &ce_is_instanceof);
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, ce_is_instanceof, ssa_op->result_def);
+ }
+ }
+ break;
+ case ZEND_FETCH_CONSTANT:
+ case ZEND_FETCH_CLASS_CONSTANT:
+ UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_RESOURCE|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY, ssa_op->result_def);
+ break;
+ case ZEND_STRLEN:
+ tmp = MAY_BE_LONG;
+ if (t1 & (MAY_BE_ANY - (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING))) {
+ tmp |= MAY_BE_NULL;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ break;
+ case ZEND_COUNT:
+ case ZEND_FUNC_NUM_ARGS:
+ UPDATE_SSA_TYPE(MAY_BE_LONG, ssa_op->result_def);
+ break;
+ case ZEND_FUNC_GET_ARGS:
+ UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN| MAY_BE_ARRAY | MAY_BE_ARRAY_PACKED | MAY_BE_ARRAY_OF_ANY, ssa_op->result_def);
+ break;
+ case ZEND_GET_CLASS:
+ case ZEND_GET_CALLED_CLASS:
+ UPDATE_SSA_TYPE(MAY_BE_FALSE|MAY_BE_STRING|MAY_BE_RCN, ssa_op->result_def);
+ break;
+ case ZEND_GET_TYPE:
+ UPDATE_SSA_TYPE(MAY_BE_STRING|MAY_BE_RC1|MAY_BE_RCN, ssa_op->result_def);
+ break;
+ case ZEND_TYPE_CHECK:
+ case ZEND_DEFINED:
+ UPDATE_SSA_TYPE(MAY_BE_FALSE|MAY_BE_TRUE, ssa_op->result_def);
+ break;
+ case ZEND_VERIFY_RETURN_TYPE:
+ if (t1 & MAY_BE_REF) {
+ tmp = t1;
+ ce = NULL;
+ } else {
+ zend_arg_info *ret_info = op_array->arg_info - 1;
+ tmp = zend_fetch_arg_info_type(script, ret_info, &ce);
+ }
+ if (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->op1_def);
+ } else {
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->op1_def);
+ }
+ } else {
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
+ } else {
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
+ }
+ }
+ break;
+ case ZEND_MAKE_REF:
+ tmp = MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ if (ssa_op->op1_def >= 0) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_CATCH:
+ /* Forbidden opcodes */
+ ZEND_UNREACHABLE();
+ break;
+ default:
+unknown_opcode:
+ if (ssa_op->op1_def >= 0) {
+ tmp = MAY_BE_ANY | MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ if (ssa_op->result_def >= 0) {
+ tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ if (opline->result_type == IS_TMP_VAR) {
+ if (opline->opcode == ZEND_FETCH_R || opline->opcode == ZEND_FETCH_IS) {
+ tmp |= MAY_BE_RCN;
+ } else {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ } else {
+ tmp |= MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN;
+ switch (opline->opcode) {
+ case ZEND_FETCH_W:
+ case ZEND_FETCH_RW:
+ case ZEND_FETCH_FUNC_ARG:
+ case ZEND_FETCH_UNSET:
+ case ZEND_FETCH_DIM_W:
+ case ZEND_FETCH_DIM_RW:
+ case ZEND_FETCH_DIM_FUNC_ARG:
+ case ZEND_FETCH_DIM_UNSET:
+ case ZEND_FETCH_OBJ_W:
+ case ZEND_FETCH_OBJ_RW:
+ case ZEND_FETCH_OBJ_FUNC_ARG:
+ case ZEND_FETCH_OBJ_UNSET:
+ case ZEND_FETCH_STATIC_PROP_W:
+ case ZEND_FETCH_STATIC_PROP_RW:
+ case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
+ case ZEND_FETCH_STATIC_PROP_UNSET:
+ tmp |= MAY_BE_INDIRECT;
+ break;
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ break;
+ }
+
+ return SUCCESS;
+}
+
+ZEND_API int zend_update_type_info(
+ const zend_op_array *op_array,
+ zend_ssa *ssa,
+ const zend_script *script,
+ zend_op *opline,
+ zend_ssa_op *ssa_op,
+ const zend_op **ssa_opcodes,
+ zend_long optimization_level)
+{
+ return _zend_update_type_info(op_array, ssa, script, NULL, opline, ssa_op, ssa_opcodes, optimization_level, 0);
+}
+
+static uint32_t get_class_entry_rank(zend_class_entry *ce) {
+ uint32_t rank = 0;
+ if (ce->ce_flags & ZEND_ACC_LINKED) {
+ while (ce->parent) {
+ rank++;
+ ce = ce->parent;
+ }
+ }
+ return rank;
+}
+
+/* Compute least common ancestor on class inheritance tree only */
+static zend_class_entry *join_class_entries(
+ zend_class_entry *ce1, zend_class_entry *ce2, int *is_instanceof) {
+ uint32_t rank1, rank2;
+ if (ce1 == ce2) {
+ return ce1;
+ }
+ if (!ce1 || !ce2) {
+ return NULL;
+ }
+
+ rank1 = get_class_entry_rank(ce1);
+ rank2 = get_class_entry_rank(ce2);
+
+ while (rank1 != rank2) {
+ if (rank1 > rank2) {
+ ce1 = !(ce1->ce_flags & ZEND_ACC_LINKED) ? NULL : ce1->parent;
+ rank1--;
+ } else {
+ ce2 = !(ce2->ce_flags & ZEND_ACC_LINKED) ? NULL : ce2->parent;
+ rank2--;
+ }
+ }
+
+ while (ce1 != ce2) {
+ ce1 = !(ce1->ce_flags & ZEND_ACC_LINKED) ? NULL : ce1->parent;
+ ce2 = !(ce2->ce_flags & ZEND_ACC_LINKED) ? NULL : ce2->parent;
+ }
+
+ if (ce1) {
+ *is_instanceof = 1;
+ }
+ return ce1;
+}
+
+int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_bitset worklist, zend_long optimization_level)
+{
+ zend_basic_block *blocks = ssa->cfg.blocks;
+ zend_ssa_var *ssa_vars = ssa->vars;
+ zend_ssa_var_info *ssa_var_info = ssa->var_info;
+ int ssa_vars_count = ssa->vars_count;
+ int i, j;
+ uint32_t tmp, worklist_len = zend_bitset_len(ssa_vars_count);
+ bool update_worklist = 1;
+
+ while (!zend_bitset_empty(worklist, worklist_len)) {
+ j = zend_bitset_first(worklist, worklist_len);
+ zend_bitset_excl(worklist, j);
+ if (ssa_vars[j].definition_phi) {
+ zend_ssa_phi *p = ssa_vars[j].definition_phi;
+ if (p->pi >= 0) {
+ zend_class_entry *ce = ssa_var_info[p->sources[0]].ce;
+ int is_instanceof = ssa_var_info[p->sources[0]].is_instanceof;
+ tmp = get_ssa_var_info(ssa, p->sources[0]);
+
+ if (!p->has_range_constraint) {
+ zend_ssa_type_constraint *constraint = &p->constraint.type;
+ tmp &= constraint->type_mask;
+ if (!(tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
+ tmp &= ~(MAY_BE_RC1|MAY_BE_RCN);
+ }
+ if ((tmp & MAY_BE_OBJECT) && constraint->ce && ce != constraint->ce) {
+ if (!ce) {
+ ce = constraint->ce;
+ is_instanceof = 1;
+ } else if (is_instanceof && instanceof_function(constraint->ce, ce)) {
+ ce = constraint->ce;
+ } else {
+ /* Ignore the constraint (either ce instanceof constraint->ce or
+ * they are unrelated, as far as we can statically determine) */
+ }
+ }
+ }
+
+ UPDATE_SSA_TYPE(tmp, j);
+ UPDATE_SSA_OBJ_TYPE(ce, is_instanceof, j);
+ } else {
+ int first = 1;
+ int is_instanceof = 0;
+ zend_class_entry *ce = NULL;
+
+ tmp = 0;
+ for (i = 0; i < blocks[p->block].predecessors_count; i++) {
+ tmp |= get_ssa_var_info(ssa, p->sources[i]);
+ }
+ UPDATE_SSA_TYPE(tmp, j);
+ for (i = 0; i < blocks[p->block].predecessors_count; i++) {
+ 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);
+ }
+ }
+ }
+ UPDATE_SSA_OBJ_TYPE(ce, ce ? is_instanceof : 0, j);
+ }
+ } else if (ssa_vars[j].definition >= 0) {
+ i = ssa_vars[j].definition;
+ if (_zend_update_type_info(op_array, ssa, script, worklist, op_array->opcodes + i, ssa->ops + i, NULL, optimization_level, 1) == FAILURE) {
+ return FAILURE;
+ }
+ }
+ }
+ return SUCCESS;
+}
+
+static bool is_narrowable_instr(zend_op *opline) {
+ return opline->opcode == ZEND_ADD || opline->opcode == ZEND_SUB
+ || opline->opcode == ZEND_MUL || opline->opcode == ZEND_DIV;
+}
+
+static bool is_effective_op1_double_cast(zend_op *opline, zval *op2) {
+ return (opline->opcode == ZEND_ADD && Z_LVAL_P(op2) == 0)
+ || (opline->opcode == ZEND_SUB && Z_LVAL_P(op2) == 0)
+ || (opline->opcode == ZEND_MUL && Z_LVAL_P(op2) == 1)
+ || (opline->opcode == ZEND_DIV && Z_LVAL_P(op2) == 1);
+}
+static bool is_effective_op2_double_cast(zend_op *opline, zval *op1) {
+ /* In PHP it holds that (double)(0-$int) is bitwise identical to 0.0-(double)$int,
+ * so allowing SUB here is fine. */
+ return (opline->opcode == ZEND_ADD && Z_LVAL_P(op1) == 0)
+ || (opline->opcode == ZEND_SUB && Z_LVAL_P(op1) == 0)
+ || (opline->opcode == ZEND_MUL && Z_LVAL_P(op1) == 1);
+}
+
+/* This function recursively checks whether it's possible to convert an integer variable
+ * initialization to a double initialization. The basic idea is that if the value is used
+ * only in add/sub/mul/div ("narrowable" instructions) with a double result value, then it
+ * will be cast to double at that point anyway, so we may as well do it earlier already.
+ *
+ * The tricky case are chains of operations, where it's not necessarily a given that converting
+ * an integer to double before the chain of operations is the same as converting it after the
+ * chain. What this function does is detect two cases where it is safe:
+ * * If the operations only involve constants, then we can simply verify that performing the
+ * calculation on integers and doubles yields the same value.
+ * * Even if one operand is not known, we may be able to determine that the operations with the
+ * integer replaced by a double only acts as an effective double cast on the unknown operand.
+ * E.g. 0+$i and 0.0+$i only differ by that cast. If then the consuming instruction of this
+ * result will perform a double cast anyway, the conversion is safe.
+ *
+ * The checks happens recursively, while keeping track of which variables are already visisted to
+ * avoid infinite loops. An iterative, worklist driven approach would be possible, but the state
+ * management more cumbersome to implement, so we don't bother for now.
+ */
+static bool can_convert_to_double(
+ const zend_op_array *op_array, zend_ssa *ssa, int var_num,
+ zval *value, zend_bitset visited) {
+ zend_ssa_var *var = &ssa->vars[var_num];
+ zend_ssa_phi *phi;
+ int use;
+ uint32_t type;
+
+ if (zend_bitset_in(visited, var_num)) {
+ return 1;
+ }
+ zend_bitset_incl(visited, var_num);
+
+ for (use = var->use_chain; use >= 0; use = zend_ssa_next_use(ssa->ops, var_num, use)) {
+ zend_op *opline = &op_array->opcodes[use];
+ zend_ssa_op *ssa_op = &ssa->ops[use];
+
+ if (zend_ssa_is_no_val_use(opline, ssa_op, var_num)) {
+ continue;
+ }
+
+ if (!is_narrowable_instr(opline)) {
+ return 0;
+ }
+
+ /* Instruction always returns double, the conversion is certainly fine */
+ type = ssa->var_info[ssa_op->result_def].type;
+ if ((type & MAY_BE_ANY) == MAY_BE_DOUBLE) {
+ continue;
+ }
+
+ /* UNDEF signals that the previous result is an effective double cast, this is only allowed
+ * if this instruction would have done the cast anyway (previous check). */
+ if (Z_ISUNDEF_P(value)) {
+ return 0;
+ }
+
+ /* Check that narrowing can actually be useful */
+ if ((type & MAY_BE_ANY) & ~(MAY_BE_LONG|MAY_BE_DOUBLE)) {
+ return 0;
+ }
+
+ {
+ /* For calculation on original values */
+ zval orig_op1, orig_op2, orig_result;
+ /* For calculation with var_num cast to double */
+ zval dval_op1, dval_op2, dval_result;
+
+ ZVAL_UNDEF(&orig_op1);
+ ZVAL_UNDEF(&dval_op1);
+ if (ssa_op->op1_use == var_num) {
+ ZVAL_COPY_VALUE(&orig_op1, value);
+ ZVAL_DOUBLE(&dval_op1, (double) Z_LVAL_P(value));
+ } else if (opline->op1_type == IS_CONST) {
+ zval *zv = CRT_CONSTANT(opline->op1);
+ if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_DOUBLE) {
+ ZVAL_COPY_VALUE(&orig_op1, zv);
+ ZVAL_COPY_VALUE(&dval_op1, zv);
+ }
+ }
+
+ ZVAL_UNDEF(&orig_op2);
+ ZVAL_UNDEF(&dval_op2);
+ if (ssa_op->op2_use == var_num) {
+ ZVAL_COPY_VALUE(&orig_op2, value);
+ ZVAL_DOUBLE(&dval_op2, (double) Z_LVAL_P(value));
+ } else if (opline->op2_type == IS_CONST) {
+ zval *zv = CRT_CONSTANT(opline->op2);
+ if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_DOUBLE) {
+ ZVAL_COPY_VALUE(&orig_op2, zv);
+ ZVAL_COPY_VALUE(&dval_op2, zv);
+ }
+ }
+
+ ZEND_ASSERT(!Z_ISUNDEF(orig_op1) || !Z_ISUNDEF(orig_op2));
+ if (Z_ISUNDEF(orig_op1)) {
+ if (opline->opcode == ZEND_MUL && Z_LVAL(orig_op2) == 0) {
+ ZVAL_LONG(&orig_result, 0);
+ } else if (is_effective_op1_double_cast(opline, &orig_op2)) {
+ ZVAL_UNDEF(&orig_result);
+ } else {
+ return 0;
+ }
+ } else if (Z_ISUNDEF(orig_op2)) {
+ if (opline->opcode == ZEND_MUL && Z_LVAL(orig_op1) == 0) {
+ ZVAL_LONG(&orig_result, 0);
+ } else if (is_effective_op2_double_cast(opline, &orig_op1)) {
+ ZVAL_UNDEF(&orig_result);
+ } else {
+ return 0;
+ }
+ } else {
+ zend_uchar opcode = opline->opcode;
+
+ if (opcode == ZEND_ASSIGN_OP) {
+ opcode = opline->extended_value;
+ }
+
+ /* Avoid division by zero */
+ if (opcode == ZEND_DIV && zval_get_double(&orig_op2) == 0.0) {
+ return 0;
+ }
+
+ get_binary_op(opcode)(&orig_result, &orig_op1, &orig_op2);
+ get_binary_op(opcode)(&dval_result, &dval_op1, &dval_op2);
+ ZEND_ASSERT(Z_TYPE(dval_result) == IS_DOUBLE);
+ if (zval_get_double(&orig_result) != Z_DVAL(dval_result)) {
+ return 0;
+ }
+ }
+
+ if (!can_convert_to_double(op_array, ssa, ssa_op->result_def, &orig_result, visited)) {
+ return 0;
+ }
+ }
+ }
+
+ for (phi = var->phi_use_chain; phi; phi = zend_ssa_next_use_phi(ssa, var_num, phi)) {
+ /* Check that narrowing can actually be useful */
+ type = ssa->var_info[phi->ssa_var].type;
+ if ((type & MAY_BE_ANY) & ~(MAY_BE_LONG|MAY_BE_DOUBLE)) {
+ return 0;
+ }
+
+ if (!can_convert_to_double(op_array, ssa, phi->ssa_var, value, visited)) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int zend_type_narrowing(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_long optimization_level)
+{
+ uint32_t bitset_len = zend_bitset_len(ssa->vars_count);
+ zend_bitset visited, worklist;
+ int i, v;
+ zend_op *opline;
+ bool narrowed = 0;
+ ALLOCA_FLAG(use_heap)
+
+ visited = ZEND_BITSET_ALLOCA(2 * bitset_len, use_heap);
+ worklist = visited + bitset_len;
+
+ zend_bitset_clear(worklist, bitset_len);
+
+ for (v = op_array->last_var; v < ssa->vars_count; v++) {
+ if ((ssa->var_info[v].type & (MAY_BE_REF | MAY_BE_ANY | MAY_BE_UNDEF)) != MAY_BE_LONG) continue;
+ if (ssa->vars[v].definition < 0) continue;
+ if (ssa->vars[v].no_val) continue;
+ opline = op_array->opcodes + ssa->vars[v].definition;
+ /* Go through assignments of literal integers and check if they can be converted to
+ * doubles instead, in the hope that we'll narrow long|double to double. */
+ if (opline->opcode == ZEND_ASSIGN && opline->result_type == IS_UNUSED &&
+ opline->op1_type == IS_CV && opline->op2_type == IS_CONST) {
+ zval *value = CRT_CONSTANT(opline->op2);
+
+ zend_bitset_clear(visited, bitset_len);
+ if (can_convert_to_double(op_array, ssa, v, value, visited)) {
+ narrowed = 1;
+ ssa->var_info[v].use_as_double = 1;
+ /* The "visited" vars are exactly those which may change their type due to
+ * narrowing. Reset their types and add them to the type inference worklist */
+ ZEND_BITSET_FOREACH(visited, bitset_len, i) {
+ ssa->var_info[i].type &= ~MAY_BE_ANY;
+ } ZEND_BITSET_FOREACH_END();
+ zend_bitset_union(worklist, visited, bitset_len);
+ }
+ }
+ }
+
+ if (!narrowed) {
+ free_alloca(visited, use_heap);
+ return SUCCESS;
+ }
+
+ if (zend_infer_types_ex(op_array, script, ssa, worklist, optimization_level) != SUCCESS) {
+ free_alloca(visited, use_heap);
+ return FAILURE;
+ }
+
+ free_alloca(visited, use_heap);
+ return SUCCESS;
+}
+
+static int is_recursive_tail_call(const zend_op_array *op_array,
+ zend_op *opline)
+{
+ zend_func_info *info = ZEND_FUNC_INFO(op_array);
+
+ if (info->ssa.ops && info->ssa.vars && info->call_map &&
+ info->ssa.ops[opline - op_array->opcodes].op1_use >= 0 &&
+ info->ssa.vars[info->ssa.ops[opline - op_array->opcodes].op1_use].definition >= 0) {
+
+ zend_op *op = op_array->opcodes + info->ssa.vars[info->ssa.ops[opline - op_array->opcodes].op1_use].definition;
+
+ if (op->opcode == ZEND_DO_UCALL) {
+ zend_call_info *call_info = info->call_map[op - op_array->opcodes];
+ if (call_info && op_array == &call_info->callee_func->op_array) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+ZEND_API void zend_init_func_return_info(
+ const zend_op_array *op_array, const zend_script *script, zend_ssa_var_info *ret)
+{
+ if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+ zend_arg_info *ret_info = op_array->arg_info - 1;
+ zend_ssa_range tmp_range = {0, 0, 0, 0};
+
+ ret->type = zend_fetch_arg_info_type(script, ret_info, &ret->ce);
+ if (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) {
+ ret->type |= MAY_BE_REF;
+ }
+ ret->is_instanceof = (ret->ce) ? 1 : 0;
+ ret->range = tmp_range;
+ ret->has_range = 0;
+ }
+}
+
+void zend_func_return_info(const zend_op_array *op_array,
+ const zend_script *script,
+ int recursive,
+ int widening,
+ zend_ssa_var_info *ret)
+{
+ zend_func_info *info = ZEND_FUNC_INFO(op_array);
+ zend_ssa *ssa = &info->ssa;
+ int blocks_count = info->ssa.cfg.blocks_count;
+ zend_basic_block *blocks = info->ssa.cfg.blocks;
+ int j;
+ uint32_t t1;
+ uint32_t tmp = 0;
+ zend_class_entry *tmp_ce = NULL;
+ int tmp_is_instanceof = -1;
+ zend_class_entry *arg_ce;
+ int arg_is_instanceof;
+ zend_ssa_range tmp_range = {0, 0, 0, 0};
+ int tmp_has_range = -1;
+
+ if (op_array->fn_flags & ZEND_ACC_GENERATOR) {
+ ret->type = MAY_BE_OBJECT | MAY_BE_RC1 | MAY_BE_RCN;
+ ret->ce = zend_ce_generator;
+ ret->is_instanceof = 0;
+ ret->range = tmp_range;
+ ret->has_range = 0;
+ return;
+ }
+
+ for (j = 0; j < blocks_count; j++) {
+ if ((blocks[j].flags & ZEND_BB_REACHABLE) && blocks[j].len != 0) {
+ zend_op *opline = op_array->opcodes + blocks[j].start + blocks[j].len - 1;
+
+ if (opline->opcode == ZEND_RETURN || opline->opcode == ZEND_RETURN_BY_REF) {
+ zend_ssa_op *ssa_op = ssa->ops ? &ssa->ops[opline - op_array->opcodes] : NULL;
+ if (!recursive && ssa_op && info->ssa.var_info &&
+ ssa_op->op1_use >= 0 &&
+ info->ssa.var_info[ssa_op->op1_use].recursive) {
+ continue;
+ }
+ if (is_recursive_tail_call(op_array, opline)) {
+ continue;
+ }
+ t1 = OP1_INFO();
+ if (t1 & MAY_BE_UNDEF) {
+ t1 |= MAY_BE_NULL;
+ }
+ if (opline->opcode == ZEND_RETURN) {
+ if (t1 & MAY_BE_RC1) {
+ t1 |= MAY_BE_RCN;
+ }
+ t1 &= ~(MAY_BE_UNDEF | MAY_BE_REF);
+ } else {
+ t1 |= MAY_BE_REF;
+ t1 &= ~(MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN);
+ }
+ tmp |= t1;
+
+ if (ssa_op && info->ssa.var_info &&
+ ssa_op->op1_use >= 0 &&
+ info->ssa.var_info[ssa_op->op1_use].ce) {
+ arg_ce = info->ssa.var_info[ssa_op->op1_use].ce;
+ arg_is_instanceof = info->ssa.var_info[ssa_op->op1_use].is_instanceof;
+ } else {
+ arg_ce = NULL;
+ arg_is_instanceof = 0;
+ }
+
+ if (tmp_is_instanceof < 0) {
+ tmp_ce = arg_ce;
+ tmp_is_instanceof = arg_is_instanceof;
+ } else if (arg_ce && arg_ce == tmp_ce) {
+ if (tmp_is_instanceof != arg_is_instanceof) {
+ tmp_is_instanceof = 1;
+ }
+ } else {
+ tmp_ce = NULL;
+ tmp_is_instanceof = 0;
+ }
+
+ if (opline->op1_type == IS_CONST) {
+ zval *zv = CRT_CONSTANT(opline->op1);
+
+ if (Z_TYPE_P(zv) == IS_NULL) {
+ if (tmp_has_range < 0) {
+ tmp_has_range = 1;
+ tmp_range.underflow = 0;
+ tmp_range.min = 0;
+ tmp_range.max = 0;
+ tmp_range.overflow = 0;
+ } else if (tmp_has_range) {
+ if (!tmp_range.underflow) {
+ tmp_range.min = MIN(tmp_range.min, 0);
+ }
+ if (!tmp_range.overflow) {
+ tmp_range.max = MAX(tmp_range.max, 0);
+ }
+ }
+ } else if (Z_TYPE_P(zv) == IS_FALSE) {
+ if (tmp_has_range < 0) {
+ tmp_has_range = 1;
+ tmp_range.underflow = 0;
+ tmp_range.min = 0;
+ tmp_range.max = 0;
+ tmp_range.overflow = 0;
+ } else if (tmp_has_range) {
+ if (!tmp_range.underflow) {
+ tmp_range.min = MIN(tmp_range.min, 0);
+ }
+ if (!tmp_range.overflow) {
+ tmp_range.max = MAX(tmp_range.max, 0);
+ }
+ }
+ } else if (Z_TYPE_P(zv) == IS_TRUE) {
+ if (tmp_has_range < 0) {
+ tmp_has_range = 1;
+ tmp_range.underflow = 0;
+ tmp_range.min = 1;
+ tmp_range.max = 1;
+ tmp_range.overflow = 0;
+ } else if (tmp_has_range) {
+ if (!tmp_range.underflow) {
+ tmp_range.min = MIN(tmp_range.min, 1);
+ }
+ if (!tmp_range.overflow) {
+ tmp_range.max = MAX(tmp_range.max, 1);
+ }
+ }
+ } else if (Z_TYPE_P(zv) == IS_LONG) {
+ if (tmp_has_range < 0) {
+ tmp_has_range = 1;
+ tmp_range.underflow = 0;
+ tmp_range.min = Z_LVAL_P(zv);
+ tmp_range.max = Z_LVAL_P(zv);
+ tmp_range.overflow = 0;
+ } else if (tmp_has_range) {
+ if (!tmp_range.underflow) {
+ tmp_range.min = MIN(tmp_range.min, Z_LVAL_P(zv));
+ }
+ if (!tmp_range.overflow) {
+ tmp_range.max = MAX(tmp_range.max, Z_LVAL_P(zv));
+ }
+ }
+ } else {
+ tmp_has_range = 0;
+ }
+ } else if (ssa_op && info->ssa.var_info && ssa_op->op1_use >= 0) {
+ if (info->ssa.var_info[ssa_op->op1_use].has_range) {
+ if (tmp_has_range < 0) {
+ tmp_has_range = 1;
+ tmp_range = info->ssa.var_info[ssa_op->op1_use].range;
+ } else if (tmp_has_range) {
+ /* union */
+ if (info->ssa.var_info[ssa_op->op1_use].range.underflow) {
+ tmp_range.underflow = 1;
+ tmp_range.min = ZEND_LONG_MIN;
+ } else {
+ tmp_range.min = MIN(tmp_range.min, info->ssa.var_info[ssa_op->op1_use].range.min);
+ }
+ if (info->ssa.var_info[ssa_op->op1_use].range.overflow) {
+ tmp_range.overflow = 1;
+ tmp_range.max = ZEND_LONG_MAX;
+ } else {
+ tmp_range.max = MAX(tmp_range.max, info->ssa.var_info[ssa_op->op1_use].range.max);
+ }
+ }
+ } else if (!widening) {
+ tmp_has_range = 1;
+ tmp_range.underflow = 1;
+ tmp_range.min = ZEND_LONG_MIN;
+ tmp_range.max = ZEND_LONG_MAX;
+ tmp_range.overflow = 1;
+ }
+ } else {
+ tmp_has_range = 0;
+ }
+ }
+ }
+ }
+
+ if (!(op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) {
+ if (tmp_is_instanceof < 0) {
+ tmp_is_instanceof = 0;
+ tmp_ce = NULL;
+ }
+ if (tmp_has_range < 0) {
+ tmp_has_range = 0;
+ }
+ ret->type = tmp;
+ ret->ce = tmp_ce;
+ ret->is_instanceof = tmp_is_instanceof;
+ }
+ ret->range = tmp_range;
+ ret->has_range = tmp_has_range;
+}
+
+static int zend_infer_types(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_long optimization_level)
+{
+ zend_ssa_var_info *ssa_var_info = ssa->var_info;
+ int ssa_vars_count = ssa->vars_count;
+ int j;
+ zend_bitset worklist;
+ ALLOCA_FLAG(use_heap);
+
+ worklist = do_alloca(sizeof(zend_ulong) * zend_bitset_len(ssa_vars_count), use_heap);
+ memset(worklist, 0, sizeof(zend_ulong) * zend_bitset_len(ssa_vars_count));
+
+ /* Type Inference */
+ for (j = op_array->last_var; j < ssa_vars_count; j++) {
+ zend_bitset_incl(worklist, j);
+ ssa_var_info[j].type = 0;
+ }
+
+ if (zend_infer_types_ex(op_array, script, ssa, worklist, optimization_level) != SUCCESS) {
+ free_alloca(worklist, use_heap);
+ return FAILURE;
+ }
+
+ /* Narrowing integer initialization to doubles */
+ zend_type_narrowing(op_array, script, ssa, optimization_level);
+
+ if (ZEND_FUNC_INFO(op_array)) {
+ zend_func_return_info(op_array, script, 1, 0, &ZEND_FUNC_INFO(op_array)->return_info);
+ }
+
+ free_alloca(worklist, use_heap);
+ return SUCCESS;
+}
+
+ZEND_API int zend_ssa_inference(zend_arena **arena, const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_long optimization_level) /* {{{ */
+{
+ zend_ssa_var_info *ssa_var_info;
+ int i;
+
+ if (!ssa->var_info) {
+ ssa->var_info = zend_arena_calloc(arena, ssa->vars_count, sizeof(zend_ssa_var_info));
+ }
+ ssa_var_info = ssa->var_info;
+
+ if (!op_array->function_name) {
+ for (i = 0; i < op_array->last_var; i++) {
+ ssa_var_info[i].type = MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ ssa_var_info[i].has_range = 0;
+ }
+ } else {
+ for (i = 0; i < op_array->last_var; i++) {
+ ssa_var_info[i].type = MAY_BE_UNDEF;
+ ssa_var_info[i].has_range = 0;
+ if (ssa->vars[i].alias) {
+ ssa_var_info[i].type |= get_ssa_alias_types(ssa->vars[i].alias);
+ }
+ }
+ }
+ for (i = op_array->last_var; i < ssa->vars_count; i++) {
+ ssa_var_info[i].type = 0;
+ ssa_var_info[i].has_range = 0;
+ }
+
+ if (zend_infer_ranges(op_array, ssa) != SUCCESS) {
+ return FAILURE;
+ }
+
+ if (zend_infer_types(op_array, script, ssa, optimization_level) != SUCCESS) {
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+/* }}} */
+
+void zend_inference_check_recursive_dependencies(zend_op_array *op_array)
+{
+ zend_func_info *info = ZEND_FUNC_INFO(op_array);
+ zend_call_info *call_info;
+ zend_bitset worklist;
+ int worklist_len, i;
+ ALLOCA_FLAG(use_heap);
+
+ if (!info->ssa.var_info || !(info->flags & ZEND_FUNC_RECURSIVE)) {
+ return;
+ }
+ worklist_len = zend_bitset_len(info->ssa.vars_count);
+ worklist = do_alloca(sizeof(zend_ulong) * worklist_len, use_heap);
+ memset(worklist, 0, sizeof(zend_ulong) * worklist_len);
+ call_info = info->callee_info;
+ while (call_info) {
+ if (call_info->recursive && call_info->caller_call_opline &&
+ info->ssa.ops[call_info->caller_call_opline - op_array->opcodes].result_def >= 0) {
+ zend_bitset_incl(worklist, info->ssa.ops[call_info->caller_call_opline - op_array->opcodes].result_def);
+ }
+ call_info = call_info->next_callee;
+ }
+ WHILE_WORKLIST(worklist, worklist_len, i) {
+ if (!info->ssa.var_info[i].recursive) {
+ info->ssa.var_info[i].recursive = 1;
+ add_usages(op_array, &info->ssa, worklist, i);
+ }
+ } WHILE_WORKLIST_END();
+ free_alloca(worklist, use_heap);
+}
+
+ZEND_API int zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op, const zend_op_array *op_array, zend_ssa *ssa, uint32_t t1, uint32_t t2)
+{
+ if (opline->op1_type == IS_CV) {
+ if (t1 & MAY_BE_UNDEF) {
+ switch (opline->opcode) {
+ case ZEND_UNSET_VAR:
+ case ZEND_ISSET_ISEMPTY_VAR:
+ 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_BIND_STATIC:
+ case ZEND_FETCH_DIM_IS:
+ case ZEND_FETCH_OBJ_IS:
+ case ZEND_SEND_REF:
+ case ZEND_UNSET_CV:
+ case ZEND_ISSET_ISEMPTY_CV:
+ case ZEND_MAKE_REF:
+ case ZEND_FETCH_DIM_W:
+ break;
+ default:
+ /* undefined variable warning */
+ return 1;
+ }
+ }
+ } else if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
+ if ((t1 & MAY_BE_RC1)
+ && (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_CASE_STRICT:
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ case ZEND_FETCH_LIST_R:
+ 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_FUNC_ARG:
+ case ZEND_SEND_VAR_NO_REF:
+ case ZEND_SEND_VAR_NO_REF_EX:
+ case ZEND_SEND_REF:
+ case ZEND_SEPARATE:
+ case ZEND_END_SILENCE:
+ case ZEND_MAKE_REF:
+ 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:
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ break;
+ default:
+ /* undefined variable warning */
+ return 1;
+ }
+ }
+ } else if (opline->op2_type & (IS_TMP_VAR|IS_VAR)) {
+ if ((t2 & MAY_BE_RC1)
+ && (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:
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ 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_BEGIN_SILENCE:
+ case ZEND_END_SILENCE:
+ case ZEND_FREE:
+ case ZEND_FE_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_MATCH:
+ case ZEND_ISSET_ISEMPTY_VAR:
+ case ZEND_ISSET_ISEMPTY_CV:
+ case ZEND_FUNC_NUM_ARGS:
+ case ZEND_FUNC_GET_ARGS:
+ case ZEND_COPY_TMP:
+ case ZEND_CASE_STRICT:
+ case ZEND_JMP_NULL:
+ return 0;
+ case ZEND_SEND_VAR:
+ case ZEND_SEND_VAL:
+ case ZEND_SEND_REF:
+ case ZEND_SEND_VAR_EX:
+ case ZEND_SEND_FUNC_ARG:
+ case ZEND_CHECK_FUNC_ARG:
+ /* May throw for named params. */
+ return opline->op2_type == IS_CONST;
+ 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, ssa_op + 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|MAY_BE_RESOURCE)) ||
+ (t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ 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|MAY_BE_RESOURCE)) ||
+ (t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ case ZEND_SL:
+ case ZEND_SR:
+ return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
+ (t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
+ !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|MAY_BE_RESOURCE)) ||
+ (t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ 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_PRE_INC:
+ case ZEND_POST_INC:
+ case ZEND_PRE_DEC:
+ case ZEND_POST_DEC:
+ return (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ case ZEND_BOOL_NOT:
+ 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_OP:
+ if (opline->extended_value == 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|MAY_BE_RESOURCE)) ||
+ (t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ } else if (opline->extended_value == ZEND_DIV ||
+ opline->extended_value == ZEND_MOD) {
+ if (!OP2_HAS_RANGE() ||
+ (OP2_MIN_RANGE() <= 0 && OP2_MAX_RANGE() >= 0)) {
+ /* Division by zero */
+ return 1;
+ }
+ return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
+ (t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ } else if (opline->extended_value == ZEND_SUB ||
+ opline->extended_value == ZEND_MUL ||
+ opline->extended_value == ZEND_POW) {
+ return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
+ (t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ } else if (opline->extended_value == ZEND_SL ||
+ opline->extended_value == ZEND_SR) {
+ return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
+ (t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
+ !OP2_HAS_RANGE() ||
+ OP2_MIN_RANGE() < 0;
+ } else if (opline->extended_value == ZEND_CONCAT) {
+ return (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
+ (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
+ } else if (opline->extended_value == ZEND_BW_OR ||
+ opline->extended_value == ZEND_BW_AND ||
+ opline->extended_value == 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|MAY_BE_RESOURCE)) ||
+ (t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ }
+ return 1;
+ case ZEND_ASSIGN:
+ if (t1 & MAY_BE_REF) {
+ return 1;
+ }
+ case ZEND_BIND_STATIC:
+ 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:
+ if ((opline+1)->op1_type == IS_CV) {
+ if (_ssa_op1_info(op_array, ssa, opline+1, ssa_op+1) & MAY_BE_UNDEF) {
+ return 1;
+ }
+ }
+ 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_ASSIGN_OBJ:
+ if (t1 & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_OBJECT))) {
+ return 1;
+ }
+ if (ssa_op->op1_use) {
+ zend_ssa_var_info *var_info = ssa->var_info + ssa_op->op1_use;
+ zend_class_entry *ce = var_info->ce;
+
+ if (var_info->is_instanceof ||
+ !ce || ce->create_object || ce->__get || ce->__set || ce->parent) {
+ return 1;
+ }
+
+ if (op_array->scope != ce && ce->default_properties_count) {
+ zend_property_info *prop_info;
+
+ if (opline->op2_type == IS_CONST) {
+ prop_info = zend_hash_find_ptr(&ce->properties_info,
+ Z_STR_P(CRT_CONSTANT(opline->op2)));
+ if (prop_info && !(prop_info->flags & ZEND_ACC_PUBLIC)) {
+ return 1;
+ }
+ } else {
+ if (t2 & (MAY_BE_ANY-MAY_BE_STRING)) {
+ return 1;
+ }
+ ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop_info) {
+ if (!(prop_info->flags & ZEND_ACC_PUBLIC)) {
+ return 1;
+ }
+ } ZEND_HASH_FOREACH_END();
+ }
+ }
+ return 0;
+ }
+ return 1;
+ 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_TYPE_P(CRT_CONSTANT(opline->op2)) == IS_CONSTANT_AST) {
+ 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_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 0;
+ EMPTY_SWITCH_DEFAULT_CASE()
+ }
+ case ZEND_ARRAY_KEY_EXISTS:
+ if ((t2 & MAY_BE_ANY) != MAY_BE_ARRAY) {
+ return 1;
+ }
+ if ((t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
+ return 1;
+ }
+ return 0;
+ case ZEND_FE_RESET_R:
+ case ZEND_FE_RESET_RW:
+ if ((t1 & (MAY_BE_ANY|MAY_BE_REF)) != MAY_BE_ARRAY) {
+ return 1;
+ }
+ return 0;
+ case ZEND_FE_FETCH_R:
+ if ((t1 & (MAY_BE_ANY|MAY_BE_REF)) != MAY_BE_ARRAY) {
+ return 1;
+ }
+ if (opline->op2_type == IS_CV
+ && (t2 & MAY_BE_RC1)
+ && (t2 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY))) {
+ return 1;
+ }
+ return 0;
+ case ZEND_FETCH_DIM_W:
+ case ZEND_FETCH_LIST_W:
+ if (t1 & (MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) {
+ return 1;
+ }
+ if (t2 & (MAY_BE_RESOURCE|MAY_BE_ARRAY|MAY_BE_OBJECT)) {
+ return 1;
+ }
+ if (opline->op2_type == IS_UNUSED) {
+ return 1;
+ }
+ return 0;
+ default:
+ return 1;
+ }
+}
+
+ZEND_API int zend_may_throw(const zend_op *opline, const zend_ssa_op *ssa_op, const zend_op_array *op_array, zend_ssa *ssa)
+{
+ return zend_may_throw_ex(opline, ssa_op, op_array, ssa, OP1_INFO(), OP2_INFO());
+}
diff --git a/Zend/Optimizer/zend_inference.h b/Zend/Optimizer/zend_inference.h
new file mode 100644
index 0000000000..8390946012
--- /dev/null
+++ b/Zend/Optimizer/zend_inference.h
@@ -0,0 +1,288 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, e-SSA based Type & Range Inference |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef ZEND_INFERENCE_H
+#define ZEND_INFERENCE_H
+
+#include "zend_optimizer.h"
+#include "zend_ssa.h"
+#include "zend_bitset.h"
+
+/* Bitmask for type inference (zend_ssa_var_info.type) */
+#include "zend_type_info.h"
+
+#define MAY_BE_PACKED_GUARD (1<<27) /* needs packed array guard */
+#define MAY_BE_CLASS_GUARD (1<<27) /* needs class guard */
+#define MAY_BE_GUARD (1<<28) /* needs type guard */
+//#define MAY_BE_IN_REG (1<<29) /* deprecated and not used */
+
+//TODO: remome MAY_BE_RC1, MAY_BE_RCN???
+#define MAY_BE_RC1 (1<<30) /* may be non-reference with refcount == 1 */
+#define MAY_BE_RCN (1u<<31) /* 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 bool _ssa_##opN##_has_range(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op) \
+ { \
+ if (opline->opN##_type == IS_CONST) { \
+ zval *zv = CRT_CONSTANT(opline->opN); \
+ return (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_TRUE || Z_TYPE_P(zv) == IS_FALSE || Z_TYPE_P(zv) == IS_NULL); \
+ } else { \
+ return (opline->opN##_type != IS_UNUSED && \
+ ssa->var_info && \
+ ssa_op->opN##_use >= 0 && \
+ ssa->var_info[ssa_op->opN##_use].has_range); \
+ } \
+ return 0; \
+ } \
+
+#define DEFINE_SSA_OP_MIN_RANGE(opN) \
+ static zend_always_inline zend_long _ssa_##opN##_min_range(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op) \
+ { \
+ if (opline->opN##_type == IS_CONST) { \
+ zval *zv = CRT_CONSTANT(opline->opN); \
+ if (Z_TYPE_P(zv) == IS_LONG) { \
+ return Z_LVAL_P(zv); \
+ } else if (Z_TYPE_P(zv) == IS_TRUE) { \
+ return 1; \
+ } else if (Z_TYPE_P(zv) == IS_FALSE) { \
+ return 0; \
+ } else if (Z_TYPE_P(zv) == IS_NULL) { \
+ return 0; \
+ } \
+ } else if (opline->opN##_type != IS_UNUSED && \
+ ssa->var_info && \
+ ssa_op->opN##_use >= 0 && \
+ ssa->var_info[ssa_op->opN##_use].has_range) { \
+ return ssa->var_info[ssa_op->opN##_use].range.min; \
+ } \
+ return ZEND_LONG_MIN; \
+ } \
+
+#define DEFINE_SSA_OP_MAX_RANGE(opN) \
+ static zend_always_inline zend_long _ssa_##opN##_max_range(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op) \
+ { \
+ if (opline->opN##_type == IS_CONST) { \
+ zval *zv = CRT_CONSTANT(opline->opN); \
+ if (Z_TYPE_P(zv) == IS_LONG) { \
+ return Z_LVAL_P(zv); \
+ } else if (Z_TYPE_P(zv) == IS_TRUE) { \
+ return 1; \
+ } else if (Z_TYPE_P(zv) == IS_FALSE) { \
+ return 0; \
+ } else if (Z_TYPE_P(zv) == IS_NULL) { \
+ return 0; \
+ } \
+ } else if (opline->opN##_type != IS_UNUSED && \
+ ssa->var_info && \
+ ssa_op->opN##_use >= 0 && \
+ ssa->var_info[ssa_op->opN##_use].has_range) { \
+ return ssa->var_info[ssa_op->opN##_use].range.max; \
+ } \
+ return ZEND_LONG_MAX; \
+ } \
+
+#define DEFINE_SSA_OP_RANGE_UNDERFLOW(opN) \
+ static zend_always_inline char _ssa_##opN##_range_underflow(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op) \
+ { \
+ if (opline->opN##_type == IS_CONST) { \
+ zval *zv = CRT_CONSTANT(opline->opN); \
+ if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_TRUE || Z_TYPE_P(zv) == IS_FALSE || Z_TYPE_P(zv) == IS_NULL) { \
+ return 0; \
+ } \
+ } else if (opline->opN##_type != IS_UNUSED && \
+ ssa->var_info && \
+ ssa_op->opN##_use >= 0 && \
+ ssa->var_info[ssa_op->opN##_use].has_range) { \
+ return ssa->var_info[ssa_op->opN##_use].range.underflow; \
+ } \
+ return 1; \
+ } \
+
+#define DEFINE_SSA_OP_RANGE_OVERFLOW(opN) \
+ static zend_always_inline char _ssa_##opN##_range_overflow(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op) \
+ { \
+ if (opline->opN##_type == IS_CONST) { \
+ zval *zv = CRT_CONSTANT(opline->opN); \
+ if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_TRUE || Z_TYPE_P(zv) == IS_FALSE || Z_TYPE_P(zv) == IS_NULL) { \
+ return 0; \
+ } \
+ } else if (opline->opN##_type != IS_UNUSED && \
+ ssa->var_info && \
+ ssa_op->opN##_use >= 0 && \
+ ssa->var_info[ssa_op->opN##_use].has_range) { \
+ return ssa->var_info[ssa_op->opN##_use].range.overflow; \
+ } \
+ return 1; \
+ } \
+
+DEFINE_SSA_OP_HAS_RANGE(op1)
+DEFINE_SSA_OP_MIN_RANGE(op1)
+DEFINE_SSA_OP_MAX_RANGE(op1)
+DEFINE_SSA_OP_RANGE_UNDERFLOW(op1)
+DEFINE_SSA_OP_RANGE_OVERFLOW(op1)
+DEFINE_SSA_OP_HAS_RANGE(op2)
+DEFINE_SSA_OP_MIN_RANGE(op2)
+DEFINE_SSA_OP_MAX_RANGE(op2)
+DEFINE_SSA_OP_RANGE_UNDERFLOW(op2)
+DEFINE_SSA_OP_RANGE_OVERFLOW(op2)
+
+#define OP1_HAS_RANGE() (_ssa_op1_has_range (op_array, ssa, opline, ssa_op))
+#define OP1_MIN_RANGE() (_ssa_op1_min_range (op_array, ssa, opline, ssa_op))
+#define OP1_MAX_RANGE() (_ssa_op1_max_range (op_array, ssa, opline, ssa_op))
+#define OP1_RANGE_UNDERFLOW() (_ssa_op1_range_underflow (op_array, ssa, opline, ssa_op))
+#define OP1_RANGE_OVERFLOW() (_ssa_op1_range_overflow (op_array, ssa, opline, ssa_op))
+#define OP2_HAS_RANGE() (_ssa_op2_has_range (op_array, ssa, opline, ssa_op))
+#define OP2_MIN_RANGE() (_ssa_op2_min_range (op_array, ssa, opline, ssa_op))
+#define OP2_MAX_RANGE() (_ssa_op2_max_range (op_array, ssa, opline, ssa_op))
+#define OP2_RANGE_UNDERFLOW() (_ssa_op2_range_underflow (op_array, ssa, opline, ssa_op))
+#define OP2_RANGE_OVERFLOW() (_ssa_op2_range_overflow (op_array, ssa, opline, ssa_op))
+
+static zend_always_inline uint32_t _const_op_type(const zval *zv) {
+ if (Z_TYPE_P(zv) == IS_CONSTANT_AST) {
+ return MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY;
+ } else if (Z_TYPE_P(zv) == IS_ARRAY) {
+ HashTable *ht = Z_ARRVAL_P(zv);
+ uint32_t tmp = MAY_BE_ARRAY;
+ zend_string *str;
+ zval *val;
+
+ if (Z_REFCOUNTED_P(zv)) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ } else {
+ tmp |= MAY_BE_RCN;
+ }
+
+ ZEND_HASH_FOREACH_STR_KEY_VAL(ht, str, val) {
+ if (str) {
+ tmp |= MAY_BE_ARRAY_KEY_STRING;
+ } else {
+ tmp |= MAY_BE_ARRAY_KEY_LONG;
+ }
+ tmp |= 1 << (Z_TYPE_P(val) + MAY_BE_ARRAY_SHIFT);
+ } ZEND_HASH_FOREACH_END();
+ if (HT_IS_PACKED(ht)) {
+ tmp &= ~MAY_BE_ARRAY_HASH;
+ }
+ return tmp;
+ } else {
+ uint32_t tmp = (1 << Z_TYPE_P(zv));
+
+ if (Z_REFCOUNTED_P(zv)) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ } else if (Z_TYPE_P(zv) == IS_STRING) {
+ tmp |= MAY_BE_RCN;
+ }
+ return tmp;
+ }
+}
+
+static zend_always_inline uint32_t get_ssa_var_info(const zend_ssa *ssa, int ssa_var_num)
+{
+ if (ssa->var_info && ssa_var_num >= 0) {
+ return ssa->var_info[ssa_var_num].type;
+ } else {
+ return MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_INDIRECT | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ }
+}
+
+#define DEFINE_SSA_OP_INFO(opN) \
+ static zend_always_inline uint32_t _ssa_##opN##_info(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op) \
+ { \
+ if (opline->opN##_type == IS_CONST) { \
+ return _const_op_type(CRT_CONSTANT(opline->opN)); \
+ } else { \
+ return get_ssa_var_info(ssa, ssa->var_info ? ssa_op->opN##_use : -1); \
+ } \
+ } \
+
+#define DEFINE_SSA_OP_DEF_INFO(opN) \
+ static zend_always_inline uint32_t _ssa_##opN##_def_info(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op) \
+ { \
+ return get_ssa_var_info(ssa, ssa->var_info ? ssa_op->opN##_def : -1); \
+ } \
+
+
+DEFINE_SSA_OP_INFO(op1)
+DEFINE_SSA_OP_INFO(op2)
+DEFINE_SSA_OP_INFO(result)
+DEFINE_SSA_OP_DEF_INFO(op1)
+DEFINE_SSA_OP_DEF_INFO(op2)
+DEFINE_SSA_OP_DEF_INFO(result)
+
+#define OP1_INFO() (_ssa_op1_info(op_array, ssa, opline, ssa_op))
+#define OP2_INFO() (_ssa_op2_info(op_array, ssa, opline, ssa_op))
+#define OP1_DATA_INFO() (_ssa_op1_info(op_array, ssa, (opline+1), (ssa_op+1)))
+#define OP2_DATA_INFO() (_ssa_op2_info(op_array, ssa, (opline+1), (ssa_op+1)))
+#define RES_USE_INFO() (_ssa_result_info(op_array, ssa, opline, ssa_op))
+#define OP1_DEF_INFO() (_ssa_op1_def_info(op_array, ssa, opline, ssa_op))
+#define OP2_DEF_INFO() (_ssa_op2_def_info(op_array, ssa, opline, ssa_op))
+#define OP1_DATA_DEF_INFO() (_ssa_op1_def_info(op_array, ssa, (opline+1), (ssa_op+1)))
+#define OP2_DATA_DEF_INFO() (_ssa_op2_def_info(op_array, ssa, (opline+1), (ssa_op+1)))
+#define RES_INFO() (_ssa_result_def_info(op_array, ssa, opline, ssa_op))
+
+static zend_always_inline bool zend_add_will_overflow(zend_long a, zend_long b) {
+ return (b > 0 && a > ZEND_LONG_MAX - b)
+ || (b < 0 && a < ZEND_LONG_MIN - b);
+}
+static zend_always_inline bool zend_sub_will_overflow(zend_long a, zend_long b) {
+ return (b > 0 && a < ZEND_LONG_MIN + b)
+ || (b < 0 && a > ZEND_LONG_MAX + b);
+}
+
+BEGIN_EXTERN_C()
+
+ZEND_API int zend_ssa_find_false_dependencies(const zend_op_array *op_array, zend_ssa *ssa);
+ZEND_API int zend_ssa_find_sccs(const zend_op_array *op_array, zend_ssa *ssa);
+ZEND_API int zend_ssa_inference(zend_arena **raena, const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_long optimization_level);
+
+ZEND_API uint32_t zend_array_element_type(uint32_t t1, zend_uchar op_type, int write, int insert);
+
+int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int var, int widening, int narrowing, zend_ssa_range *tmp);
+ZEND_API int zend_inference_propagate_range(const zend_op_array *op_array, zend_ssa *ssa, zend_op *opline, zend_ssa_op* ssa_op, int var, zend_ssa_range *tmp);
+void zend_inference_init_range(const zend_op_array *op_array, zend_ssa *ssa, int var, bool underflow, zend_long min, zend_long max, bool overflow);
+int zend_inference_narrowing_meet(zend_ssa_var_info *var_info, zend_ssa_range *r);
+int zend_inference_widening_meet(zend_ssa_var_info *var_info, zend_ssa_range *r);
+void zend_inference_check_recursive_dependencies(zend_op_array *op_array);
+
+int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_bitset worklist, zend_long optimization_level);
+
+ZEND_API uint32_t zend_fetch_arg_info_type(
+ const zend_script *script, zend_arg_info *arg_info, zend_class_entry **pce);
+ZEND_API void zend_init_func_return_info(
+ const zend_op_array *op_array, const zend_script *script, zend_ssa_var_info *ret);
+void zend_func_return_info(const zend_op_array *op_array,
+ const zend_script *script,
+ int recursive,
+ int widening,
+ zend_ssa_var_info *ret);
+
+ZEND_API int zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op, const zend_op_array *op_array, zend_ssa *ssa, uint32_t t1, uint32_t t2);
+ZEND_API int zend_may_throw(const zend_op *opline, const zend_ssa_op *ssa_op, const zend_op_array *op_array, zend_ssa *ssa);
+
+ZEND_API int zend_update_type_info(
+ const zend_op_array *op_array, zend_ssa *ssa, const zend_script *script,
+ zend_op *opline, zend_ssa_op *ssa_op, const zend_op **ssa_opcodes,
+ zend_long optimization_level);
+
+END_EXTERN_C()
+
+#endif /* ZEND_INFERENCE_H */
diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c
new file mode 100644
index 0000000000..ae548fc520
--- /dev/null
+++ b/Zend/Optimizer/zend_optimizer.c
@@ -0,0 +1,1568 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend OPcache |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Andi Gutmans <andi@php.net> |
+ | Zeev Suraski <zeev@php.net> |
+ | Stanislav Malyshev <stas@zend.com> |
+ | Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#include "php.h"
+#include "Optimizer/zend_optimizer.h"
+#include "Optimizer/zend_optimizer_internal.h"
+#include "zend_API.h"
+#include "zend_constants.h"
+#include "zend_execute.h"
+#include "zend_vm.h"
+#include "zend_cfg.h"
+#include "zend_func_info.h"
+#include "zend_call_graph.h"
+#include "zend_inference.h"
+#include "zend_dump.h"
+
+static void zend_optimizer_zval_dtor_wrapper(zval *zvalue)
+{
+ zval_ptr_dtor_nogc(zvalue);
+}
+
+void zend_optimizer_collect_constant(zend_optimizer_ctx *ctx, zval *name, zval* value)
+{
+ zval val;
+
+ if (!ctx->constants) {
+ ctx->constants = zend_arena_alloc(&ctx->arena, sizeof(HashTable));
+ zend_hash_init(ctx->constants, 16, NULL, zend_optimizer_zval_dtor_wrapper, 0);
+ }
+ ZVAL_COPY(&val, value);
+ zend_hash_add(ctx->constants, Z_STR_P(name), &val);
+}
+
+int zend_optimizer_eval_binary_op(zval *result, zend_uchar opcode, zval *op1, zval *op2) /* {{{ */
+{
+ binary_op_type binary_op = get_binary_op(opcode);
+ int er, ret;
+
+ if (zend_binary_op_produces_error(opcode, op1, op2)) {
+ return FAILURE;
+ }
+
+ er = EG(error_reporting);
+ EG(error_reporting) = 0;
+ ret = binary_op(result, op1, op2);
+ EG(error_reporting) = er;
+
+ return ret;
+}
+/* }}} */
+
+int zend_optimizer_eval_unary_op(zval *result, zend_uchar opcode, zval *op1) /* {{{ */
+{
+ unary_op_type unary_op = get_unary_op(opcode);
+
+ if (unary_op) {
+ if (opcode == ZEND_BW_NOT
+ && Z_TYPE_P(op1) != IS_LONG
+ && Z_TYPE_P(op1) != IS_DOUBLE
+ && Z_TYPE_P(op1) != IS_STRING) {
+ /* produces "Unsupported operand types" exception */
+ return FAILURE;
+ }
+ return unary_op(result, op1);
+ } else { /* ZEND_BOOL */
+ ZVAL_BOOL(result, zend_is_true(op1));
+ return SUCCESS;
+ }
+}
+/* }}} */
+
+int zend_optimizer_eval_cast(zval *result, uint32_t type, zval *op1) /* {{{ */
+{
+ switch (type) {
+ case IS_NULL:
+ ZVAL_NULL(result);
+ return SUCCESS;
+ case _IS_BOOL:
+ ZVAL_BOOL(result, zval_is_true(op1));
+ return SUCCESS;
+ case IS_LONG:
+ ZVAL_LONG(result, zval_get_long(op1));
+ return SUCCESS;
+ case IS_DOUBLE:
+ ZVAL_DOUBLE(result, zval_get_double(op1));
+ return SUCCESS;
+ case IS_STRING:
+ /* Conversion from double to string takes into account run-time
+ 'precision' setting and cannot be evaluated at compile-time */
+ if (Z_TYPE_P(op1) != IS_ARRAY && Z_TYPE_P(op1) != IS_DOUBLE) {
+ ZVAL_STR(result, zval_get_string(op1));
+ return SUCCESS;
+ }
+ break;
+ case IS_ARRAY:
+ ZVAL_COPY(result, op1);
+ convert_to_array(result);
+ return SUCCESS;
+ }
+ return FAILURE;
+}
+/* }}} */
+
+int zend_optimizer_eval_strlen(zval *result, zval *op1) /* {{{ */
+{
+ if (Z_TYPE_P(op1) != IS_STRING) {
+ return FAILURE;
+ }
+ ZVAL_LONG(result, Z_STRLEN_P(op1));
+ return SUCCESS;
+}
+/* }}} */
+
+int zend_optimizer_get_collected_constant(HashTable *constants, zval *name, zval* value)
+{
+ zval *val;
+
+ if ((val = zend_hash_find(constants, Z_STR_P(name))) != NULL) {
+ ZVAL_COPY(value, val);
+ return 1;
+ }
+ return 0;
+}
+
+int zend_optimizer_add_literal(zend_op_array *op_array, zval *zv)
+{
+ int i = op_array->last_literal;
+ op_array->last_literal++;
+ op_array->literals = (zval*)erealloc(op_array->literals, op_array->last_literal * sizeof(zval));
+ ZVAL_COPY_VALUE(&op_array->literals[i], zv);
+ Z_EXTRA(op_array->literals[i]) = 0;
+ return i;
+}
+
+static inline int zend_optimizer_add_literal_string(zend_op_array *op_array, zend_string *str) {
+ zval zv;
+ ZVAL_STR(&zv, str);
+ zend_string_hash_val(str);
+ return zend_optimizer_add_literal(op_array, &zv);
+}
+
+static inline void drop_leading_backslash(zval *val) {
+ if (Z_STRVAL_P(val)[0] == '\\') {
+ zend_string *str = zend_string_init(Z_STRVAL_P(val) + 1, Z_STRLEN_P(val) - 1, 0);
+ zval_ptr_dtor_nogc(val);
+ ZVAL_STR(val, str);
+ }
+}
+
+static inline uint32_t alloc_cache_slots(zend_op_array *op_array, uint32_t num) {
+ uint32_t ret = op_array->cache_size;
+ op_array->cache_size += num * sizeof(void *);
+ return ret;
+}
+
+#define REQUIRES_STRING(val) do { \
+ if (Z_TYPE_P(val) != IS_STRING) { \
+ return 0; \
+ } \
+} while (0)
+
+#define TO_STRING_NOWARN(val) do { \
+ if (Z_TYPE_P(val) >= IS_ARRAY) { \
+ return 0; \
+ } \
+ convert_to_string(val); \
+} while (0)
+
+int zend_optimizer_update_op1_const(zend_op_array *op_array,
+ zend_op *opline,
+ zval *val)
+{
+ switch (opline->opcode) {
+ case ZEND_OP_DATA:
+ switch ((opline-1)->opcode) {
+ case ZEND_ASSIGN_OBJ_REF:
+ case ZEND_ASSIGN_STATIC_PROP_REF:
+ return 0;
+ }
+ opline->op1.constant = zend_optimizer_add_literal(op_array, val);
+ break;
+ case ZEND_FREE:
+ case ZEND_CHECK_VAR:
+ MAKE_NOP(opline);
+ zval_ptr_dtor_nogc(val);
+ return 1;
+ case ZEND_SEND_VAR_EX:
+ case ZEND_SEND_FUNC_ARG:
+ case ZEND_FETCH_DIM_W:
+ case ZEND_FETCH_DIM_RW:
+ case ZEND_FETCH_DIM_FUNC_ARG:
+ case ZEND_FETCH_DIM_UNSET:
+ case ZEND_FETCH_LIST_W:
+ case ZEND_ASSIGN_DIM:
+ case ZEND_RETURN_BY_REF:
+ case ZEND_INSTANCEOF:
+ case ZEND_MAKE_REF:
+ return 0;
+ case ZEND_CATCH:
+ REQUIRES_STRING(val);
+ drop_leading_backslash(val);
+ opline->op1.constant = zend_optimizer_add_literal(op_array, val);
+ opline->extended_value = alloc_cache_slots(op_array, 1) | (opline->extended_value & ZEND_LAST_CATCH);
+ zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val)));
+ break;
+ case ZEND_DEFINED:
+ REQUIRES_STRING(val);
+ drop_leading_backslash(val);
+ opline->op1.constant = zend_optimizer_add_literal(op_array, val);
+ opline->extended_value = alloc_cache_slots(op_array, 1);
+ zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val)));
+ break;
+ case ZEND_NEW:
+ REQUIRES_STRING(val);
+ drop_leading_backslash(val);
+ opline->op1.constant = zend_optimizer_add_literal(op_array, val);
+ opline->op2.num = alloc_cache_slots(op_array, 1);
+ zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val)));
+ break;
+ case ZEND_INIT_STATIC_METHOD_CALL:
+ REQUIRES_STRING(val);
+ drop_leading_backslash(val);
+ opline->op1.constant = zend_optimizer_add_literal(op_array, val);
+ if (opline->op2_type != IS_CONST) {
+ opline->result.num = alloc_cache_slots(op_array, 1);
+ }
+ zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val)));
+ break;
+ case ZEND_FETCH_CLASS_CONSTANT:
+ REQUIRES_STRING(val);
+ drop_leading_backslash(val);
+ opline->op1.constant = zend_optimizer_add_literal(op_array, val);
+ if (opline->op2_type != IS_CONST) {
+ opline->extended_value = alloc_cache_slots(op_array, 1);
+ }
+ zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val)));
+ break;
+ case ZEND_ASSIGN_OP:
+ case ZEND_ASSIGN_DIM_OP:
+ case ZEND_ASSIGN_OBJ_OP:
+ break;
+ case ZEND_ASSIGN_STATIC_PROP_OP:
+ case ZEND_ASSIGN_STATIC_PROP:
+ case ZEND_ASSIGN_STATIC_PROP_REF:
+ case ZEND_FETCH_STATIC_PROP_R:
+ case ZEND_FETCH_STATIC_PROP_W:
+ case ZEND_FETCH_STATIC_PROP_RW:
+ case ZEND_FETCH_STATIC_PROP_IS:
+ case ZEND_FETCH_STATIC_PROP_UNSET:
+ case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
+ case ZEND_UNSET_STATIC_PROP:
+ case ZEND_ISSET_ISEMPTY_STATIC_PROP:
+ case ZEND_PRE_INC_STATIC_PROP:
+ case ZEND_PRE_DEC_STATIC_PROP:
+ case ZEND_POST_INC_STATIC_PROP:
+ case ZEND_POST_DEC_STATIC_PROP:
+ TO_STRING_NOWARN(val);
+ opline->op1.constant = zend_optimizer_add_literal(op_array, val);
+ if (opline->op2_type == IS_CONST && (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*) == op_array->cache_size) {
+ op_array->cache_size += sizeof(void *);
+ } else {
+ opline->extended_value = alloc_cache_slots(op_array, 3) | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
+ }
+ break;
+ case ZEND_SEND_VAR:
+ opline->opcode = ZEND_SEND_VAL;
+ opline->op1.constant = zend_optimizer_add_literal(op_array, val);
+ break;
+ case ZEND_SEPARATE:
+ case ZEND_SEND_VAR_NO_REF:
+ case ZEND_SEND_VAR_NO_REF_EX:
+ return 0;
+ case ZEND_VERIFY_RETURN_TYPE:
+ /* This would require a non-local change.
+ * zend_optimizer_replace_by_const() supports this. */
+ return 0;
+ case ZEND_CASE:
+ case ZEND_CASE_STRICT:
+ case ZEND_FETCH_LIST_R:
+ case ZEND_COPY_TMP:
+ case ZEND_FETCH_CLASS_NAME:
+ return 0;
+ case ZEND_ECHO:
+ {
+ zval zv;
+ if (Z_TYPE_P(val) != IS_STRING && zend_optimizer_eval_cast(&zv, IS_STRING, val) == SUCCESS) {
+ zval_ptr_dtor_nogc(val);
+ val = &zv;
+ }
+ opline->op1.constant = zend_optimizer_add_literal(op_array, val);
+ if (Z_TYPE_P(val) == IS_STRING && Z_STRLEN_P(val) == 0) {
+ MAKE_NOP(opline);
+ }
+ /* TODO: In a subsequent pass, *after* this step and compacting nops, combine consecutive ZEND_ECHOs using the block information from ssa->cfg */
+ /* (e.g. for ext/opcache/tests/opt/sccp_010.phpt) */
+ break;
+ }
+ case ZEND_CONCAT:
+ case ZEND_FAST_CONCAT:
+ case ZEND_FETCH_R:
+ case ZEND_FETCH_W:
+ case ZEND_FETCH_RW:
+ case ZEND_FETCH_IS:
+ case ZEND_FETCH_UNSET:
+ case ZEND_FETCH_FUNC_ARG:
+ TO_STRING_NOWARN(val);
+ if (opline->opcode == ZEND_CONCAT && opline->op2_type == IS_CONST) {
+ opline->opcode = ZEND_FAST_CONCAT;
+ }
+ /* break missing intentionally */
+ default:
+ opline->op1.constant = zend_optimizer_add_literal(op_array, val);
+ break;
+ }
+
+ opline->op1_type = IS_CONST;
+ if (Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_STRING) {
+ zend_string_hash_val(Z_STR(ZEND_OP1_LITERAL(opline)));
+ }
+ return 1;
+}
+
+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:
+ return 0;
+ case ZEND_FETCH_CLASS:
+ if ((opline + 1)->opcode == ZEND_INSTANCEOF &&
+ (opline + 1)->op2.var == opline->result.var) {
+ return 0;
+ }
+ /* break missing intentionally */
+ case ZEND_INSTANCEOF:
+ REQUIRES_STRING(val);
+ drop_leading_backslash(val);
+ opline->op2.constant = zend_optimizer_add_literal(op_array, val);
+ zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val)));
+ opline->extended_value = alloc_cache_slots(op_array, 1);
+ break;
+ case ZEND_INIT_FCALL_BY_NAME:
+ REQUIRES_STRING(val);
+ drop_leading_backslash(val);
+ opline->op2.constant = zend_optimizer_add_literal(op_array, val);
+ zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val)));
+ opline->result.num = alloc_cache_slots(op_array, 1);
+ break;
+ case ZEND_ASSIGN_STATIC_PROP:
+ case ZEND_ASSIGN_STATIC_PROP_REF:
+ case ZEND_FETCH_STATIC_PROP_R:
+ case ZEND_FETCH_STATIC_PROP_W:
+ case ZEND_FETCH_STATIC_PROP_RW:
+ case ZEND_FETCH_STATIC_PROP_IS:
+ case ZEND_FETCH_STATIC_PROP_UNSET:
+ case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
+ case ZEND_UNSET_STATIC_PROP:
+ case ZEND_ISSET_ISEMPTY_STATIC_PROP:
+ case ZEND_PRE_INC_STATIC_PROP:
+ case ZEND_PRE_DEC_STATIC_PROP:
+ case ZEND_POST_INC_STATIC_PROP:
+ case ZEND_POST_DEC_STATIC_PROP:
+ case ZEND_ASSIGN_STATIC_PROP_OP:
+ REQUIRES_STRING(val);
+ drop_leading_backslash(val);
+ opline->op2.constant = zend_optimizer_add_literal(op_array, val);
+ zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val)));
+ if (opline->op1_type != IS_CONST) {
+ opline->extended_value = alloc_cache_slots(op_array, 1) | (opline->extended_value & (ZEND_RETURNS_FUNCTION|ZEND_ISEMPTY|ZEND_FETCH_OBJ_FLAGS));
+ }
+ break;
+ case ZEND_INIT_FCALL:
+ REQUIRES_STRING(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);
+ opline->result.num = alloc_cache_slots(op_array, 1);
+ break;
+ case ZEND_INIT_DYNAMIC_CALL:
+ if (Z_TYPE_P(val) == IS_STRING) {
+ if (zend_memrchr(Z_STRVAL_P(val), ':', Z_STRLEN_P(val))) {
+ return 0;
+ }
+
+ if (zend_optimizer_classify_function(Z_STR_P(val), opline->extended_value)) {
+ /* Dynamic call to various special functions must stay dynamic,
+ * otherwise would drop a warning */
+ return 0;
+ }
+
+ opline->opcode = ZEND_INIT_FCALL_BY_NAME;
+ drop_leading_backslash(val);
+ opline->op2.constant = zend_optimizer_add_literal(op_array, val);
+ zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val)));
+ opline->result.num = alloc_cache_slots(op_array, 1);
+ } else {
+ opline->op2.constant = zend_optimizer_add_literal(op_array, val);
+ }
+ break;
+ case ZEND_INIT_METHOD_CALL:
+ REQUIRES_STRING(val);
+ opline->op2.constant = zend_optimizer_add_literal(op_array, val);
+ zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val)));
+ opline->result.num = alloc_cache_slots(op_array, 2);
+ break;
+ case ZEND_INIT_STATIC_METHOD_CALL:
+ REQUIRES_STRING(val);
+ opline->op2.constant = zend_optimizer_add_literal(op_array, val);
+ zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val)));
+ if (opline->op1_type != IS_CONST) {
+ opline->result.num = alloc_cache_slots(op_array, 2);
+ }
+ break;
+ case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_OBJ_REF:
+ case ZEND_FETCH_OBJ_R:
+ case ZEND_FETCH_OBJ_W:
+ case ZEND_FETCH_OBJ_RW:
+ case ZEND_FETCH_OBJ_IS:
+ case ZEND_FETCH_OBJ_UNSET:
+ case ZEND_FETCH_OBJ_FUNC_ARG:
+ case ZEND_UNSET_OBJ:
+ case ZEND_PRE_INC_OBJ:
+ case ZEND_PRE_DEC_OBJ:
+ case ZEND_POST_INC_OBJ:
+ case ZEND_POST_DEC_OBJ:
+ case ZEND_ASSIGN_OBJ_OP:
+ TO_STRING_NOWARN(val);
+ opline->op2.constant = zend_optimizer_add_literal(op_array, val);
+ opline->extended_value = alloc_cache_slots(op_array, 3);
+ break;
+ case ZEND_ISSET_ISEMPTY_PROP_OBJ:
+ TO_STRING_NOWARN(val);
+ opline->op2.constant = zend_optimizer_add_literal(op_array, val);
+ opline->extended_value = alloc_cache_slots(op_array, 3) | (opline->extended_value & ZEND_ISEMPTY);
+ break;
+ case ZEND_ASSIGN_DIM_OP:
+ case ZEND_ISSET_ISEMPTY_DIM_OBJ:
+ case ZEND_ASSIGN_DIM:
+ case ZEND_UNSET_DIM:
+ case ZEND_FETCH_DIM_R:
+ case ZEND_FETCH_DIM_W:
+ case ZEND_FETCH_DIM_RW:
+ case ZEND_FETCH_DIM_IS:
+ case ZEND_FETCH_DIM_FUNC_ARG:
+ case ZEND_FETCH_DIM_UNSET:
+ case ZEND_FETCH_LIST_R:
+ case ZEND_FETCH_LIST_W:
+ if (Z_TYPE_P(val) == IS_STRING) {
+ zend_ulong index;
+
+ if (ZEND_HANDLE_NUMERIC(Z_STR_P(val), index)) {
+ ZVAL_LONG(&tmp, index);
+ opline->op2.constant = zend_optimizer_add_literal(op_array, &tmp);
+ zend_string_hash_val(Z_STR_P(val));
+ zend_optimizer_add_literal(op_array, val);
+ Z_EXTRA(op_array->literals[opline->op2.constant]) = ZEND_EXTRA_VALUE;
+ break;
+ }
+ }
+ opline->op2.constant = zend_optimizer_add_literal(op_array, val);
+ break;
+ case ZEND_ADD_ARRAY_ELEMENT:
+ case ZEND_INIT_ARRAY:
+ if (Z_TYPE_P(val) == IS_STRING) {
+ zend_ulong index;
+ if (ZEND_HANDLE_NUMERIC(Z_STR_P(val), index)) {
+ zval_ptr_dtor_nogc(val);
+ ZVAL_LONG(val, index);
+ }
+ }
+ opline->op2.constant = zend_optimizer_add_literal(op_array, val);
+ break;
+ case ZEND_ROPE_INIT:
+ case ZEND_ROPE_ADD:
+ case ZEND_ROPE_END:
+ case ZEND_CONCAT:
+ case ZEND_FAST_CONCAT:
+ TO_STRING_NOWARN(val);
+ if (opline->opcode == ZEND_CONCAT && opline->op1_type == IS_CONST) {
+ opline->opcode = ZEND_FAST_CONCAT;
+ }
+ /* break missing intentionally */
+ default:
+ opline->op2.constant = zend_optimizer_add_literal(op_array, val);
+ break;
+ }
+
+ opline->op2_type = IS_CONST;
+ if (Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING) {
+ zend_string_hash_val(Z_STR(ZEND_OP2_LITERAL(opline)));
+ }
+ return 1;
+}
+
+int zend_optimizer_replace_by_const(zend_op_array *op_array,
+ zend_op *opline,
+ zend_uchar type,
+ uint32_t var,
+ zval *val)
+{
+ zend_op *end = op_array->opcodes + op_array->last;
+
+ while (opline < end) {
+ if (opline->op1_type == type &&
+ opline->op1.var == var) {
+ switch (opline->opcode) {
+ case ZEND_FETCH_DIM_W:
+ case ZEND_FETCH_DIM_RW:
+ case ZEND_FETCH_DIM_FUNC_ARG:
+ case ZEND_FETCH_DIM_UNSET:
+ case ZEND_FETCH_LIST_W:
+ case ZEND_ASSIGN_DIM:
+ case ZEND_SEPARATE:
+ case ZEND_RETURN_BY_REF:
+ return 0;
+ case ZEND_SEND_VAR:
+ opline->extended_value = 0;
+ opline->opcode = ZEND_SEND_VAL;
+ break;
+ case ZEND_SEND_VAR_EX:
+ case ZEND_SEND_FUNC_ARG:
+ opline->extended_value = 0;
+ opline->opcode = ZEND_SEND_VAL_EX;
+ break;
+ case ZEND_SEND_VAR_NO_REF:
+ return 0;
+ case ZEND_SEND_VAR_NO_REF_EX:
+ opline->opcode = ZEND_SEND_VAL;
+ break;
+ case ZEND_SEND_USER:
+ opline->opcode = ZEND_SEND_VAL_EX;
+ break;
+ /* In most cases IS_TMP_VAR operand may be used only once.
+ * The operands are usually destroyed by the opcode handler.
+ * ZEND_CASE[_STRICT] and ZEND_FETCH_LIST_R are exceptions, they keeps operand
+ * unchanged, and allows its reuse. these instructions
+ * usually terminated by ZEND_FREE that finally kills the value.
+ */
+ case ZEND_FETCH_LIST_R: {
+ zend_op *m = opline;
+
+ do {
+ if (m->opcode == ZEND_FETCH_LIST_R &&
+ m->op1_type == type &&
+ m->op1.var == var) {
+ zval v;
+ ZVAL_COPY(&v, val);
+ if (Z_TYPE(v) == IS_STRING) {
+ zend_string_hash_val(Z_STR(v));
+ }
+ m->op1.constant = zend_optimizer_add_literal(op_array, &v);
+ m->op1_type = IS_CONST;
+ }
+ m++;
+ } while (m->opcode != ZEND_FREE || m->op1_type != type || m->op1.var != var);
+
+ ZEND_ASSERT(m->opcode == ZEND_FREE && m->op1_type == type && m->op1.var == var);
+ MAKE_NOP(m);
+ zval_ptr_dtor_nogc(val);
+ return 1;
+ }
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ case ZEND_MATCH:
+ case ZEND_CASE:
+ case ZEND_CASE_STRICT: {
+ zend_op *end = op_array->opcodes + op_array->last;
+ while (opline < end) {
+ if (opline->op1_type == type && opline->op1.var == var) {
+ if (
+ opline->opcode == ZEND_CASE
+ || opline->opcode == ZEND_CASE_STRICT
+ || opline->opcode == ZEND_SWITCH_LONG
+ || opline->opcode == ZEND_SWITCH_STRING
+ || opline->opcode == ZEND_MATCH
+ ) {
+ zval v;
+
+ if (opline->opcode == ZEND_CASE) {
+ opline->opcode = ZEND_IS_EQUAL;
+ } else if (opline->opcode == ZEND_CASE_STRICT) {
+ opline->opcode = ZEND_IS_IDENTICAL;
+ }
+ ZVAL_COPY(&v, val);
+ if (Z_TYPE(v) == IS_STRING) {
+ zend_string_hash_val(Z_STR(v));
+ }
+ opline->op1.constant = zend_optimizer_add_literal(op_array, &v);
+ opline->op1_type = IS_CONST;
+ } else if (opline->opcode == ZEND_FREE) {
+ if (opline->extended_value == ZEND_FREE_SWITCH) {
+ /* We found the end of the switch. */
+ MAKE_NOP(opline);
+ break;
+ }
+
+ ZEND_ASSERT(opline->extended_value == ZEND_FREE_ON_RETURN);
+ MAKE_NOP(opline);
+ } else {
+ ZEND_UNREACHABLE();
+ }
+ }
+ opline++;
+ }
+ zval_ptr_dtor_nogc(val);
+ return 1;
+ }
+ case ZEND_VERIFY_RETURN_TYPE: {
+ zend_arg_info *ret_info = op_array->arg_info - 1;
+ if (!ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(val))
+ || (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE)) {
+ return 0;
+ }
+ MAKE_NOP(opline);
+
+ /* zend_handle_loops_and_finally may inserts other oplines */
+ do {
+ ++opline;
+ } while (opline->opcode != ZEND_RETURN && opline->opcode != ZEND_RETURN_BY_REF);
+ ZEND_ASSERT(opline->op1.var == var);
+
+ break;
+ }
+ default:
+ break;
+ }
+ return zend_optimizer_update_op1_const(op_array, opline, val);
+ }
+
+ if (opline->op2_type == type &&
+ opline->op2.var == var) {
+ return zend_optimizer_update_op2_const(op_array, opline, val);
+ }
+ opline++;
+ }
+
+ return 1;
+}
+
+/* Update jump offsets after a jump was migrated to another opline */
+void zend_optimizer_migrate_jump(zend_op_array *op_array, zend_op *new_opline, zend_op *opline) {
+ switch (new_opline->opcode) {
+ case ZEND_JMP:
+ case ZEND_FAST_CALL:
+ ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op1, ZEND_OP1_JMP_ADDR(opline));
+ break;
+ case ZEND_JMPZNZ:
+ new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
+ /* break missing intentionally */
+ case ZEND_JMPZ:
+ case ZEND_JMPNZ:
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ case ZEND_FE_RESET_R:
+ case ZEND_FE_RESET_RW:
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ case ZEND_ASSERT_CHECK:
+ case ZEND_JMP_NULL:
+ ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op2, ZEND_OP2_JMP_ADDR(opline));
+ break;
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
+ break;
+ case ZEND_CATCH:
+ if (!(opline->extended_value & ZEND_LAST_CATCH)) {
+ ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op2, ZEND_OP2_JMP_ADDR(opline));
+ }
+ break;
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ case ZEND_MATCH:
+ {
+ HashTable *jumptable = Z_ARRVAL(ZEND_OP2_LITERAL(opline));
+ zval *zv;
+ ZEND_HASH_FOREACH_VAL(jumptable, zv) {
+ Z_LVAL_P(zv) = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv)));
+ } ZEND_HASH_FOREACH_END();
+ new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
+ break;
+ }
+ }
+}
+
+/* Shift jump offsets based on shiftlist */
+void zend_optimizer_shift_jump(zend_op_array *op_array, zend_op *opline, uint32_t *shiftlist) {
+ switch (opline->opcode) {
+ case ZEND_JMP:
+ case ZEND_FAST_CALL:
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op1, ZEND_OP1_JMP_ADDR(opline) - shiftlist[ZEND_OP1_JMP_ADDR(opline) - op_array->opcodes]);
+ break;
+ case ZEND_JMPZNZ:
+ opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value) - shiftlist[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]);
+ /* break missing intentionally */
+ case ZEND_JMPZ:
+ case ZEND_JMPNZ:
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ case ZEND_FE_RESET_R:
+ case ZEND_FE_RESET_RW:
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ case ZEND_ASSERT_CHECK:
+ case ZEND_JMP_NULL:
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(opline) - shiftlist[ZEND_OP2_JMP_ADDR(opline) - op_array->opcodes]);
+ break;
+ case ZEND_CATCH:
+ if (!(opline->extended_value & ZEND_LAST_CATCH)) {
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(opline) - shiftlist[ZEND_OP2_JMP_ADDR(opline) - op_array->opcodes]);
+ }
+ break;
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value) - shiftlist[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]);
+ break;
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ case ZEND_MATCH:
+ {
+ HashTable *jumptable = Z_ARRVAL(ZEND_OP2_LITERAL(opline));
+ zval *zv;
+ ZEND_HASH_FOREACH_VAL(jumptable, zv) {
+ Z_LVAL_P(zv) = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv)) - shiftlist[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv))]);
+ } ZEND_HASH_FOREACH_END();
+ opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value) - shiftlist[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]);
+ break;
+ }
+ }
+}
+
+static zend_class_entry *get_class_entry_from_op1(
+ zend_script *script, zend_op_array *op_array, zend_op *opline) {
+ if (opline->op1_type == IS_CONST) {
+ zval *op1 = CRT_CONSTANT(opline->op1);
+ if (Z_TYPE_P(op1) == IS_STRING) {
+ zend_string *class_name = Z_STR_P(op1 + 1);
+ zend_class_entry *ce;
+ if (script && (ce = zend_hash_find_ptr(&script->class_table, class_name))) {
+ return ce;
+ } else if ((ce = zend_hash_find_ptr(EG(class_table), class_name))) {
+ if (ce->type == ZEND_INTERNAL_CLASS) {
+ return ce;
+ } else if (ce->type == ZEND_USER_CLASS &&
+ ce->info.user.filename &&
+ ce->info.user.filename == op_array->filename) {
+ return ce;
+ }
+ }
+ }
+ } else if (opline->op1_type == IS_UNUSED && op_array->scope
+ && !(op_array->scope->ce_flags & ZEND_ACC_TRAIT)
+ && (opline->op1.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_SELF) {
+ return op_array->scope;
+ }
+ return NULL;
+}
+
+zend_function *zend_optimizer_get_called_func(
+ zend_script *script, zend_op_array *op_array, zend_op *opline, bool *is_prototype)
+{
+ *is_prototype = 0;
+ switch (opline->opcode) {
+ case ZEND_INIT_FCALL:
+ {
+ zend_string *function_name = Z_STR_P(CRT_CONSTANT(opline->op2));
+ zend_function *func;
+ if (script && (func = zend_hash_find_ptr(&script->function_table, function_name)) != NULL) {
+ return func;
+ } else if ((func = zend_hash_find_ptr(EG(function_table), function_name)) != NULL) {
+ if (func->type == ZEND_INTERNAL_FUNCTION) {
+ return func;
+ } else if (func->type == ZEND_USER_FUNCTION &&
+ func->op_array.filename &&
+ func->op_array.filename == op_array->filename) {
+ return func;
+ }
+ }
+ break;
+ }
+ case ZEND_INIT_FCALL_BY_NAME:
+ case ZEND_INIT_NS_FCALL_BY_NAME:
+ if (opline->op2_type == IS_CONST && Z_TYPE_P(CRT_CONSTANT(opline->op2)) == IS_STRING) {
+ zval *function_name = CRT_CONSTANT(opline->op2) + 1;
+ zend_function *func;
+ if (script && (func = zend_hash_find_ptr(&script->function_table, Z_STR_P(function_name)))) {
+ return func;
+ } else if ((func = zend_hash_find_ptr(EG(function_table), Z_STR_P(function_name))) != NULL) {
+ if (func->type == ZEND_INTERNAL_FUNCTION) {
+ return func;
+ } else if (func->type == ZEND_USER_FUNCTION &&
+ func->op_array.filename &&
+ func->op_array.filename == op_array->filename) {
+ return func;
+ }
+ }
+ }
+ break;
+ case ZEND_INIT_STATIC_METHOD_CALL:
+ if (opline->op2_type == IS_CONST && Z_TYPE_P(CRT_CONSTANT(opline->op2)) == IS_STRING) {
+ zend_class_entry *ce = get_class_entry_from_op1(
+ script, op_array, opline);
+ if (ce) {
+ zend_string *func_name = Z_STR_P(CRT_CONSTANT(opline->op2) + 1);
+ zend_function *fbc = zend_hash_find_ptr(&ce->function_table, func_name);
+ if (fbc) {
+ bool is_public = (fbc->common.fn_flags & ZEND_ACC_PUBLIC) != 0;
+ bool same_scope = fbc->common.scope == op_array->scope;
+ if (is_public || same_scope) {
+ return fbc;
+ }
+ }
+ }
+ }
+ break;
+ case ZEND_INIT_METHOD_CALL:
+ if (opline->op1_type == IS_UNUSED
+ && opline->op2_type == IS_CONST && Z_TYPE_P(CRT_CONSTANT(opline->op2)) == IS_STRING
+ && op_array->scope
+ && !(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)
+ && !(op_array->scope->ce_flags & ZEND_ACC_TRAIT)) {
+ zend_string *method_name = Z_STR_P(CRT_CONSTANT(opline->op2) + 1);
+ zend_function *fbc = zend_hash_find_ptr(
+ &op_array->scope->function_table, method_name);
+ if (fbc) {
+ bool is_private = (fbc->common.fn_flags & ZEND_ACC_PRIVATE) != 0;
+ bool is_final = (fbc->common.fn_flags & ZEND_ACC_FINAL) != 0;
+ bool same_scope = fbc->common.scope == op_array->scope;
+ if (is_private) {
+ /* Only use private method if in the same scope. We can't even use it
+ * as a prototype, as it may be overridden with changed signature. */
+ return same_scope ? fbc : NULL;
+ }
+ /* If the method is non-final, it may be overridden,
+ * but only with a compatible method signature. */
+ *is_prototype = !is_final;
+ return fbc;
+ }
+ }
+ break;
+ case ZEND_NEW:
+ {
+ zend_class_entry *ce = get_class_entry_from_op1(
+ script, op_array, opline);
+ if (ce && ce->type == ZEND_USER_CLASS) {
+ return ce->constructor;
+ }
+ break;
+ }
+ }
+ return NULL;
+}
+
+uint32_t zend_optimizer_classify_function(zend_string *name, uint32_t num_args) {
+ if (zend_string_equals_literal(name, "extract")) {
+ return ZEND_FUNC_INDIRECT_VAR_ACCESS;
+ } else if (zend_string_equals_literal(name, "compact")) {
+ return ZEND_FUNC_INDIRECT_VAR_ACCESS;
+ } else if (zend_string_equals_literal(name, "get_defined_vars")) {
+ return ZEND_FUNC_INDIRECT_VAR_ACCESS;
+ } else if (zend_string_equals_literal(name, "db2_execute")) {
+ return ZEND_FUNC_INDIRECT_VAR_ACCESS;
+ } else if (zend_string_equals_literal(name, "func_num_args")) {
+ return ZEND_FUNC_VARARG;
+ } else if (zend_string_equals_literal(name, "func_get_arg")) {
+ return ZEND_FUNC_VARARG;
+ } else if (zend_string_equals_literal(name, "func_get_args")) {
+ return ZEND_FUNC_VARARG;
+ } else {
+ return 0;
+ }
+}
+
+zend_op *zend_optimizer_get_loop_var_def(const zend_op_array *op_array, zend_op *free_opline) {
+ uint32_t var = free_opline->op1.var;
+ ZEND_ASSERT(zend_optimizer_is_loop_var_free(free_opline));
+
+ while (--free_opline >= op_array->opcodes) {
+ if ((free_opline->result_type & (IS_TMP_VAR|IS_VAR)) && free_opline->result.var == var) {
+ return free_opline;
+ }
+ }
+ return NULL;
+}
+
+static void zend_optimize(zend_op_array *op_array,
+ zend_optimizer_ctx *ctx)
+{
+ if (op_array->type == ZEND_EVAL_CODE) {
+ return;
+ }
+
+ if (ctx->debug_level & ZEND_DUMP_BEFORE_OPTIMIZER) {
+ zend_dump_op_array(op_array, ZEND_DUMP_LIVE_RANGES, "before optimizer", NULL);
+ }
+
+ /* pass 1 (Simple local optimizations)
+ * - persistent constant substitution (true, false, null, etc)
+ * - constant casting (ADD expects numbers, CONCAT strings, etc)
+ * - constant expression evaluation
+ * - optimize constant conditional JMPs
+ * - pre-evaluate constant function calls
+ * - eliminate FETCH $GLOBALS followed by FETCH_DIM/UNSET_DIM/ISSET_ISEMPTY_DIM
+ */
+ if (ZEND_OPTIMIZER_PASS_1 & ctx->optimization_level) {
+ zend_optimizer_pass1(op_array, ctx);
+ if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_1) {
+ zend_dump_op_array(op_array, 0, "after pass 1", NULL);
+ }
+ }
+
+ /* pass 3: (Jump optimization)
+ * - optimize series of JMPs
+ */
+ if (ZEND_OPTIMIZER_PASS_3 & ctx->optimization_level) {
+ zend_optimizer_pass3(op_array, ctx);
+ if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_3) {
+ zend_dump_op_array(op_array, 0, "after pass 3", NULL);
+ }
+ }
+
+ /* pass 4:
+ * - INIT_FCALL_BY_NAME -> DO_FCALL
+ */
+ if (ZEND_OPTIMIZER_PASS_4 & ctx->optimization_level) {
+ zend_optimize_func_calls(op_array, ctx);
+ if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_4) {
+ zend_dump_op_array(op_array, 0, "after pass 4", NULL);
+ }
+ }
+
+ /* pass 5:
+ * - CFG optimization
+ */
+ if (ZEND_OPTIMIZER_PASS_5 & ctx->optimization_level) {
+ zend_optimize_cfg(op_array, ctx);
+ if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_5) {
+ zend_dump_op_array(op_array, 0, "after pass 5", NULL);
+ }
+ }
+
+ /* pass 6:
+ * - DFA optimization
+ */
+ if ((ZEND_OPTIMIZER_PASS_6 & ctx->optimization_level) &&
+ !(ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level)) {
+ zend_optimize_dfa(op_array, ctx);
+ if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_6) {
+ zend_dump_op_array(op_array, 0, "after pass 6", NULL);
+ }
+ }
+
+ /* pass 9:
+ * - Optimize temp variables usage
+ */
+ if (ZEND_OPTIMIZER_PASS_9 & ctx->optimization_level) {
+ zend_optimize_temporary_variables(op_array, ctx);
+ if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_9) {
+ zend_dump_op_array(op_array, 0, "after pass 9", NULL);
+ }
+ }
+
+ /* pass 10:
+ * - remove NOPs
+ */
+ if (((ZEND_OPTIMIZER_PASS_10|ZEND_OPTIMIZER_PASS_5) & ctx->optimization_level) == ZEND_OPTIMIZER_PASS_10) {
+ zend_optimizer_nop_removal(op_array, ctx);
+ if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_10) {
+ zend_dump_op_array(op_array, 0, "after pass 10", NULL);
+ }
+ }
+
+ /* pass 11:
+ * - Compact literals table
+ */
+ 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;
+ }
+
+ if (ctx->debug_level & ZEND_DUMP_AFTER_OPTIMIZER) {
+ zend_dump_op_array(op_array, 0, "after optimizer", NULL);
+ }
+}
+
+static void zend_revert_pass_two(zend_op_array *op_array)
+{
+ zend_op *opline, *end;
+
+ ZEND_ASSERT((op_array->fn_flags & ZEND_ACC_DONE_PASS_TWO) != 0);
+
+ opline = op_array->opcodes;
+ end = opline + op_array->last;
+ while (opline < end) {
+ if (opline->op1_type == IS_CONST) {
+ ZEND_PASS_TWO_UNDO_CONSTANT(op_array, opline, opline->op1);
+ }
+ if (opline->op2_type == IS_CONST) {
+ ZEND_PASS_TWO_UNDO_CONSTANT(op_array, opline, opline->op2);
+ }
+ /* reset smart branch flags IS_SMART_BRANCH_JMP[N]Z */
+ opline->result_type &= (IS_TMP_VAR|IS_VAR|IS_CV|IS_CONST);
+ opline++;
+ }
+#if !ZEND_USE_ABS_CONST_ADDR
+ if (op_array->literals) {
+ zval *literals = emalloc(sizeof(zval) * op_array->last_literal);
+ memcpy(literals, op_array->literals, sizeof(zval) * op_array->last_literal);
+ op_array->literals = literals;
+ }
+#endif
+
+ op_array->fn_flags &= ~ZEND_ACC_DONE_PASS_TWO;
+}
+
+static void zend_redo_pass_two(zend_op_array *op_array)
+{
+ zend_op *opline, *end;
+#if ZEND_USE_ABS_JMP_ADDR && !ZEND_USE_ABS_CONST_ADDR
+ zend_op *old_opcodes = op_array->opcodes;
+#endif
+
+ ZEND_ASSERT((op_array->fn_flags & ZEND_ACC_DONE_PASS_TWO) == 0);
+
+#if !ZEND_USE_ABS_CONST_ADDR
+ if (op_array->last_literal) {
+ op_array->opcodes = (zend_op *) erealloc(op_array->opcodes,
+ ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op) * op_array->last, 16) +
+ sizeof(zval) * op_array->last_literal);
+ memcpy(((char*)op_array->opcodes) + ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op) * op_array->last, 16),
+ op_array->literals, sizeof(zval) * op_array->last_literal);
+ efree(op_array->literals);
+ op_array->literals = (zval*)(((char*)op_array->opcodes) + ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op) * op_array->last, 16));
+ } else {
+ if (op_array->literals) {
+ efree(op_array->literals);
+ }
+ op_array->literals = NULL;
+ }
+#endif
+
+ opline = op_array->opcodes;
+ end = opline + op_array->last;
+ while (opline < end) {
+ if (opline->op1_type == IS_CONST) {
+ ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline, opline->op1);
+ }
+ if (opline->op2_type == IS_CONST) {
+ ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline, opline->op2);
+ }
+ /* fix jumps to point to new array */
+ switch (opline->opcode) {
+#if ZEND_USE_ABS_JMP_ADDR && !ZEND_USE_ABS_CONST_ADDR
+ case ZEND_JMP:
+ case ZEND_FAST_CALL:
+ opline->op1.jmp_addr = &op_array->opcodes[opline->op1.jmp_addr - old_opcodes];
+ break;
+ case ZEND_JMPZNZ:
+ /* relative extended_value don't have to be changed */
+ /* break omitted intentionally */
+ case ZEND_JMPZ:
+ case ZEND_JMPNZ:
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ case ZEND_FE_RESET_R:
+ case ZEND_FE_RESET_RW:
+ case ZEND_ASSERT_CHECK:
+ case ZEND_JMP_NULL:
+ opline->op2.jmp_addr = &op_array->opcodes[opline->op2.jmp_addr - old_opcodes];
+ break;
+ case ZEND_CATCH:
+ if (!(opline->extended_value & ZEND_LAST_CATCH)) {
+ opline->op2.jmp_addr = &op_array->opcodes[opline->op2.jmp_addr - old_opcodes];
+ }
+ break;
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ case ZEND_MATCH:
+ /* relative extended_value don't have to be changed */
+ break;
+#endif
+ case ZEND_IS_IDENTICAL:
+ case ZEND_IS_NOT_IDENTICAL:
+ case ZEND_IS_EQUAL:
+ case ZEND_IS_NOT_EQUAL:
+ case ZEND_IS_SMALLER:
+ case ZEND_IS_SMALLER_OR_EQUAL:
+ case ZEND_CASE:
+ case ZEND_CASE_STRICT:
+ case ZEND_ISSET_ISEMPTY_CV:
+ case ZEND_ISSET_ISEMPTY_VAR:
+ case ZEND_ISSET_ISEMPTY_DIM_OBJ:
+ case ZEND_ISSET_ISEMPTY_PROP_OBJ:
+ case ZEND_ISSET_ISEMPTY_STATIC_PROP:
+ case ZEND_INSTANCEOF:
+ case ZEND_TYPE_CHECK:
+ case ZEND_DEFINED:
+ case ZEND_IN_ARRAY:
+ case ZEND_ARRAY_KEY_EXISTS:
+ if (opline->result_type & IS_TMP_VAR) {
+ /* reinitialize result_type of smart branch instructions */
+ if (opline + 1 < end) {
+ if ((opline+1)->opcode == ZEND_JMPZ
+ && (opline+1)->op1_type == IS_TMP_VAR
+ && (opline+1)->op1.var == opline->result.var) {
+ opline->result_type = IS_SMART_BRANCH_JMPZ | IS_TMP_VAR;
+ } else if ((opline+1)->opcode == ZEND_JMPNZ
+ && (opline+1)->op1_type == IS_TMP_VAR
+ && (opline+1)->op1.var == opline->result.var) {
+ opline->result_type = IS_SMART_BRANCH_JMPNZ | IS_TMP_VAR;
+ }
+ }
+ }
+ break;
+ }
+ ZEND_VM_SET_OPCODE_HANDLER(opline);
+ opline++;
+ }
+
+ op_array->fn_flags |= ZEND_ACC_DONE_PASS_TWO;
+}
+
+static void zend_redo_pass_two_ex(zend_op_array *op_array, zend_ssa *ssa)
+{
+ zend_op *opline, *end;
+#if ZEND_USE_ABS_JMP_ADDR && !ZEND_USE_ABS_CONST_ADDR
+ zend_op *old_opcodes = op_array->opcodes;
+#endif
+
+ ZEND_ASSERT((op_array->fn_flags & ZEND_ACC_DONE_PASS_TWO) == 0);
+
+#if !ZEND_USE_ABS_CONST_ADDR
+ if (op_array->last_literal) {
+ op_array->opcodes = (zend_op *) erealloc(op_array->opcodes,
+ ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op) * op_array->last, 16) +
+ sizeof(zval) * op_array->last_literal);
+ memcpy(((char*)op_array->opcodes) + ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op) * op_array->last, 16),
+ op_array->literals, sizeof(zval) * op_array->last_literal);
+ efree(op_array->literals);
+ op_array->literals = (zval*)(((char*)op_array->opcodes) + ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op) * op_array->last, 16));
+ } else {
+ if (op_array->literals) {
+ efree(op_array->literals);
+ }
+ op_array->literals = NULL;
+ }
+#endif
+
+ opline = op_array->opcodes;
+ end = opline + op_array->last;
+ while (opline < end) {
+ zend_ssa_op *ssa_op = &ssa->ops[opline - op_array->opcodes];
+ uint32_t op1_info = opline->op1_type == IS_UNUSED ? 0 : (OP1_INFO() & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_KEY_ANY));
+ uint32_t op2_info = opline->op1_type == IS_UNUSED ? 0 : (OP2_INFO() & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_KEY_ANY));
+ uint32_t res_info =
+ (opline->opcode == ZEND_PRE_INC ||
+ opline->opcode == ZEND_PRE_DEC ||
+ opline->opcode == ZEND_POST_INC ||
+ opline->opcode == ZEND_POST_DEC) ?
+ ((ssa->ops[opline - op_array->opcodes].op1_def >= 0) ? (OP1_DEF_INFO() & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_KEY_ANY)) : MAY_BE_ANY) :
+ (opline->result_type == IS_UNUSED ? 0 : (RES_INFO() & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_KEY_ANY)));
+
+ if (opline->op1_type == IS_CONST) {
+ ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline, opline->op1);
+ }
+ if (opline->op2_type == IS_CONST) {
+ ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline, opline->op2);
+ }
+
+ /* fix jumps to point to new array */
+ switch (opline->opcode) {
+#if ZEND_USE_ABS_JMP_ADDR && !ZEND_USE_ABS_CONST_ADDR
+ case ZEND_JMP:
+ case ZEND_FAST_CALL:
+ opline->op1.jmp_addr = &op_array->opcodes[opline->op1.jmp_addr - old_opcodes];
+ break;
+ case ZEND_JMPZNZ:
+ /* relative extended_value don't have to be changed */
+ /* break omitted intentionally */
+ case ZEND_JMPZ:
+ case ZEND_JMPNZ:
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ case ZEND_FE_RESET_R:
+ case ZEND_FE_RESET_RW:
+ case ZEND_ASSERT_CHECK:
+ case ZEND_JMP_NULL:
+ opline->op2.jmp_addr = &op_array->opcodes[opline->op2.jmp_addr - old_opcodes];
+ break;
+ case ZEND_CATCH:
+ if (!(opline->extended_value & ZEND_LAST_CATCH)) {
+ opline->op2.jmp_addr = &op_array->opcodes[opline->op2.jmp_addr - old_opcodes];
+ }
+ break;
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ case ZEND_MATCH:
+ /* relative extended_value don't have to be changed */
+ break;
+#endif
+ case ZEND_IS_IDENTICAL:
+ case ZEND_IS_NOT_IDENTICAL:
+ case ZEND_IS_EQUAL:
+ case ZEND_IS_NOT_EQUAL:
+ case ZEND_IS_SMALLER:
+ case ZEND_IS_SMALLER_OR_EQUAL:
+ case ZEND_CASE:
+ case ZEND_CASE_STRICT:
+ case ZEND_ISSET_ISEMPTY_CV:
+ case ZEND_ISSET_ISEMPTY_VAR:
+ case ZEND_ISSET_ISEMPTY_DIM_OBJ:
+ case ZEND_ISSET_ISEMPTY_PROP_OBJ:
+ case ZEND_ISSET_ISEMPTY_STATIC_PROP:
+ case ZEND_INSTANCEOF:
+ case ZEND_TYPE_CHECK:
+ case ZEND_DEFINED:
+ case ZEND_IN_ARRAY:
+ case ZEND_ARRAY_KEY_EXISTS:
+ if (opline->result_type & IS_TMP_VAR) {
+ /* reinitialize result_type of smart branch instructions */
+ if (opline + 1 < end) {
+ if ((opline+1)->opcode == ZEND_JMPZ
+ && (opline+1)->op1_type == IS_TMP_VAR
+ && (opline+1)->op1.var == opline->result.var) {
+ opline->result_type = IS_SMART_BRANCH_JMPZ | IS_TMP_VAR;
+ } else if ((opline+1)->opcode == ZEND_JMPNZ
+ && (opline+1)->op1_type == IS_TMP_VAR
+ && (opline+1)->op1.var == opline->result.var) {
+ opline->result_type = IS_SMART_BRANCH_JMPNZ | IS_TMP_VAR;
+ }
+ }
+ }
+ break;
+ }
+ zend_vm_set_opcode_handler_ex(opline, op1_info, op2_info, res_info);
+ opline++;
+ }
+
+ op_array->fn_flags |= ZEND_ACC_DONE_PASS_TWO;
+}
+
+static void zend_optimize_op_array(zend_op_array *op_array,
+ zend_optimizer_ctx *ctx)
+{
+ /* Revert pass_two() */
+ zend_revert_pass_two(op_array);
+
+ /* Do actual optimizations */
+ zend_optimize(op_array, ctx);
+
+ /* Redo pass_two() */
+ zend_redo_pass_two(op_array);
+
+ if (op_array->live_range) {
+ zend_recalc_live_ranges(op_array, NULL);
+ }
+}
+
+static void zend_adjust_fcall_stack_size(zend_op_array *op_array, zend_optimizer_ctx *ctx)
+{
+ zend_function *func;
+ zend_op *opline, *end;
+
+ opline = op_array->opcodes;
+ end = opline + op_array->last;
+ while (opline < end) {
+ if (opline->opcode == ZEND_INIT_FCALL) {
+ func = zend_hash_find_ptr(
+ &ctx->script->function_table,
+ Z_STR_P(RT_CONSTANT(opline, opline->op2)));
+ if (func) {
+ opline->op1.num = zend_vm_calc_used_stack(opline->extended_value, func);
+ }
+ }
+ opline++;
+ }
+}
+
+static void zend_adjust_fcall_stack_size_graph(zend_op_array *op_array)
+{
+ zend_func_info *func_info = ZEND_FUNC_INFO(op_array);
+
+ if (func_info) {
+ zend_call_info *call_info =func_info->callee_info;
+
+ while (call_info) {
+ zend_op *opline = call_info->caller_init_opline;
+
+ if (opline && call_info->callee_func && opline->opcode == ZEND_INIT_FCALL) {
+ opline->op1.num = zend_vm_calc_used_stack(opline->extended_value, call_info->callee_func);
+ }
+ call_info = call_info->next_callee;
+ }
+ }
+}
+
+static bool needs_live_range(zend_op_array *op_array, zend_op *def_opline) {
+ zend_func_info *func_info = ZEND_FUNC_INFO(op_array);
+ zend_ssa_op *ssa_op = &func_info->ssa.ops[def_opline - op_array->opcodes];
+ int ssa_var = ssa_op->result_def;
+ if (ssa_var < 0) {
+ /* Be conservative. */
+ return 1;
+ }
+
+ /* If the variable is used by a PHI, this may be the assignment of the final branch of a
+ * ternary/etc structure. While this is where the live range starts, the value from the other
+ * branch may also be used. As such, use the type of the PHI node for the following check. */
+ if (func_info->ssa.vars[ssa_var].phi_use_chain) {
+ ssa_var = func_info->ssa.vars[ssa_var].phi_use_chain->ssa_var;
+ }
+
+ uint32_t type = func_info->ssa.var_info[ssa_var].type;
+ return (type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) != 0;
+}
+
+void zend_foreach_op_array(zend_script *script, zend_op_array_func_t func, void *context)
+{
+ zend_class_entry *ce;
+ zend_string *key;
+ zend_op_array *op_array;
+
+ func(&script->main_op_array, context);
+
+ ZEND_HASH_FOREACH_PTR(&script->function_table, op_array) {
+ func(op_array, context);
+ } ZEND_HASH_FOREACH_END();
+
+ ZEND_HASH_FOREACH_STR_KEY_PTR(&script->class_table, key, ce) {
+ if (ce->refcount > 1 && !zend_string_equals_ci(key, ce->name)) {
+ continue;
+ }
+ ZEND_HASH_FOREACH_PTR(&ce->function_table, op_array) {
+ if (op_array->scope == ce
+ && op_array->type == ZEND_USER_FUNCTION
+ && !(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) {
+ func(op_array, context);
+ }
+ } ZEND_HASH_FOREACH_END();
+ } ZEND_HASH_FOREACH_END();
+}
+
+static void step_optimize_op_array(zend_op_array *op_array, void *context) {
+ zend_optimize_op_array(op_array, (zend_optimizer_ctx *) context);
+}
+
+static void step_adjust_fcall_stack_size(zend_op_array *op_array, void *context) {
+ zend_adjust_fcall_stack_size(op_array, (zend_optimizer_ctx *) context);
+}
+
+static void step_dump_after_optimizer(zend_op_array *op_array, void *context) {
+ zend_dump_op_array(op_array, ZEND_DUMP_LIVE_RANGES, "after optimizer", NULL);
+}
+
+ZEND_API int zend_optimize_script(zend_script *script, zend_long optimization_level, zend_long debug_level)
+{
+ zend_class_entry *ce;
+ zend_string *key;
+ zend_op_array *op_array;
+ zend_string *name;
+ zend_optimizer_ctx ctx;
+ zend_call_graph call_graph;
+
+ ctx.arena = zend_arena_create(64 * 1024);
+ ctx.script = script;
+ ctx.constants = NULL;
+ ctx.optimization_level = optimization_level;
+ ctx.debug_level = debug_level;
+
+ if ((ZEND_OPTIMIZER_PASS_6 & optimization_level) &&
+ (ZEND_OPTIMIZER_PASS_7 & optimization_level) &&
+ zend_build_call_graph(&ctx.arena, script, &call_graph) == SUCCESS) {
+ /* Optimize using call-graph */
+ int i;
+ zend_func_info *func_info;
+
+ for (i = 0; i < call_graph.op_arrays_count; i++) {
+ zend_revert_pass_two(call_graph.op_arrays[i]);
+ zend_optimize(call_graph.op_arrays[i], &ctx);
+ }
+
+ zend_analyze_call_graph(&ctx.arena, script, &call_graph);
+
+ for (i = 0; i < call_graph.op_arrays_count; i++) {
+ func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]);
+ if (func_info) {
+ func_info->call_map = zend_build_call_map(&ctx.arena, func_info, call_graph.op_arrays[i]);
+ if (call_graph.op_arrays[i]->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+ zend_init_func_return_info(call_graph.op_arrays[i], script, &func_info->return_info);
+ }
+ }
+ }
+
+ for (i = 0; i < call_graph.op_arrays_count; i++) {
+ func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]);
+ if (func_info) {
+ if (zend_dfa_analyze_op_array(call_graph.op_arrays[i], &ctx, &func_info->ssa) == SUCCESS) {
+ func_info->flags = func_info->ssa.cfg.flags;
+ } else {
+ ZEND_SET_FUNC_INFO(call_graph.op_arrays[i], NULL);
+ }
+ }
+ }
+
+ //TODO: perform inner-script inference???
+ 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, func_info->call_map);
+ }
+ }
+
+ if (debug_level & ZEND_DUMP_AFTER_PASS_7) {
+ for (i = 0; i < call_graph.op_arrays_count; i++) {
+ zend_dump_op_array(call_graph.op_arrays[i], 0, "after pass 7", NULL);
+ }
+ }
+
+ 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]);
+ }
+ }
+
+ for (i = 0; i < call_graph.op_arrays_count; i++) {
+ op_array = call_graph.op_arrays[i];
+ func_info = ZEND_FUNC_INFO(op_array);
+ if (func_info && func_info->ssa.var_info) {
+ zend_redo_pass_two_ex(op_array, &func_info->ssa);
+ if (op_array->live_range) {
+ zend_recalc_live_ranges(op_array, needs_live_range);
+ }
+ } else {
+ zend_redo_pass_two(op_array);
+ if (op_array->live_range) {
+ zend_recalc_live_ranges(op_array, NULL);
+ }
+ }
+ }
+
+ for (i = 0; i < call_graph.op_arrays_count; i++) {
+ ZEND_SET_FUNC_INFO(call_graph.op_arrays[i], NULL);
+ }
+ } else {
+ zend_foreach_op_array(script, step_optimize_op_array, &ctx);
+
+ if (ZEND_OPTIMIZER_PASS_12 & optimization_level) {
+ zend_foreach_op_array(script, step_adjust_fcall_stack_size, &ctx);
+ }
+ }
+
+ ZEND_HASH_FOREACH_STR_KEY_PTR(&script->class_table, key, ce) {
+ if (ce->refcount > 1 && !zend_string_equals_ci(key, ce->name)) {
+ continue;
+ }
+ ZEND_HASH_FOREACH_STR_KEY_PTR(&ce->function_table, name, op_array) {
+ if (op_array->scope != ce && op_array->type == ZEND_USER_FUNCTION) {
+ zend_op_array *orig_op_array =
+ zend_hash_find_ptr(&op_array->scope->function_table, name);
+
+ ZEND_ASSERT(orig_op_array != NULL);
+ if (orig_op_array != op_array) {
+ uint32_t fn_flags = op_array->fn_flags;
+ zend_function *prototype = op_array->prototype;
+ HashTable *ht = op_array->static_variables;
+
+ *op_array = *orig_op_array;
+ op_array->fn_flags = fn_flags;
+ op_array->prototype = prototype;
+ op_array->static_variables = ht;
+ }
+ }
+ } ZEND_HASH_FOREACH_END();
+ } ZEND_HASH_FOREACH_END();
+
+ if ((debug_level & ZEND_DUMP_AFTER_OPTIMIZER) &&
+ (ZEND_OPTIMIZER_PASS_7 & optimization_level)) {
+ zend_foreach_op_array(script, step_dump_after_optimizer, NULL);
+ }
+
+ if (ctx.constants) {
+ zend_hash_destroy(ctx.constants);
+ }
+ zend_arena_destroy(ctx.arena);
+
+ return 1;
+}
+
+int zend_optimizer_startup(void)
+{
+ return zend_func_info_startup();
+}
+
+int zend_optimizer_shutdown(void)
+{
+ return zend_func_info_shutdown();
+}
diff --git a/Zend/Optimizer/zend_optimizer.h b/Zend/Optimizer/zend_optimizer.h
new file mode 100644
index 0000000000..4b43255437
--- /dev/null
+++ b/Zend/Optimizer/zend_optimizer.h
@@ -0,0 +1,98 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend OPcache |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Andi Gutmans <andi@php.net> |
+ | Zeev Suraski <zeev@php.net> |
+ | Stanislav Malyshev <stas@zend.com> |
+ | Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef ZEND_OPTIMIZER_H
+#define ZEND_OPTIMIZER_H
+
+#include "zend.h"
+#include "zend_compile.h"
+
+#define ZEND_OPTIMIZER_PASS_1 (1<<0) /* Simple local optimizations */
+#define ZEND_OPTIMIZER_PASS_2 (1<<1) /* */
+#define ZEND_OPTIMIZER_PASS_3 (1<<2) /* Jump optimization */
+#define ZEND_OPTIMIZER_PASS_4 (1<<3) /* INIT_FCALL_BY_NAME -> DO_FCALL */
+#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) /* 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) /* Remove unused variables */
+#define ZEND_OPTIMIZER_PASS_14 (1<<13) /* DCE (dead code elimination) */
+#define ZEND_OPTIMIZER_PASS_15 (1<<14) /* (unsafe) Collect constants */
+#define ZEND_OPTIMIZER_PASS_16 (1<<15) /* Inline functions */
+
+#define ZEND_OPTIMIZER_IGNORE_OVERLOADING (1<<16) /* (unsafe) Ignore possibility of operator overloading */
+
+#define ZEND_OPTIMIZER_ALL_PASSES 0x7FFFFFFF
+
+#define DEFAULT_OPTIMIZATION_LEVEL "0x7FFEBFFF"
+
+
+#define ZEND_DUMP_AFTER_PASS_1 ZEND_OPTIMIZER_PASS_1
+#define ZEND_DUMP_AFTER_PASS_2 ZEND_OPTIMIZER_PASS_2
+#define ZEND_DUMP_AFTER_PASS_3 ZEND_OPTIMIZER_PASS_3
+#define ZEND_DUMP_AFTER_PASS_4 ZEND_OPTIMIZER_PASS_4
+#define ZEND_DUMP_AFTER_PASS_5 ZEND_OPTIMIZER_PASS_5
+#define ZEND_DUMP_AFTER_PASS_6 ZEND_OPTIMIZER_PASS_6
+#define ZEND_DUMP_AFTER_PASS_7 ZEND_OPTIMIZER_PASS_7
+#define ZEND_DUMP_AFTER_PASS_8 ZEND_OPTIMIZER_PASS_8
+#define ZEND_DUMP_AFTER_PASS_9 ZEND_OPTIMIZER_PASS_9
+#define ZEND_DUMP_AFTER_PASS_10 ZEND_OPTIMIZER_PASS_10
+#define ZEND_DUMP_AFTER_PASS_11 ZEND_OPTIMIZER_PASS_11
+#define ZEND_DUMP_AFTER_PASS_12 ZEND_OPTIMIZER_PASS_12
+#define ZEND_DUMP_AFTER_PASS_13 ZEND_OPTIMIZER_PASS_13
+#define ZEND_DUMP_AFTER_PASS_14 ZEND_OPTIMIZER_PASS_14
+
+#define ZEND_DUMP_BEFORE_OPTIMIZER (1<<16)
+#define ZEND_DUMP_AFTER_OPTIMIZER (1<<17)
+
+#define ZEND_DUMP_BEFORE_BLOCK_PASS (1<<18)
+#define ZEND_DUMP_AFTER_BLOCK_PASS (1<<19)
+#define ZEND_DUMP_BLOCK_PASS_VARS (1<<20)
+
+#define ZEND_DUMP_BEFORE_DFA_PASS (1<<21)
+#define ZEND_DUMP_AFTER_DFA_PASS (1<<22)
+#define ZEND_DUMP_DFA_CFG (1<<23)
+#define ZEND_DUMP_DFA_DOMINATORS (1<<24)
+#define ZEND_DUMP_DFA_LIVENESS (1<<25)
+#define ZEND_DUMP_DFA_PHI (1<<26)
+#define ZEND_DUMP_DFA_SSA (1<<27)
+#define ZEND_DUMP_DFA_SSA_VARS (1<<28)
+#define ZEND_DUMP_SCCP (1<<29)
+
+typedef struct _zend_script {
+ zend_string *filename;
+ zend_op_array main_op_array;
+ HashTable function_table;
+ HashTable class_table;
+ uint32_t first_early_binding_opline; /* the linked list of delayed declarations */
+} zend_script;
+
+BEGIN_EXTERN_C()
+ZEND_API int zend_optimize_script(zend_script *script, zend_long optimization_level, zend_long debug_level);
+int zend_optimizer_startup(void);
+int zend_optimizer_shutdown(void);
+END_EXTERN_C()
+
+#endif
diff --git a/Zend/Optimizer/zend_optimizer_internal.h b/Zend/Optimizer/zend_optimizer_internal.h
new file mode 100644
index 0000000000..911eb79e64
--- /dev/null
+++ b/Zend/Optimizer/zend_optimizer_internal.h
@@ -0,0 +1,123 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend OPcache |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Andi Gutmans <andi@php.net> |
+ | Zeev Suraski <zeev@php.net> |
+ | Stanislav Malyshev <stas@zend.com> |
+ | Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef ZEND_OPTIMIZER_INTERNAL_H
+#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)
+#define ZEND_OP2_LITERAL(opline) (op_array)->literals[(opline)->op2.constant]
+#define ZEND_OP2_JMP_ADDR(opline) OP_JMP_ADDR(opline, (opline)->op2)
+
+#define VAR_NUM(v) EX_VAR_TO_NUM(v)
+#define NUM_VAR(v) EX_NUM_TO_VAR(v)
+
+#define INV_COND(op) ((op) == ZEND_JMPZ ? ZEND_JMPNZ : ZEND_JMPZ)
+#define INV_EX_COND(op) ((op) == ZEND_JMPZ_EX ? ZEND_JMPNZ : ZEND_JMPZ)
+#define INV_COND_EX(op) ((op) == ZEND_JMPZ ? ZEND_JMPNZ_EX : ZEND_JMPZ_EX)
+#define INV_EX_COND_EX(op) ((op) == ZEND_JMPZ_EX ? ZEND_JMPNZ_EX : ZEND_JMPZ_EX)
+
+#define RESULT_UNUSED(op) (op->result_type == IS_UNUSED)
+#define SAME_VAR(op1, op2) (op1 ## _type == op2 ## _type && op1.var == op2.var)
+
+typedef struct _zend_optimizer_ctx {
+ zend_arena *arena;
+ zend_script *script;
+ HashTable *constants;
+ zend_long optimization_level;
+ zend_long debug_level;
+} zend_optimizer_ctx;
+
+#define LITERAL_LONG(op, val) do { \
+ zval _c; \
+ ZVAL_LONG(&_c, val); \
+ op.constant = zend_optimizer_add_literal(op_array, &_c); \
+ } while (0)
+
+#define LITERAL_BOOL(op, val) do { \
+ zval _c; \
+ ZVAL_BOOL(&_c, val); \
+ op.constant = zend_optimizer_add_literal(op_array, &_c); \
+ } while (0)
+
+#define literal_dtor(zv) do { \
+ zval_ptr_dtor_nogc(zv); \
+ ZVAL_NULL(zv); \
+ } while (0)
+
+#define COPY_NODE(target, src) do { \
+ target ## _type = src ## _type; \
+ target = src; \
+ } while (0)
+
+static inline bool zend_optimizer_is_loop_var_free(const zend_op *opline) {
+ return (opline->opcode == ZEND_FE_FREE && opline->extended_value != ZEND_FREE_ON_RETURN)
+ || (opline->opcode == ZEND_FREE && opline->extended_value == ZEND_FREE_SWITCH);
+}
+
+int zend_optimizer_add_literal(zend_op_array *op_array, zval *zv);
+int zend_optimizer_get_persistent_constant(zend_string *name, zval *result, int copy);
+void zend_optimizer_collect_constant(zend_optimizer_ctx *ctx, zval *name, zval* value);
+int zend_optimizer_get_collected_constant(HashTable *constants, zval *name, zval* value);
+int zend_optimizer_eval_binary_op(zval *result, zend_uchar opcode, zval *op1, zval *op2);
+int zend_optimizer_eval_unary_op(zval *result, zend_uchar opcode, zval *op1);
+int zend_optimizer_eval_cast(zval *result, uint32_t type, zval *op1);
+int zend_optimizer_eval_strlen(zval *result, zval *op1);
+int zend_optimizer_update_op1_const(zend_op_array *op_array,
+ zend_op *opline,
+ zval *val);
+int zend_optimizer_update_op2_const(zend_op_array *op_array,
+ zend_op *opline,
+ zval *val);
+int zend_optimizer_replace_by_const(zend_op_array *op_array,
+ zend_op *opline,
+ zend_uchar type,
+ uint32_t var,
+ zval *val);
+zend_op *zend_optimizer_get_loop_var_def(const zend_op_array *op_array, zend_op *free_opline);
+
+void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx);
+void zend_optimizer_pass3(zend_op_array *op_array, zend_optimizer_ctx *ctx);
+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);
+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, zend_optimizer_ctx *ctx);
+void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx *ctx);
+void zend_optimizer_compact_vars(zend_op_array *op_array);
+zend_function *zend_optimizer_get_called_func(
+ zend_script *script, zend_op_array *op_array, zend_op *opline, bool *is_prototype);
+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);
+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, bool reorder_dtor_effects);
+int zend_ssa_escape_analysis(const zend_script *script, zend_op_array *op_array, zend_ssa *ssa);
+
+typedef void (*zend_op_array_func_t)(zend_op_array *, void *context);
+void zend_foreach_op_array(zend_script *script, zend_op_array_func_t func, void *context);
+
+#endif
diff --git a/Zend/Optimizer/zend_ssa.c b/Zend/Optimizer/zend_ssa.c
new file mode 100644
index 0000000000..6fe97cbc1e
--- /dev/null
+++ b/Zend/Optimizer/zend_ssa.c
@@ -0,0 +1,1628 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, SSA - Static Single Assignment Form |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Dmitry Stogov <dmitry@php.net> |
+ | Nikita Popov <nikic@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#include "php.h"
+#include "zend_compile.h"
+#include "zend_dfg.h"
+#include "zend_ssa.h"
+#include "zend_dump.h"
+#include "zend_inference.h"
+#include "Optimizer/zend_optimizer_internal.h"
+
+static bool dominates(const zend_basic_block *blocks, int a, int b) {
+ while (blocks[b].level > blocks[a].level) {
+ b = blocks[b].idom;
+ }
+ return a == b;
+}
+
+static bool will_rejoin(
+ const zend_cfg *cfg, const zend_dfg *dfg, const zend_basic_block *block,
+ int other_successor, int exclude, int var) {
+ int i;
+ for (i = 0; i < block->predecessors_count; i++) {
+ int predecessor = cfg->predecessors[block->predecessor_offset + i];
+ if (predecessor == exclude) {
+ continue;
+ }
+
+ /* The variable is changed in this predecessor,
+ * so we will not rejoin with the original value. */
+ // TODO: This should not be limited to the direct predecessor block.
+ if (DFG_ISSET(dfg->def, dfg->size, predecessor, var)) {
+ continue;
+ }
+
+ /* The other successor dominates this predecessor,
+ * so we will get the original value from it. */
+ if (dominates(cfg->blocks, other_successor, predecessor)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static bool needs_pi(const zend_op_array *op_array, zend_dfg *dfg, zend_ssa *ssa, int from, int to, int var) /* {{{ */
+{
+ zend_basic_block *from_block, *to_block;
+ int other_successor;
+
+ if (!DFG_ISSET(dfg->in, dfg->size, to, var)) {
+ /* Variable is not live, certainly won't benefit from pi */
+ return 0;
+ }
+
+ /* Make sure that both successors of the from block aren't the same. Pi nodes are associated
+ * with predecessor blocks, so we can't distinguish which edge the pi belongs to. */
+ from_block = &ssa->cfg.blocks[from];
+ ZEND_ASSERT(from_block->successors_count == 2);
+ if (from_block->successors[0] == from_block->successors[1]) {
+ return 0;
+ }
+
+ to_block = &ssa->cfg.blocks[to];
+ if (to_block->predecessors_count == 1) {
+ /* Always place pi if one predecessor (an if branch) */
+ return 1;
+ }
+
+ /* Check whether we will rejoin with the original value coming from the other successor,
+ * in which case the pi node will not have an effect. */
+ other_successor = from_block->successors[0] == to
+ ? from_block->successors[1] : from_block->successors[0];
+ return !will_rejoin(&ssa->cfg, dfg, to_block, other_successor, from, var);
+}
+/* }}} */
+
+static zend_ssa_phi *add_pi(
+ zend_arena **arena, const zend_op_array *op_array, zend_dfg *dfg, zend_ssa *ssa,
+ int from, int to, int var) /* {{{ */
+{
+ zend_ssa_phi *phi;
+ if (!needs_pi(op_array, dfg, ssa, from, to, var)) {
+ return NULL;
+ }
+
+ phi = zend_arena_calloc(arena, 1,
+ ZEND_MM_ALIGNED_SIZE(sizeof(zend_ssa_phi)) +
+ ZEND_MM_ALIGNED_SIZE(sizeof(int) * ssa->cfg.blocks[to].predecessors_count) +
+ sizeof(void*) * ssa->cfg.blocks[to].predecessors_count);
+ phi->sources = (int*)(((char*)phi) + ZEND_MM_ALIGNED_SIZE(sizeof(zend_ssa_phi)));
+ memset(phi->sources, 0xff, sizeof(int) * ssa->cfg.blocks[to].predecessors_count);
+ phi->use_chains = (zend_ssa_phi**)(((char*)phi->sources) + ZEND_MM_ALIGNED_SIZE(sizeof(int) * ssa->cfg.blocks[to].predecessors_count));
+
+ phi->pi = from;
+ phi->var = var;
+ phi->ssa_var = -1;
+ phi->next = ssa->blocks[to].phis;
+ ssa->blocks[to].phis = phi;
+
+ /* Block "to" now defines "var" via the pi statement, so add it to the "def" set. Note that
+ * this is not entirely accurate, because the pi is actually placed along the edge from->to.
+ * If there is a back-edge to "to" this may result in non-minimal SSA form. */
+ DFG_SET(dfg->def, dfg->size, to, var);
+
+ /* If there are multiple predecessors in the target block, we need to place a phi there.
+ * However this can (generally) not be expressed in terms of dominance frontiers, so place it
+ * explicitly. dfg->use here really is dfg->phi, we're reusing the set. */
+ if (ssa->cfg.blocks[to].predecessors_count > 1) {
+ DFG_SET(dfg->use, dfg->size, to, var);
+ }
+
+ return phi;
+}
+/* }}} */
+
+static void pi_range(
+ zend_ssa_phi *phi, int min_var, int max_var, zend_long min, zend_long max,
+ char underflow, char overflow, char negative) /* {{{ */
+{
+ zend_ssa_range_constraint *constraint = &phi->constraint.range;
+ constraint->min_var = min_var;
+ constraint->max_var = max_var;
+ constraint->min_ssa_var = -1;
+ constraint->max_ssa_var = -1;
+ constraint->range.min = min;
+ constraint->range.max = max;
+ constraint->range.underflow = underflow;
+ constraint->range.overflow = overflow;
+ constraint->negative = negative ? NEG_INIT : NEG_NONE;
+ phi->has_range_constraint = 1;
+}
+/* }}} */
+
+static inline void pi_range_equals(zend_ssa_phi *phi, int var, zend_long val) {
+ pi_range(phi, var, var, val, val, 0, 0, 0);
+}
+static inline void pi_range_not_equals(zend_ssa_phi *phi, int var, zend_long val) {
+ pi_range(phi, var, var, val, val, 0, 0, 1);
+}
+static inline void pi_range_min(zend_ssa_phi *phi, int var, zend_long val) {
+ pi_range(phi, var, -1, val, ZEND_LONG_MAX, 0, 1, 0);
+}
+static inline void pi_range_max(zend_ssa_phi *phi, int var, zend_long val) {
+ pi_range(phi, -1, var, ZEND_LONG_MIN, val, 1, 0, 0);
+}
+
+static void pi_type_mask(zend_ssa_phi *phi, uint32_t type_mask) {
+ phi->has_range_constraint = 0;
+ phi->constraint.type.ce = NULL;
+ phi->constraint.type.type_mask = MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN;
+ phi->constraint.type.type_mask |= type_mask;
+ if (type_mask & MAY_BE_NULL) {
+ phi->constraint.type.type_mask |= MAY_BE_UNDEF;
+ }
+}
+static inline void pi_not_type_mask(zend_ssa_phi *phi, uint32_t type_mask) {
+ uint32_t relevant = MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ pi_type_mask(phi, ~type_mask & relevant);
+}
+static inline uint32_t mask_for_type_check(uint32_t type) {
+ if (type & MAY_BE_ARRAY) {
+ return type | (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
+ } else {
+ return type;
+ }
+}
+
+/* We can interpret $a + 5 == 0 as $a = 0 - 5, i.e. shift the adjustment to the other operand.
+ * This negated adjustment is what is written into the "adjustment" parameter. */
+static int find_adjusted_tmp_var(const zend_op_array *op_array, uint32_t build_flags, zend_op *opline, uint32_t var_num, zend_long *adjustment) /* {{{ */
+{
+ zend_op *op = opline;
+ zval *zv;
+
+ while (op != op_array->opcodes) {
+ op--;
+ if (op->result_type != IS_TMP_VAR || op->result.var != var_num) {
+ continue;
+ }
+
+ if (op->opcode == ZEND_POST_DEC) {
+ if (op->op1_type == IS_CV) {
+ *adjustment = -1;
+ return EX_VAR_TO_NUM(op->op1.var);
+ }
+ } else if (op->opcode == ZEND_POST_INC) {
+ if (op->op1_type == IS_CV) {
+ *adjustment = 1;
+ return EX_VAR_TO_NUM(op->op1.var);
+ }
+ } else if (op->opcode == ZEND_ADD) {
+ if (op->op1_type == IS_CV && op->op2_type == IS_CONST) {
+ zv = CRT_CONSTANT_EX(op_array, op, op->op2);
+ if (Z_TYPE_P(zv) == IS_LONG
+ && Z_LVAL_P(zv) != ZEND_LONG_MIN) {
+ *adjustment = -Z_LVAL_P(zv);
+ return EX_VAR_TO_NUM(op->op1.var);
+ }
+ } else if (op->op2_type == IS_CV && op->op1_type == IS_CONST) {
+ zv = CRT_CONSTANT_EX(op_array, op, op->op1);
+ if (Z_TYPE_P(zv) == IS_LONG
+ && Z_LVAL_P(zv) != ZEND_LONG_MIN) {
+ *adjustment = -Z_LVAL_P(zv);
+ return EX_VAR_TO_NUM(op->op2.var);
+ }
+ }
+ } else if (op->opcode == ZEND_SUB) {
+ if (op->op1_type == IS_CV && op->op2_type == IS_CONST) {
+ zv = CRT_CONSTANT_EX(op_array, op, op->op2);
+ if (Z_TYPE_P(zv) == IS_LONG) {
+ *adjustment = Z_LVAL_P(zv);
+ return EX_VAR_TO_NUM(op->op1.var);
+ }
+ }
+ }
+ break;
+ }
+ return -1;
+}
+/* }}} */
+
+/* e-SSA construction: Pi placement (Pi is actually a Phi with single
+ * source and constraint).
+ * Order of Phis is important, Pis must be placed before Phis
+ */
+static void place_essa_pis(
+ zend_arena **arena, const zend_script *script, const zend_op_array *op_array,
+ uint32_t build_flags, zend_ssa *ssa, zend_dfg *dfg) /* {{{ */ {
+ zend_basic_block *blocks = ssa->cfg.blocks;
+ int j, blocks_count = ssa->cfg.blocks_count;
+ for (j = 0; j < blocks_count; j++) {
+ zend_ssa_phi *pi;
+ zend_op *opline = op_array->opcodes + blocks[j].start + blocks[j].len - 1;
+ int bt; /* successor block number if a condition is true */
+ int bf; /* successor block number if a condition is false */
+
+ if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0 || blocks[j].len == 0) {
+ continue;
+ }
+ /* the last instruction of basic block is conditional branch,
+ * based on comparison of CV(s)
+ */
+ switch (opline->opcode) {
+ case ZEND_JMPZ:
+ case ZEND_JMPZNZ:
+ bf = blocks[j].successors[0];
+ bt = blocks[j].successors[1];
+ break;
+ case ZEND_JMPNZ:
+ bt = blocks[j].successors[0];
+ bf = blocks[j].successors[1];
+ break;
+ case ZEND_COALESCE:
+ if (opline->op1_type == IS_CV) {
+ int var = EX_VAR_TO_NUM(opline->op1.var);
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, blocks[j].successors[0], var))) {
+ pi_not_type_mask(pi, MAY_BE_NULL);
+ }
+ }
+ continue;
+ case ZEND_JMP_NULL:
+ if (opline->op1_type == IS_CV) {
+ int var = EX_VAR_TO_NUM(opline->op1.var);
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, blocks[j].successors[1], var))) {
+ pi_not_type_mask(pi, MAY_BE_NULL);
+ }
+ }
+ continue;
+ default:
+ continue;
+ }
+ if (opline->op1_type == IS_TMP_VAR &&
+ ((opline-1)->opcode == ZEND_IS_EQUAL ||
+ (opline-1)->opcode == ZEND_IS_NOT_EQUAL ||
+ (opline-1)->opcode == ZEND_IS_SMALLER ||
+ (opline-1)->opcode == ZEND_IS_SMALLER_OR_EQUAL) &&
+ opline->op1.var == (opline-1)->result.var) {
+ int var1 = -1;
+ int var2 = -1;
+ zend_long val1 = 0;
+ zend_long val2 = 0;
+// long val = 0;
+
+ if ((opline-1)->op1_type == IS_CV) {
+ var1 = EX_VAR_TO_NUM((opline-1)->op1.var);
+ } else if ((opline-1)->op1_type == IS_TMP_VAR) {
+ var1 = find_adjusted_tmp_var(
+ op_array, build_flags, opline, (opline-1)->op1.var, &val2);
+ }
+
+ if ((opline-1)->op2_type == IS_CV) {
+ var2 = EX_VAR_TO_NUM((opline-1)->op2.var);
+ } else if ((opline-1)->op2_type == IS_TMP_VAR) {
+ var2 = find_adjusted_tmp_var(
+ op_array, build_flags, opline, (opline-1)->op2.var, &val1);
+ }
+
+ if (var1 >= 0 && var2 >= 0) {
+ if (!zend_sub_will_overflow(val1, val2) && !zend_sub_will_overflow(val2, val1)) {
+ zend_long tmp = val1;
+ val1 -= val2;
+ val2 -= tmp;
+ } else {
+ var1 = -1;
+ var2 = -1;
+ }
+ } else if (var1 >= 0 && var2 < 0) {
+ zend_long add_val2 = 0;
+ if ((opline-1)->op2_type == IS_CONST) {
+ zval *zv = CRT_CONSTANT_EX(op_array, (opline-1), (opline-1)->op2);
+
+ if (Z_TYPE_P(zv) == IS_LONG) {
+ add_val2 = Z_LVAL_P(zv);
+ } else if (Z_TYPE_P(zv) == IS_FALSE) {
+ add_val2 = 0;
+ } else if (Z_TYPE_P(zv) == IS_TRUE) {
+ add_val2 = 1;
+ } else {
+ var1 = -1;
+ }
+ } else {
+ var1 = -1;
+ }
+ if (!zend_add_will_overflow(val2, add_val2)) {
+ val2 += add_val2;
+ } else {
+ var1 = -1;
+ }
+ } else if (var1 < 0 && var2 >= 0) {
+ zend_long add_val1 = 0;
+ if ((opline-1)->op1_type == IS_CONST) {
+ zval *zv = CRT_CONSTANT_EX(op_array, (opline-1), (opline-1)->op1);
+ if (Z_TYPE_P(zv) == IS_LONG) {
+ add_val1 = Z_LVAL_P(CRT_CONSTANT_EX(op_array, (opline-1), (opline-1)->op1));
+ } else if (Z_TYPE_P(zv) == IS_FALSE) {
+ add_val1 = 0;
+ } else if (Z_TYPE_P(zv) == IS_TRUE) {
+ add_val1 = 1;
+ } else {
+ var2 = -1;
+ }
+ } else {
+ var2 = -1;
+ }
+ if (!zend_add_will_overflow(val1, add_val1)) {
+ val1 += add_val1;
+ } else {
+ var2 = -1;
+ }
+ }
+
+ if (var1 >= 0) {
+ if ((opline-1)->opcode == ZEND_IS_EQUAL) {
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var1))) {
+ pi_range_equals(pi, var2, val2);
+ }
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var1))) {
+ pi_range_not_equals(pi, var2, val2);
+ }
+ } else if ((opline-1)->opcode == ZEND_IS_NOT_EQUAL) {
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var1))) {
+ pi_range_equals(pi, var2, val2);
+ }
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var1))) {
+ pi_range_not_equals(pi, var2, val2);
+ }
+ } else if ((opline-1)->opcode == ZEND_IS_SMALLER) {
+ if (val2 > ZEND_LONG_MIN) {
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var1))) {
+ pi_range_max(pi, var2, val2-1);
+ }
+ }
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var1))) {
+ pi_range_min(pi, var2, val2);
+ }
+ } else if ((opline-1)->opcode == ZEND_IS_SMALLER_OR_EQUAL) {
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var1))) {
+ pi_range_max(pi, var2, val2);
+ }
+ if (val2 < ZEND_LONG_MAX) {
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var1))) {
+ pi_range_min(pi, var2, val2+1);
+ }
+ }
+ }
+ }
+ if (var2 >= 0) {
+ if((opline-1)->opcode == ZEND_IS_EQUAL) {
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var2))) {
+ pi_range_equals(pi, var1, val1);
+ }
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var2))) {
+ pi_range_not_equals(pi, var1, val1);
+ }
+ } else if ((opline-1)->opcode == ZEND_IS_NOT_EQUAL) {
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var2))) {
+ pi_range_equals(pi, var1, val1);
+ }
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var2))) {
+ pi_range_not_equals(pi, var1, val1);
+ }
+ } else if ((opline-1)->opcode == ZEND_IS_SMALLER) {
+ if (val1 < ZEND_LONG_MAX) {
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var2))) {
+ pi_range_min(pi, var1, val1+1);
+ }
+ }
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var2))) {
+ pi_range_max(pi, var1, val1);
+ }
+ } else if ((opline-1)->opcode == ZEND_IS_SMALLER_OR_EQUAL) {
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var2))) {
+ pi_range_min(pi, var1, val1);
+ }
+ if (val1 > ZEND_LONG_MIN) {
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var2))) {
+ pi_range_max(pi, var1, val1-1);
+ }
+ }
+ }
+ }
+ } else if (opline->op1_type == IS_TMP_VAR &&
+ ((opline-1)->opcode == ZEND_POST_INC ||
+ (opline-1)->opcode == ZEND_POST_DEC) &&
+ opline->op1.var == (opline-1)->result.var &&
+ (opline-1)->op1_type == IS_CV) {
+ int var = EX_VAR_TO_NUM((opline-1)->op1.var);
+
+ if ((opline-1)->opcode == ZEND_POST_DEC) {
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var))) {
+ pi_range_equals(pi, -1, -1);
+ }
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) {
+ pi_range_not_equals(pi, -1, -1);
+ }
+ } else if ((opline-1)->opcode == ZEND_POST_INC) {
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var))) {
+ pi_range_equals(pi, -1, 1);
+ }
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) {
+ pi_range_not_equals(pi, -1, 1);
+ }
+ }
+ } else if (opline->op1_type == IS_TMP_VAR &&
+ ((opline-1)->opcode == ZEND_PRE_INC ||
+ (opline-1)->opcode == ZEND_PRE_DEC) &&
+ opline->op1.var == (opline-1)->result.var &&
+ (opline-1)->op1_type == IS_CV) {
+ int var = EX_VAR_TO_NUM((opline-1)->op1.var);
+
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var))) {
+ pi_range_equals(pi, -1, 0);
+ }
+ /* speculative */
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) {
+ pi_range_not_equals(pi, -1, 0);
+ }
+ } else if (opline->op1_type == IS_TMP_VAR && (opline-1)->opcode == ZEND_TYPE_CHECK &&
+ opline->op1.var == (opline-1)->result.var && (opline-1)->op1_type == IS_CV) {
+ int var = EX_VAR_TO_NUM((opline-1)->op1.var);
+ uint32_t type = (opline-1)->extended_value;
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) {
+ pi_type_mask(pi, mask_for_type_check(type));
+ }
+ if (type != MAY_BE_RESOURCE) {
+ /* is_resource() may return false for closed resources */
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var))) {
+ pi_not_type_mask(pi, mask_for_type_check(type));
+ }
+ }
+ } else if (opline->op1_type == IS_TMP_VAR &&
+ ((opline-1)->opcode == ZEND_IS_IDENTICAL
+ || (opline-1)->opcode == ZEND_IS_NOT_IDENTICAL) &&
+ opline->op1.var == (opline-1)->result.var) {
+ int var;
+ zval *val;
+ uint32_t type_mask;
+ if ((opline-1)->op1_type == IS_CV && (opline-1)->op2_type == IS_CONST) {
+ var = EX_VAR_TO_NUM((opline-1)->op1.var);
+ val = CRT_CONSTANT_EX(op_array, (opline-1), (opline-1)->op2);
+ } else if ((opline-1)->op1_type == IS_CONST && (opline-1)->op2_type == IS_CV) {
+ var = EX_VAR_TO_NUM((opline-1)->op2.var);
+ val = CRT_CONSTANT_EX(op_array, (opline-1), (opline-1)->op1);
+ } else {
+ continue;
+ }
+
+ /* We're interested in === null/true/false comparisons here, because they eliminate
+ * a type in the false-branch. Other === VAL comparisons are unlikely to be useful. */
+ if (Z_TYPE_P(val) != IS_NULL && Z_TYPE_P(val) != IS_TRUE && Z_TYPE_P(val) != IS_FALSE) {
+ continue;
+ }
+
+ type_mask = _const_op_type(val);
+ if ((opline-1)->opcode == ZEND_IS_IDENTICAL) {
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) {
+ pi_type_mask(pi, type_mask);
+ }
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var))) {
+ pi_not_type_mask(pi, type_mask);
+ }
+ } else {
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var))) {
+ pi_type_mask(pi, type_mask);
+ }
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) {
+ pi_not_type_mask(pi, type_mask);
+ }
+ }
+ } else if (opline->op1_type == IS_TMP_VAR && (opline-1)->opcode == ZEND_INSTANCEOF &&
+ opline->op1.var == (opline-1)->result.var && (opline-1)->op1_type == IS_CV &&
+ (opline-1)->op2_type == IS_CONST) {
+ int var = EX_VAR_TO_NUM((opline-1)->op1.var);
+ zend_string *lcname = Z_STR_P(CRT_CONSTANT_EX(op_array, (opline-1), (opline-1)->op2) + 1);
+ zend_class_entry *ce = script ? zend_hash_find_ptr(&script->class_table, lcname) : NULL;
+ if (!ce) {
+ ce = zend_hash_find_ptr(CG(class_table), lcname);
+ if (!ce || ce->type != ZEND_INTERNAL_CLASS) {
+ continue;
+ }
+ }
+
+ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) {
+ pi_type_mask(pi, MAY_BE_OBJECT);
+ pi->constraint.type.ce = ce;
+ }
+ }
+ }
+}
+/* }}} */
+
+static zend_always_inline int _zend_ssa_rename_op(const zend_op_array *op_array, const zend_op *opline, uint32_t k, uint32_t build_flags, int ssa_vars_count, zend_ssa_op *ssa_ops, int *var) /* {{{ */
+{
+ const zend_op *next;
+
+ if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ ssa_ops[k].op1_use = var[EX_VAR_TO_NUM(opline->op1.var)];
+ //USE_SSA_VAR(op_array->last_var + opline->op1.var)
+ }
+ if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ ssa_ops[k].op2_use = var[EX_VAR_TO_NUM(opline->op2.var)];
+ //USE_SSA_VAR(op_array->last_var + opline->op2.var)
+ }
+ if ((build_flags & ZEND_SSA_USE_CV_RESULTS)
+ && opline->result_type == IS_CV
+ && opline->opcode != ZEND_RECV) {
+ ssa_ops[k].result_use = var[EX_VAR_TO_NUM(opline->result.var)];
+ //USE_SSA_VAR(op_array->last_var + opline->result.var)
+ }
+
+ switch (opline->opcode) {
+ case ZEND_ASSIGN:
+ if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op2_type == IS_CV) {
+ ssa_ops[k].op2_def = ssa_vars_count;
+ var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count;
+ ssa_vars_count++;
+ //NEW_SSA_VAR(opline->op2.var)
+ }
+ if (opline->op1_type == IS_CV) {
+add_op1_def:
+ ssa_ops[k].op1_def = ssa_vars_count;
+ var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count;
+ ssa_vars_count++;
+ //NEW_SSA_VAR(opline->op1.var)
+ }
+ break;
+ case ZEND_ASSIGN_REF:
+ if (opline->op2_type == IS_CV) {
+ ssa_ops[k].op2_def = ssa_vars_count;
+ var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count;
+ ssa_vars_count++;
+ //NEW_SSA_VAR(opline->op2.var)
+ }
+ if (opline->op1_type == IS_CV) {
+ goto add_op1_def;
+ }
+ break;
+ case ZEND_ASSIGN_DIM:
+ case ZEND_ASSIGN_OBJ:
+ next = opline + 1;
+ if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
+ //USE_SSA_VAR(op_array->last_var + next->op1.var);
+ if (build_flags & ZEND_SSA_RC_INFERENCE && next->op1_type == IS_CV) {
+ ssa_ops[k + 1].op1_def = ssa_vars_count;
+ var[EX_VAR_TO_NUM(next->op1.var)] = ssa_vars_count;
+ ssa_vars_count++;
+ //NEW_SSA_VAR(next->op1.var)
+ }
+ }
+ if (opline->op1_type == IS_CV) {
+ goto add_op1_def;
+ }
+ break;
+ case ZEND_ASSIGN_OBJ_REF:
+ next = opline + 1;
+ if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
+ //USE_SSA_VAR(op_array->last_var + next->op1.var);
+ if (next->op1_type == IS_CV) {
+ ssa_ops[k + 1].op1_def = ssa_vars_count;
+ var[EX_VAR_TO_NUM(next->op1.var)] = ssa_vars_count;
+ ssa_vars_count++;
+ //NEW_SSA_VAR(next->op1.var)
+ }
+ }
+ if (opline->op1_type == IS_CV) {
+ goto add_op1_def;
+ }
+ break;
+ case ZEND_ASSIGN_STATIC_PROP:
+ next = opline + 1;
+ if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
+ //USE_SSA_VAR(op_array->last_var + next->op1.var);
+ if ((build_flags & ZEND_SSA_RC_INFERENCE) && next->op1_type == IS_CV) {
+ ssa_ops[k + 1].op1_def = ssa_vars_count;
+ var[EX_VAR_TO_NUM(next->op1.var)] = ssa_vars_count;
+ ssa_vars_count++;
+ //NEW_SSA_VAR(next->op1.var)
+ }
+ }
+ break;
+ case ZEND_ASSIGN_STATIC_PROP_REF:
+ next = opline + 1;
+ if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
+ //USE_SSA_VAR(op_array->last_var + next->op1.var);
+ if (next->op1_type == IS_CV) {
+ ssa_ops[k + 1].op1_def = ssa_vars_count;
+ var[EX_VAR_TO_NUM(next->op1.var)] = ssa_vars_count;
+ ssa_vars_count++;
+ //NEW_SSA_VAR(next->op1.var)
+ }
+ }
+ break;
+ case ZEND_ASSIGN_STATIC_PROP_OP:
+ next = opline + 1;
+ if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
+ //USE_SSA_VAR(op_array->last_var + next->op1.var);
+ }
+ break;
+ case ZEND_ASSIGN_DIM_OP:
+ case ZEND_ASSIGN_OBJ_OP:
+ next = opline + 1;
+ if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
+ //USE_SSA_VAR(op_array->last_var + next->op1.var);
+ }
+ if (opline->op1_type == IS_CV) {
+ goto add_op1_def;
+ }
+ break;
+ case ZEND_ASSIGN_OP:
+ case ZEND_PRE_INC:
+ case ZEND_PRE_DEC:
+ case ZEND_POST_INC:
+ case ZEND_POST_DEC:
+ case ZEND_BIND_GLOBAL:
+ case ZEND_BIND_STATIC:
+ case ZEND_SEND_VAR_NO_REF:
+ case ZEND_SEND_VAR_NO_REF_EX:
+ case ZEND_SEND_VAR_EX:
+ case ZEND_SEND_FUNC_ARG:
+ case ZEND_SEND_REF:
+ case ZEND_SEND_UNPACK:
+ case ZEND_FE_RESET_RW:
+ case ZEND_MAKE_REF:
+ case ZEND_PRE_INC_OBJ:
+ case ZEND_PRE_DEC_OBJ:
+ case ZEND_POST_INC_OBJ:
+ case ZEND_POST_DEC_OBJ:
+ case ZEND_UNSET_DIM:
+ case ZEND_UNSET_OBJ:
+ case ZEND_FETCH_DIM_W:
+ case ZEND_FETCH_DIM_RW:
+ case ZEND_FETCH_DIM_FUNC_ARG:
+ case ZEND_FETCH_DIM_UNSET:
+ case ZEND_FETCH_LIST_W:
+ if (opline->op1_type == IS_CV) {
+ goto add_op1_def;
+ }
+ break;
+ case ZEND_SEND_VAR:
+ case ZEND_CAST:
+ case ZEND_QM_ASSIGN:
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ case ZEND_FE_RESET_R:
+ if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op1_type == IS_CV) {
+ goto add_op1_def;
+ }
+ break;
+ case ZEND_ADD_ARRAY_UNPACK:
+ ssa_ops[k].result_use = var[EX_VAR_TO_NUM(opline->result.var)];
+ break;
+ case ZEND_ADD_ARRAY_ELEMENT:
+ ssa_ops[k].result_use = var[EX_VAR_TO_NUM(opline->result.var)];
+ /* break missing intentionally */
+ case ZEND_INIT_ARRAY:
+ if (((build_flags & ZEND_SSA_RC_INFERENCE)
+ || (opline->extended_value & ZEND_ARRAY_ELEMENT_REF))
+ && opline->op1_type == IS_CV) {
+ goto add_op1_def;
+ }
+ break;
+ case ZEND_YIELD:
+ if (opline->op1_type == IS_CV
+ && ((op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE)
+ || (build_flags & ZEND_SSA_RC_INFERENCE))) {
+ goto add_op1_def;
+ }
+ break;
+ case ZEND_UNSET_CV:
+ goto add_op1_def;
+ case ZEND_VERIFY_RETURN_TYPE:
+ if (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) {
+ goto add_op1_def;
+ }
+ break;
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ if (opline->op2_type != IS_CV) {
+ ssa_ops[k].op2_use = -1; /* not used */
+ }
+ ssa_ops[k].op2_def = ssa_vars_count;
+ var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count;
+ ssa_vars_count++;
+ //NEW_SSA_VAR(opline->op2.var)
+ break;
+ case ZEND_BIND_LEXICAL:
+ if ((opline->extended_value & ZEND_BIND_REF) || (build_flags & ZEND_SSA_RC_INFERENCE)) {
+ ssa_ops[k].op2_def = ssa_vars_count;
+ var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count;
+ ssa_vars_count++;
+ //NEW_SSA_VAR(opline->op2.var)
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+ ssa_ops[k].result_def = ssa_vars_count;
+ var[EX_VAR_TO_NUM(opline->result.var)] = ssa_vars_count;
+ ssa_vars_count++;
+ //NEW_SSA_VAR(op_array->last_var + opline->result.var)
+ }
+
+ return ssa_vars_count;
+}
+/* }}} */
+
+ZEND_API int zend_ssa_rename_op(const zend_op_array *op_array, const zend_op *opline, uint32_t k, uint32_t build_flags, int ssa_vars_count, zend_ssa_op *ssa_ops, int *var) /* {{{ */
+{
+ return _zend_ssa_rename_op(op_array, opline, k, build_flags, ssa_vars_count, ssa_ops, var);
+}
+/* }}} */
+
+static int zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, int *var, int n) /* {{{ */
+{
+ zend_basic_block *blocks = ssa->cfg.blocks;
+ zend_ssa_block *ssa_blocks = ssa->blocks;
+ zend_ssa_op *ssa_ops = ssa->ops;
+ int ssa_vars_count = ssa->vars_count;
+ int i, j;
+ zend_op *opline, *end;
+ int *tmp = NULL;
+ ALLOCA_FLAG(use_heap = 0);
+
+ // FIXME: Can we optimize this copying out in some cases?
+ if (blocks[n].next_child >= 0) {
+ tmp = do_alloca(sizeof(int) * (op_array->last_var + op_array->T), use_heap);
+ memcpy(tmp, var, sizeof(int) * (op_array->last_var + op_array->T));
+ var = tmp;
+ }
+
+ if (ssa_blocks[n].phis) {
+ zend_ssa_phi *phi = ssa_blocks[n].phis;
+ do {
+ if (phi->ssa_var < 0) {
+ phi->ssa_var = ssa_vars_count;
+ var[phi->var] = ssa_vars_count;
+ ssa_vars_count++;
+ } else {
+ var[phi->var] = phi->ssa_var;
+ }
+ phi = phi->next;
+ } while (phi);
+ }
+
+ opline = op_array->opcodes + blocks[n].start;
+ end = opline + blocks[n].len;
+ for (; opline < end; opline++) {
+ uint32_t k = opline - op_array->opcodes;
+ if (opline->opcode != ZEND_OP_DATA) {
+ ssa_vars_count = _zend_ssa_rename_op(op_array, opline, k, build_flags, ssa_vars_count, ssa_ops, var);
+ }
+ }
+
+ zend_ssa_op *fe_fetch_ssa_op = blocks[n].len != 0
+ && ((end-1)->opcode == ZEND_FE_FETCH_R || (end-1)->opcode == ZEND_FE_FETCH_RW)
+ && (end-1)->op2_type == IS_CV
+ ? &ssa_ops[blocks[n].start + blocks[n].len - 1] : NULL;
+ for (i = 0; i < blocks[n].successors_count; i++) {
+ int succ = blocks[n].successors[i];
+ zend_ssa_phi *p;
+ for (p = ssa_blocks[succ].phis; p; p = p->next) {
+ if (p->pi == n) {
+ /* e-SSA Pi */
+ if (p->has_range_constraint) {
+ if (p->constraint.range.min_var >= 0) {
+ p->constraint.range.min_ssa_var = var[p->constraint.range.min_var];
+ }
+ if (p->constraint.range.max_var >= 0) {
+ p->constraint.range.max_ssa_var = var[p->constraint.range.max_var];
+ }
+ }
+ for (j = 0; j < blocks[succ].predecessors_count; j++) {
+ p->sources[j] = var[p->var];
+ }
+ if (p->ssa_var < 0) {
+ p->ssa_var = ssa_vars_count;
+ ssa_vars_count++;
+ }
+ } else if (p->pi < 0) {
+ /* Normal Phi */
+ for (j = 0; j < blocks[succ].predecessors_count; j++)
+ if (ssa->cfg.predecessors[blocks[succ].predecessor_offset + j] == n) {
+ break;
+ }
+ ZEND_ASSERT(j < blocks[succ].predecessors_count);
+ p->sources[j] = var[p->var];
+ if (fe_fetch_ssa_op && i == 0 && p->sources[j] == fe_fetch_ssa_op->op2_def) {
+ /* On the exit edge of an FE_FETCH, use the pre-modification value instead. */
+ p->sources[j] = fe_fetch_ssa_op->op2_use;
+ }
+ }
+ }
+ for (p = ssa_blocks[succ].phis; p && (p->pi >= 0); p = p->next) {
+ if (p->pi == n) {
+ zend_ssa_phi *q = p->next;
+ while (q) {
+ if (q->pi < 0 && q->var == p->var) {
+ for (j = 0; j < blocks[succ].predecessors_count; j++) {
+ if (ssa->cfg.predecessors[blocks[succ].predecessor_offset + j] == n) {
+ break;
+ }
+ }
+ ZEND_ASSERT(j < blocks[succ].predecessors_count);
+ q->sources[j] = p->ssa_var;
+ }
+ q = q->next;
+ }
+ }
+ }
+ }
+
+ ssa->vars_count = ssa_vars_count;
+
+ j = blocks[n].children;
+ while (j >= 0) {
+ // FIXME: Tail call optimization?
+ if (zend_ssa_rename(op_array, build_flags, ssa, var, j) != SUCCESS)
+ return FAILURE;
+ j = blocks[j].next_child;
+ }
+
+ if (tmp) {
+ free_alloca(tmp, use_heap);
+ }
+
+ return SUCCESS;
+}
+/* }}} */
+
+ZEND_API int zend_build_ssa(zend_arena **arena, const zend_script *script, const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa) /* {{{ */
+{
+ zend_basic_block *blocks = ssa->cfg.blocks;
+ zend_ssa_block *ssa_blocks;
+ int blocks_count = ssa->cfg.blocks_count;
+ uint32_t set_size;
+ zend_bitset def, in, phi;
+ int *var = NULL;
+ int i, j, k, changed;
+ zend_dfg dfg;
+ ALLOCA_FLAG(dfg_use_heap)
+ ALLOCA_FLAG(var_use_heap)
+
+ if ((blocks_count * (op_array->last_var + op_array->T)) > 4 * 1024 * 1024) {
+ /* Don't build SSA for very big functions */
+ return FAILURE;
+ }
+
+ ssa_blocks = zend_arena_calloc(arena, blocks_count, sizeof(zend_ssa_block));
+ ssa->blocks = ssa_blocks;
+
+ /* Compute Variable Liveness */
+ dfg.vars = op_array->last_var + op_array->T;
+ dfg.size = set_size = zend_bitset_len(dfg.vars);
+ dfg.tmp = do_alloca((set_size * sizeof(zend_ulong)) * (blocks_count * 4 + 1), dfg_use_heap);
+ memset(dfg.tmp, 0, (set_size * sizeof(zend_ulong)) * (blocks_count * 4 + 1));
+ dfg.def = dfg.tmp + set_size;
+ dfg.use = dfg.def + set_size * blocks_count;
+ dfg.in = dfg.use + set_size * blocks_count;
+ dfg.out = dfg.in + set_size * blocks_count;
+
+ if (zend_build_dfg(op_array, &ssa->cfg, &dfg, build_flags) != SUCCESS) {
+ free_alloca(dfg.tmp, dfg_use_heap);
+ return FAILURE;
+ }
+
+ if (build_flags & ZEND_SSA_DEBUG_LIVENESS) {
+ zend_dump_dfg(op_array, &ssa->cfg, &dfg);
+ }
+
+ def = dfg.def;
+ in = dfg.in;
+
+ /* Reuse the "use" set, as we no longer need it */
+ phi = dfg.use;
+ zend_bitset_clear(phi, set_size * blocks_count);
+
+ /* Place e-SSA pis. This will add additional "def" points, so it must
+ * happen before def propagation. */
+ place_essa_pis(arena, script, op_array, build_flags, ssa, &dfg);
+
+ /* SSA construction, Step 1: Propagate "def" sets in merge points */
+ do {
+ changed = 0;
+ for (j = 0; j < blocks_count; j++) {
+ zend_bitset def_j = def + j * set_size, phi_j = phi + j * set_size;
+ if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) {
+ continue;
+ }
+ if (blocks[j].predecessors_count > 1) {
+ if (blocks[j].flags & ZEND_BB_IRREDUCIBLE_LOOP) {
+ /* Prevent any values from flowing into irreducible loops by
+ replacing all incoming values with explicit phis. The
+ register allocator depends on this property. */
+ zend_bitset_union(phi_j, in + (j * set_size), set_size);
+ } else {
+ for (k = 0; k < blocks[j].predecessors_count; k++) {
+ i = ssa->cfg.predecessors[blocks[j].predecessor_offset + k];
+ while (i != -1 && i != blocks[j].idom) {
+ zend_bitset_union_with_intersection(
+ phi_j, phi_j, def + (i * set_size), in + (j * set_size), set_size);
+ i = blocks[i].idom;
+ }
+ }
+ }
+ if (!zend_bitset_subset(phi_j, def_j, set_size)) {
+ zend_bitset_union(def_j, phi_j, set_size);
+ changed = 1;
+ }
+ }
+ }
+ } while (changed);
+
+ /* SSA construction, Step 2: Phi placement based on Dominance Frontiers */
+ var = do_alloca(sizeof(int) * (op_array->last_var + op_array->T), var_use_heap);
+ if (!var) {
+ free_alloca(dfg.tmp, dfg_use_heap);
+ return FAILURE;
+ }
+
+ for (j = 0; j < blocks_count; j++) {
+ if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) {
+ continue;
+ }
+ if (!zend_bitset_empty(phi + j * set_size, set_size)) {
+ ZEND_BITSET_REVERSE_FOREACH(phi + j * set_size, set_size, i) {
+ zend_ssa_phi *phi = zend_arena_calloc(arena, 1,
+ ZEND_MM_ALIGNED_SIZE(sizeof(zend_ssa_phi)) +
+ ZEND_MM_ALIGNED_SIZE(sizeof(int) * blocks[j].predecessors_count) +
+ sizeof(void*) * blocks[j].predecessors_count);
+
+ phi->sources = (int*)(((char*)phi) + ZEND_MM_ALIGNED_SIZE(sizeof(zend_ssa_phi)));
+ memset(phi->sources, 0xff, sizeof(int) * blocks[j].predecessors_count);
+ phi->use_chains = (zend_ssa_phi**)(((char*)phi->sources) + ZEND_MM_ALIGNED_SIZE(sizeof(int) * ssa->cfg.blocks[j].predecessors_count));
+
+ phi->pi = -1;
+ phi->var = i;
+ phi->ssa_var = -1;
+
+ /* Place phis after pis */
+ {
+ zend_ssa_phi **pp = &ssa_blocks[j].phis;
+ while (*pp) {
+ if ((*pp)->pi < 0) {
+ break;
+ }
+ pp = &(*pp)->next;
+ }
+ phi->next = *pp;
+ *pp = phi;
+ }
+ } ZEND_BITSET_FOREACH_END();
+ }
+ }
+
+ if (build_flags & ZEND_SSA_DEBUG_PHI_PLACEMENT) {
+ zend_dump_phi_placement(op_array, ssa);
+ }
+
+ /* SSA construction, Step 3: Renaming */
+ ssa->ops = zend_arena_calloc(arena, op_array->last, sizeof(zend_ssa_op));
+ memset(ssa->ops, 0xff, op_array->last * sizeof(zend_ssa_op));
+ memset(var + op_array->last_var, 0xff, op_array->T * sizeof(int));
+ /* Create uninitialized SSA variables for each CV */
+ for (j = 0; j < op_array->last_var; j++) {
+ var[j] = j;
+ }
+ ssa->vars_count = op_array->last_var;
+ if (zend_ssa_rename(op_array, build_flags, ssa, var, 0) != SUCCESS) {
+ free_alloca(var, var_use_heap);
+ free_alloca(dfg.tmp, dfg_use_heap);
+ return FAILURE;
+ }
+
+ free_alloca(var, var_use_heap);
+ free_alloca(dfg.tmp, dfg_use_heap);
+
+ return SUCCESS;
+}
+/* }}} */
+
+ZEND_API int zend_ssa_compute_use_def_chains(zend_arena **arena, const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
+{
+ zend_ssa_var *ssa_vars;
+ int i;
+
+ if (!ssa->vars) {
+ ssa->vars = zend_arena_calloc(arena, ssa->vars_count, sizeof(zend_ssa_var));
+ }
+ ssa_vars = ssa->vars;
+
+ for (i = 0; i < op_array->last_var; i++) {
+ ssa_vars[i].var = i;
+ ssa_vars[i].scc = -1;
+ ssa_vars[i].definition = -1;
+ ssa_vars[i].use_chain = -1;
+ }
+ for (i = op_array->last_var; i < ssa->vars_count; i++) {
+ ssa_vars[i].var = -1;
+ ssa_vars[i].scc = -1;
+ ssa_vars[i].definition = -1;
+ ssa_vars[i].use_chain = -1;
+ }
+
+ for (i = op_array->last - 1; i >= 0; i--) {
+ zend_ssa_op *op = ssa->ops + i;
+
+ if (op->op1_use >= 0) {
+ op->op1_use_chain = ssa_vars[op->op1_use].use_chain;
+ ssa_vars[op->op1_use].use_chain = i;
+ }
+ if (op->op2_use >= 0 && op->op2_use != op->op1_use) {
+ op->op2_use_chain = ssa_vars[op->op2_use].use_chain;
+ ssa_vars[op->op2_use].use_chain = i;
+ }
+ if (op->result_use >= 0 && op->result_use != op->op1_use && op->result_use != op->op2_use) {
+ op->res_use_chain = ssa_vars[op->result_use].use_chain;
+ ssa_vars[op->result_use].use_chain = i;
+ }
+ if (op->op1_def >= 0) {
+ ssa_vars[op->op1_def].var = EX_VAR_TO_NUM(op_array->opcodes[i].op1.var);
+ ssa_vars[op->op1_def].definition = i;
+ }
+ if (op->op2_def >= 0) {
+ ssa_vars[op->op2_def].var = EX_VAR_TO_NUM(op_array->opcodes[i].op2.var);
+ ssa_vars[op->op2_def].definition = i;
+ }
+ if (op->result_def >= 0) {
+ ssa_vars[op->result_def].var = EX_VAR_TO_NUM(op_array->opcodes[i].result.var);
+ ssa_vars[op->result_def].definition = i;
+ }
+ }
+
+ for (i = 0; i < ssa->cfg.blocks_count; i++) {
+ zend_ssa_phi *phi = ssa->blocks[i].phis;
+ while (phi) {
+ phi->block = i;
+ ssa_vars[phi->ssa_var].var = phi->var;
+ ssa_vars[phi->ssa_var].definition_phi = phi;
+ if (phi->pi >= 0) {
+ 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 */
+ zend_ssa_range_constraint *constraint = &phi->constraint.range;
+ if (constraint->min_ssa_var >= 0) {
+ phi->sym_use_chain = ssa_vars[constraint->min_ssa_var].sym_use_chain;
+ ssa_vars[constraint->min_ssa_var].sym_use_chain = phi;
+ } else if (constraint->max_ssa_var >= 0) {
+ phi->sym_use_chain = ssa_vars[constraint->max_ssa_var].sym_use_chain;
+ ssa_vars[constraint->max_ssa_var].sym_use_chain = phi;
+ }
+ }
+ } else {
+ int j;
+
+ for (j = 0; j < ssa->cfg.blocks[i].predecessors_count; j++) {
+ 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;
+ }
+ }
+ }
+ phi = phi->next;
+ }
+ }
+
+ /* Mark indirectly accessed variables */
+ for (i = 0; i < op_array->last_var; i++) {
+ if ((ssa->cfg.flags & ZEND_FUNC_INDIRECT_VAR_ACCESS)) {
+ ssa_vars[i].alias = SYMTABLE_ALIAS;
+ } else if (zend_string_equals_literal(op_array->vars[i], "http_response_header")) {
+ ssa_vars[i].alias = HTTP_RESPONSE_HEADER_ALIAS;
+ }
+ }
+ for (i = op_array->last_var; i < ssa->vars_count; i++) {
+ if (ssa_vars[i].var < op_array->last_var) {
+ ssa_vars[i].alias = ssa_vars[ssa_vars[i].var].alias;
+ }
+ }
+
+ return SUCCESS;
+}
+/* }}} */
+
+int zend_ssa_unlink_use_chain(zend_ssa *ssa, int op, int var) /* {{{ */
+{
+ if (ssa->vars[var].use_chain == op) {
+ ssa->vars[var].use_chain = zend_ssa_next_use(ssa->ops, var, op);
+ return 1;
+ } else {
+ int use = ssa->vars[var].use_chain;
+
+ while (use >= 0) {
+ if (ssa->ops[use].result_use == var) {
+ if (ssa->ops[use].res_use_chain == op) {
+ ssa->ops[use].res_use_chain = zend_ssa_next_use(ssa->ops, var, op);
+ return 1;
+ } else {
+ use = ssa->ops[use].res_use_chain;
+ }
+ } else if (ssa->ops[use].op1_use == var) {
+ if (ssa->ops[use].op1_use_chain == op) {
+ ssa->ops[use].op1_use_chain = zend_ssa_next_use(ssa->ops, var, op);
+ return 1;
+ } else {
+ use = ssa->ops[use].op1_use_chain;
+ }
+ } else if (ssa->ops[use].op2_use == var) {
+ if (ssa->ops[use].op2_use_chain == op) {
+ ssa->ops[use].op2_use_chain = zend_ssa_next_use(ssa->ops, var, op);
+ return 1;
+ } else {
+ use = ssa->ops[use].op2_use_chain;
+ }
+ } else {
+ break;
+ }
+ }
+ /* something wrong */
+ ZEND_UNREACHABLE();
+ return 0;
+ }
+}
+/* }}} */
+
+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_UNREACHABLE();
+ 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 *next_use_phi) /* {{{ */
+{
+ 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 = next_use_phi;
+ }
+}
+/* }}} */
+
+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, zend_ssa_next_use_phi(ssa, source, phi));
+ } 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];
+ zend_ssa_phi *next_phi = phi->use_chains[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));
+ memmove(phi->use_chains + pred_offset, phi->use_chains + pred_offset + 1, (predecessors_count - pred_offset) * sizeof(zend_ssa_phi*));
+ }
+
+ /* 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(next_phi == NULL);
+ } else if (j >= pred_offset) {
+ phi->use_chains[j] = next_phi;
+ }
+ 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, next_phi);
+}
+/* }}} */
+
+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_predecessor(zend_ssa *ssa, int from, int to) /* {{{ */
+{
+ zend_basic_block *next_block = &ssa->cfg.blocks[to];
+ zend_ssa_block *next_ssa_block = &ssa->blocks[to];
+ zend_ssa_phi *phi;
+ int j;
+
+ /* Find at which predecessor offset this block is referenced */
+ int pred_offset = -1;
+ int *predecessors = &ssa->cfg.predecessors[next_block->predecessor_offset];
+
+ for (j = 0; j < next_block->predecessors_count; j++) {
+ if (predecessors[j] == from) {
+ pred_offset = j;
+ break;
+ }
+ }
+
+ /* If there are duplicate successors, the predecessors may have been removed in
+ * a previous iteration already. */
+ if (pred_offset == -1) {
+ return;
+ }
+
+ /* 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 == from) {
+ zend_ssa_rename_var_uses(ssa, phi->ssa_var, phi->sources[0], /* update_types */ 0);
+ 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));
+ }
+}
+/* }}} */
+
+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;
+ }
+
+ 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_ssa_remove_predecessor(ssa, i, block->successors[s]);
+ }
+
+ /* 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, 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. */
+ 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;
+ 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;
+ } else {
+ phi->use_chains[j] = NULL;
+ }
+ }
+ }
+
+ /* 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;
+}
+/* }}} */
diff --git a/Zend/Optimizer/zend_ssa.h b/Zend/Optimizer/zend_ssa.h
new file mode 100644
index 0000000000..ddc9f95f43
--- /dev/null
+++ b/Zend/Optimizer/zend_ssa.h
@@ -0,0 +1,326 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, SSA - Static Single Assignment Form |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef ZEND_SSA_H
+#define ZEND_SSA_H
+
+#include "zend_optimizer.h"
+#include "zend_cfg.h"
+
+typedef struct _zend_ssa_range {
+ zend_long min;
+ zend_long max;
+ bool underflow;
+ bool overflow;
+} zend_ssa_range;
+
+typedef enum _zend_ssa_negative_lat {
+ NEG_NONE = 0,
+ NEG_INIT = 1,
+ NEG_INVARIANT = 2,
+ NEG_USE_LT = 3,
+ NEG_USE_GT = 4,
+ NEG_UNKNOWN = 5
+} zend_ssa_negative_lat;
+
+/* Special kind of SSA Phi function used in eSSA */
+typedef struct _zend_ssa_range_constraint {
+ zend_ssa_range range; /* simple range constraint */
+ int min_var;
+ int max_var;
+ int min_ssa_var; /* ((min_var>0) ? MIN(ssa_var) : 0) + range.min */
+ int max_ssa_var; /* ((max_var>0) ? MAX(ssa_var) : 0) + range.max */
+ zend_ssa_negative_lat negative;
+} zend_ssa_range_constraint;
+
+typedef struct _zend_ssa_type_constraint {
+ uint32_t type_mask; /* Type mask to intersect with */
+ zend_class_entry *ce; /* Class entry for instanceof constraints */
+} zend_ssa_type_constraint;
+
+typedef union _zend_ssa_pi_constraint {
+ zend_ssa_range_constraint range;
+ zend_ssa_type_constraint type;
+} zend_ssa_pi_constraint;
+
+/* SSA Phi - ssa_var = Phi(source0, source1, ...sourceN) */
+typedef struct _zend_ssa_phi zend_ssa_phi;
+struct _zend_ssa_phi {
+ zend_ssa_phi *next; /* next Phi in the same BB */
+ int pi; /* if >= 0 this is actually a e-SSA Pi */
+ zend_ssa_pi_constraint constraint; /* e-SSA Pi constraint */
+ int var; /* Original CV, VAR or TMP variable index */
+ int ssa_var; /* SSA variable index */
+ int block; /* current BB index */
+ int visited : 1; /* flag to avoid recursive processing */
+ int has_range_constraint : 1;
+ zend_ssa_phi **use_chains;
+ zend_ssa_phi *sym_use_chain;
+ int *sources; /* Array of SSA IDs that produce this var.
+ As many as this block has
+ predecessors. */
+};
+
+typedef struct _zend_ssa_block {
+ zend_ssa_phi *phis;
+} zend_ssa_block;
+
+typedef struct _zend_ssa_op {
+ int op1_use;
+ int op2_use;
+ int result_use;
+ int op1_def;
+ int op2_def;
+ int result_def;
+ int op1_use_chain;
+ int op2_use_chain;
+ int res_use_chain;
+} zend_ssa_op;
+
+typedef enum _zend_ssa_alias_kind {
+ NO_ALIAS,
+ SYMTABLE_ALIAS,
+ HTTP_RESPONSE_HEADER_ALIAS
+} zend_ssa_alias_kind;
+
+typedef enum _zend_ssa_escape_state {
+ ESCAPE_STATE_UNKNOWN,
+ ESCAPE_STATE_NO_ESCAPE,
+ ESCAPE_STATE_FUNCTION_ESCAPE,
+ ESCAPE_STATE_GLOBAL_ESCAPE
+} zend_ssa_escape_state;
+
+typedef struct _zend_ssa_var {
+ int var; /* original var number; op.var for CVs and following numbers for VARs and TMP_VARs */
+ int scc; /* strongly connected component */
+ int definition; /* opcode that defines this value */
+ zend_ssa_phi *definition_phi; /* phi that defines this value */
+ int use_chain; /* uses of this value, linked through opN_use_chain */
+ zend_ssa_phi *phi_use_chain; /* uses of this value in Phi, linked through use_chain */
+ zend_ssa_phi *sym_use_chain; /* uses of this value in Pi constraints */
+ unsigned int no_val : 1; /* value doesn't matter (used as op1 in ZEND_ASSIGN) */
+ unsigned int scc_entry : 1;
+ unsigned int alias : 2; /* value may be changed indirectly */
+ unsigned int escape_state : 2;
+} zend_ssa_var;
+
+typedef struct _zend_ssa_var_info {
+ uint32_t type; /* inferred type (see zend_inference.h) */
+ zend_ssa_range range;
+ zend_class_entry *ce;
+ unsigned int has_range : 1;
+ unsigned int is_instanceof : 1; /* 0 - class == "ce", 1 - may be child of "ce" */
+ unsigned int recursive : 1;
+ unsigned int use_as_double : 1;
+ unsigned int delayed_fetch_this : 1;
+ unsigned int avoid_refcounting : 1;
+ unsigned int guarded_reference : 1;
+ unsigned int indirect_reference : 1; /* IS_INDIRECT returned by FETCH_DIM_W/FETCH_OBJ_W */
+} zend_ssa_var_info;
+
+typedef struct _zend_ssa {
+ zend_cfg cfg; /* control flow graph */
+ int vars_count; /* number of SSA variables */
+ int sccs; /* number of SCCs */
+ zend_ssa_block *blocks; /* array of SSA blocks */
+ zend_ssa_op *ops; /* array of SSA instructions */
+ zend_ssa_var *vars; /* use/def chain of SSA variables */
+ zend_ssa_var_info *var_info;
+} zend_ssa;
+
+BEGIN_EXTERN_C()
+
+ZEND_API int zend_build_ssa(zend_arena **arena, const zend_script *script, const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa);
+ZEND_API int zend_ssa_compute_use_def_chains(zend_arena **arena, const zend_op_array *op_array, zend_ssa *ssa);
+ZEND_API int zend_ssa_rename_op(const zend_op_array *op_array, const zend_op *opline, uint32_t k, uint32_t build_flags, int ssa_vars_count, zend_ssa_op *ssa_ops, int *var);
+int zend_ssa_unlink_use_chain(zend_ssa *ssa, int op, int var);
+
+void zend_ssa_remove_predecessor(zend_ssa *ssa, int from, int to);
+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, 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)
+{
+ ssa_op += use;
+ if (ssa_op->op1_use == var) {
+ return ssa_op->op1_use_chain;
+ } else if (ssa_op->op2_use == var) {
+ return ssa_op->op2_use_chain;
+ } else {
+ return ssa_op->res_use_chain;
+ }
+}
+
+static zend_always_inline zend_ssa_phi* zend_ssa_next_use_phi(const zend_ssa *ssa, int var, const 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];
+ }
+ }
+ }
+ return NULL;
+}
+
+static zend_always_inline bool zend_ssa_is_no_val_use(const zend_op *opline, const zend_ssa_op *ssa_op, int var)
+{
+ if (opline->opcode == ZEND_ASSIGN
+ || opline->opcode == ZEND_UNSET_CV
+ || opline->opcode == ZEND_BIND_GLOBAL
+ || opline->opcode == ZEND_BIND_STATIC) {
+ return ssa_op->op1_use == var && ssa_op->op2_use != var;
+ }
+ if (opline->opcode == ZEND_FE_FETCH_R || opline->opcode == ZEND_FE_FETCH_RW) {
+ return ssa_op->op2_use == var && ssa_op->op1_use != var;
+ }
+ if (ssa_op->result_use == var
+ && opline->opcode != ZEND_ADD_ARRAY_ELEMENT
+ && opline->opcode != ZEND_ADD_ARRAY_UNPACK) {
+ return ssa_op->op1_use != var && ssa_op->op2_use != var;
+ }
+ 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/Zend/Optimizer/zend_worklist.h b/Zend/Optimizer/zend_worklist.h
new file mode 100644
index 0000000000..2f3e3dd979
--- /dev/null
+++ b/Zend/Optimizer/zend_worklist.h
@@ -0,0 +1,121 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 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: Andy Wingo <wingo@igalia.com> |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef _ZEND_WORKLIST_H_
+#define _ZEND_WORKLIST_H_
+
+#include "zend_arena.h"
+#include "zend_bitset.h"
+
+typedef struct _zend_worklist_stack {
+ int *buf;
+ int len;
+ int capacity;
+} zend_worklist_stack;
+
+#define ZEND_WORKLIST_STACK_ALLOCA(s, _len, use_heap) do { \
+ (s)->buf = (int*)do_alloca(sizeof(int) * _len, use_heap); \
+ (s)->len = 0; \
+ (s)->capacity = _len; \
+ } while (0)
+
+#define ZEND_WORKLIST_STACK_FREE_ALLOCA(s, use_heap) \
+ free_alloca((s)->buf, use_heap)
+
+static inline int zend_worklist_stack_prepare(zend_arena **arena, zend_worklist_stack *stack, int len)
+{
+ ZEND_ASSERT(len >= 0);
+
+ stack->buf = (int*)zend_arena_calloc(arena, sizeof(*stack->buf), len);
+ stack->len = 0;
+ stack->capacity = len;
+
+ return SUCCESS;
+}
+
+static inline void zend_worklist_stack_push(zend_worklist_stack *stack, int i)
+{
+ ZEND_ASSERT(stack->len < stack->capacity);
+ stack->buf[stack->len++] = i;
+}
+
+static inline int zend_worklist_stack_peek(zend_worklist_stack *stack)
+{
+ ZEND_ASSERT(stack->len);
+ return stack->buf[stack->len - 1];
+}
+
+static inline int zend_worklist_stack_pop(zend_worklist_stack *stack)
+{
+ ZEND_ASSERT(stack->len);
+ return stack->buf[--stack->len];
+}
+
+typedef struct _zend_worklist {
+ zend_bitset visited;
+ zend_worklist_stack stack;
+} zend_worklist;
+
+#define ZEND_WORKLIST_ALLOCA(w, _len, use_heap) do { \
+ (w)->stack.buf = (int*)do_alloca(ZEND_MM_ALIGNED_SIZE(sizeof(int) * _len) + sizeof(zend_ulong) * zend_bitset_len(_len), use_heap); \
+ (w)->stack.len = 0; \
+ (w)->stack.capacity = _len; \
+ (w)->visited = (zend_bitset)((char*)(w)->stack.buf + ZEND_MM_ALIGNED_SIZE(sizeof(int) * _len)); \
+ memset((w)->visited, 0, sizeof(zend_ulong) * zend_bitset_len(_len)); \
+ } while (0)
+
+#define ZEND_WORKLIST_FREE_ALLOCA(w, use_heap) \
+ free_alloca((w)->stack.buf, use_heap)
+
+static inline int zend_worklist_prepare(zend_arena **arena, zend_worklist *worklist, int len)
+{
+ ZEND_ASSERT(len >= 0);
+ worklist->visited = (zend_bitset)zend_arena_calloc(arena, sizeof(zend_ulong), zend_bitset_len(len));
+ return zend_worklist_stack_prepare(arena, &worklist->stack, len);
+}
+
+static inline int zend_worklist_len(zend_worklist *worklist)
+{
+ return worklist->stack.len;
+}
+
+static inline int zend_worklist_push(zend_worklist *worklist, int i)
+{
+ ZEND_ASSERT(i >= 0 && i < worklist->stack.capacity);
+
+ if (zend_bitset_in(worklist->visited, i)) {
+ return 0;
+ }
+
+ zend_bitset_incl(worklist->visited, i);
+ zend_worklist_stack_push(&worklist->stack, i);
+ return 1;
+}
+
+static inline int zend_worklist_peek(zend_worklist *worklist)
+{
+ return zend_worklist_stack_peek(&worklist->stack);
+}
+
+static inline int zend_worklist_pop(zend_worklist *worklist)
+{
+ /* Does not clear visited flag */
+ return zend_worklist_stack_pop(&worklist->stack);
+}
+
+#endif /* _ZEND_WORKLIST_H_ */