diff options
Diffstat (limited to 'Zend/Optimizer')
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_ */ |