void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa) { 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 i; int remove_nops = 0; // 1: #1.T = OP_Y | #3.CV = OP_Y // 2: ASSIGN #2.CV [undef,scalar] -> #3.CV, #1.T | NOP // -- // 2: ASSIGN #2.CV [undef,scalar] -> #3.CV, X | 3.CV = QM_ASSIGN X for (i = 0; i < ssa->vars_count; i++) { int op2 = ssa->vars[i].definition; if (op2 >= 0 && op_array->opcodes[op2].opcode == ZEND_ASSIGN && op_array->opcodes[op2].op1_type == IS_CV && !RETURN_VALUE_USED(&op_array->opcodes[op2]) ) { int var2 = ssa->ops[op2].op1_use; if (var2 >= 0 && !(ssa->var_info[var2].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) ) { if ((op_array->opcodes[op2].op2_type & (IS_TMP_VAR|IS_VAR)) && ssa->ops[op2].op2_use >= 0 && !(ssa->var_info[ssa->ops[op2].op2_use].type & MAY_BE_REF) && ssa->vars[ssa->ops[op2].op2_use].definition >= 0 && ssa->ops[ssa->vars[ssa->ops[op2].op2_use].definition].result_def == ssa->ops[op2].op2_use && ssa->ops[ssa->vars[ssa->ops[op2].op2_use].definition].result_use < 0 && ssa->vars[ssa->ops[op2].op2_use].use_chain == op2 && ssa->ops[op2].op2_use_chain < 0 && !ssa->vars[ssa->ops[op2].op2_use].phi_use_chain && !ssa->vars[ssa->ops[op2].op2_use].sym_use_chain /* see Zend/tests/generators/aborted_yield_during_new.phpt */ && op_array->opcodes[ssa->vars[ssa->ops[op2].op2_use].definition].opcode != ZEND_NEW ) { int var1 = ssa->ops[op2].op2_use; int op1 = ssa->vars[var1].definition; int var3 = i; if (zend_ssa_unlink_use_chain(ssa, op2, var2)) { /* Reconstruct SSA */ ssa->vars[var3].definition = op1; ssa->ops[op1].result_def = var3; ssa->vars[var1].definition = -1; ssa->vars[var1].use_chain = -1; ssa->ops[op2].op1_use = -1; ssa->ops[op2].op2_use = -1; ssa->ops[op2].op1_def = -1; ssa->ops[op2].op1_use_chain = -1; /* Update opcodes */ op_array->opcodes[op1].result_type = op_array->opcodes[op2].op1_type; op_array->opcodes[op1].result.var = op_array->opcodes[op2].op1.var; MAKE_NOP(&op_array->opcodes[op2]); remove_nops = 1; } } else if (op_array->opcodes[op2].op2_type == IS_CONST || ((op_array->opcodes[op2].op2_type & (IS_TMP_VAR|IS_VAR|IS_CV)) && ssa->ops[op2].op2_use >= 0 && ssa->ops[op2].op2_def < 0) ) { int var3 = i; if (zend_ssa_unlink_use_chain(ssa, op2, var2)) { /* Reconstruct SSA */ ssa->ops[op2].result_def = var3; ssa->ops[op2].op1_def = -1; ssa->ops[op2].op1_use = ssa->ops[op2].op2_use; ssa->ops[op2].op1_use_chain = ssa->ops[op2].op2_use_chain; ssa->ops[op2].op2_use = -1; ssa->ops[op2].op2_use_chain = -1; /* Update opcode */ op_array->opcodes[op2].result_type = op_array->opcodes[op2].op1_type; op_array->opcodes[op2].result.var = op_array->opcodes[op2].op1.var; op_array->opcodes[op2].op1_type = op_array->opcodes[op2].op2_type; op_array->opcodes[op2].op1.var = op_array->opcodes[op2].op2.var; op_array->opcodes[op2].op2_type = IS_UNUSED; op_array->opcodes[op2].op2.var = 0; op_array->opcodes[op2].opcode = ZEND_QM_ASSIGN; } } } } } if (remove_nops) { zend_ssa_remove_nops(op_array, ssa); } } if (ctx->debug_level & ZEND_DUMP_AFTER_DFA_PASS) { zend_dump_op_array(op_array, ZEND_DUMP_SSA, "after dfa pass", ssa); } }
void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa) { 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; zval tmp; 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; /* 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 && (OP2_INFO() & MAY_BE_ANY) == MAY_BE_DOUBLE && Z_TYPE_INFO_P(CT_CONSTANT_EX(op_array, opline->op1.constant)) == IS_LONG ) { // op_1: #v.? = ADD long(?), #?.? [double] => #v.? = ADD double(?), #?.? [double] zval *zv = CT_CONSTANT_EX(op_array, opline->op1.constant); ZVAL_DOUBLE(&tmp, zval_get_double(zv)); opline->op1.constant = zend_optimizer_add_literal(op_array, &tmp); } else if (opline->op1_type != IS_CONST && opline->op2_type == IS_CONST && (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 *zv = CT_CONSTANT_EX(op_array, opline->op2.constant); ZVAL_DOUBLE(&tmp, zval_get_double(zv)); opline->op2.constant = zend_optimizer_add_literal(op_array, &tmp); } } } if (ssa->vars[v].var >= op_array->last_var) { /* skip TMP and VAR */ continue; } 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 /* see Zend/tests/generators/aborted_yield_during_new.phpt */ && op_array->opcodes[ssa->vars[src_var].definition].opcode != ZEND_NEW ) { 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; } } 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 (zend_ssa_unlink_use_chain(ssa, op_1, orig_var)) { /* Reconstruct SSA */ ssa->ops[op_1].result_def = v; ssa->ops[op_1].op1_def = -1; ssa->ops[op_1].op1_use = ssa->ops[op_1].op2_use; ssa->ops[op_1].op1_use_chain = ssa->ops[op_1].op2_use_chain; ssa->ops[op_1].op2_use = -1; ssa->ops[op_1].op2_use_chain = -1; /* Update opcode */ opline->result_type = opline->op1_type; opline->result.var = opline->op1.var; opline->op1_type = opline->op2_type; opline->op1.var = opline->op2.var; opline->op2_type = IS_UNUSED; opline->op2.var = 0; opline->opcode = ZEND_QM_ASSIGN; } } } } else if (opline->opcode == ZEND_ASSIGN_ADD && opline->extended_value == 0 && 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; SET_UNUSED(opline->op2); } else if (opline->opcode == ZEND_ASSIGN_SUB && opline->extended_value == 0 && 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; SET_UNUSED(opline->op2); } else if (opline->opcode == ZEND_VERIFY_RETURN_TYPE && 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 && (ssa->var_info[ssa->ops[op_1].op1_use].type & (MAY_BE_ANY|MAY_BE_UNDEF)) == (ssa->var_info[ssa->ops[op_1].op1_def].type & MAY_BE_ANY)) { // op_1: VERIFY_RETURN_TYPE #orig_var.CV [T] -> #v.CV [T] => NOP int orig_var = ssa->ops[op_1].op1_use; int ret = ssa->vars[v].use_chain; ssa->vars[orig_var].use_chain = ret; ssa->ops[ret].op1_use = orig_var; 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; } 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_ADD || opline->opcode == ZEND_ASSIGN_SUB || opline->opcode == ZEND_ASSIGN_MUL || opline->opcode == ZEND_ASSIGN_DIV || opline->opcode == ZEND_ASSIGN_MOD || opline->opcode == ZEND_ASSIGN_SL || opline->opcode == ZEND_ASSIGN_SR || opline->opcode == ZEND_ASSIGN_BW_OR || opline->opcode == ZEND_ASSIGN_BW_AND || opline->opcode == ZEND_ASSIGN_BW_XOR) && opline->extended_value == 0) { // op_1: ASSIGN_ADD #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 -= (ZEND_ASSIGN_ADD - ZEND_ADD); opline->result_type = opline->op1_type; opline->result.var = opline->op1.var; } } if (remove_nops) { zend_ssa_remove_nops(op_array, ssa); } } if (ctx->debug_level & ZEND_DUMP_AFTER_DFA_PASS) { zend_dump_op_array(op_array, ZEND_DUMP_SSA, "after dfa pass", ssa); } }
int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa) { zend_func_info *func_info = ZEND_FUNC_INFO(op_array); int removed_ops = 0; if (func_info->callee_info) { zend_call_info *call_info = func_info->callee_info; do { if (call_info->caller_call_opline->opcode == ZEND_DO_ICALL && call_info->callee_func && ZSTR_LEN(call_info->callee_func->common.function_name) == sizeof("in_array")-1 && memcmp(ZSTR_VAL(call_info->callee_func->common.function_name), "in_array", sizeof("in_array")-1) == 0 && (call_info->caller_init_opline->extended_value == 2 || (call_info->caller_init_opline->extended_value == 3 && (call_info->caller_call_opline - 1)->opcode == ZEND_SEND_VAL && (call_info->caller_call_opline - 1)->op1_type == IS_CONST))) { zend_op *send_array; zend_op *send_needly; zend_bool strict = 0; if (call_info->caller_init_opline->extended_value == 2) { send_array = call_info->caller_call_opline - 1; send_needly = call_info->caller_call_opline - 2; } else { if (zend_is_true(CT_CONSTANT_EX(op_array, (call_info->caller_call_opline - 1)->op1.constant))) { strict = 1; } send_array = call_info->caller_call_opline - 2; send_needly = call_info->caller_call_opline - 3; } if (send_array->opcode == ZEND_SEND_VAL && send_array->op1_type == IS_CONST && Z_TYPE_P(CT_CONSTANT_EX(op_array, send_array->op1.constant)) == IS_ARRAY && (send_needly->opcode == ZEND_SEND_VAL || send_needly->opcode == ZEND_SEND_VAR) ) { int ok = 1; HashTable *src = Z_ARRVAL_P(CT_CONSTANT_EX(op_array, send_array->op1.constant)); HashTable *dst; zval *val, tmp; zend_ulong idx; ZVAL_TRUE(&tmp); dst = 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++; } }