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_analyze_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, uint32_t *flags) { 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, 0, &ssa->cfg, flags) != SUCCESS) { return FAILURE; } if (*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, flags) != 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, op_array, build_flags, ssa, flags) != SUCCESS) { return FAILURE; } if (ctx->debug_level & ZEND_DUMP_DFA_SSA) { zend_dump_op_array(op_array, ZEND_DUMP_SSA, "before dfa pass", 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) != SUCCESS) { return FAILURE; } if (ctx->debug_level & ZEND_DUMP_DFA_SSA_VARS) { zend_dump_ssa_variables(op_array, ssa, 0); } return SUCCESS; }
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); } }