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); } } }
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); } } }
/* Removes unreachable blocks. This will remove both the instructions (and phis) in the * blocks, as well as remove them from the successor / predecessor lists and mark them * unreachable. Blocks already marked unreachable are not removed. */ int scdf_remove_unreachable_blocks(scdf_ctx *scdf) { zend_ssa *ssa = scdf->ssa; int i; int removed_ops = 0; for (i = 0; i < ssa->cfg.blocks_count; i++) { if (!zend_bitset_in(scdf->executable_blocks, i) && (ssa->cfg.blocks[i].flags & ZEND_BB_REACHABLE) && !kept_alive_by_live_range(scdf, i)) { removed_ops += ssa->cfg.blocks[i].len; zend_ssa_remove_block(scdf->op_array, ssa, i); } } return removed_ops; }
/* If a live range starts in a reachable block and ends in an unreachable block, we should * not eliminate the latter. While it cannot be reached, the FREE opcode of the loop var * is necessary for the correctness of temporary compaction. */ static zend_bool kept_alive_by_live_range(scdf_ctx *scdf, uint32_t block) { uint32_t i; const zend_op_array *op_array = scdf->op_array; const zend_cfg *cfg = &scdf->ssa->cfg; for (i = 0; i < op_array->last_live_range; i++) { zend_live_range *live_range = &op_array->live_range[i]; uint32_t start_block = cfg->map[live_range->start]; uint32_t end_block = cfg->map[live_range->end]; if (end_block == block && start_block != block && zend_bitset_in(scdf->executable_blocks, start_block)) { return 1; } } return 0; }
/* If a live range starts in a reachable block and ends in an unreachable block, we should * not eliminate the latter. While it cannot be reached, the FREE opcode of the loop var * is necessary for the correctness of temporary compaction. */ static zend_bool kept_alive_by_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]; for (i = block->start; i < block->start + block->len; i++) { zend_op *opline = &op_array->opcodes[i]; if (opline->opcode == ZEND_FE_FREE || (opline->opcode == ZEND_FREE && opline->extended_value == ZEND_FREE_SWITCH)) { 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; }
int zend_build_ssa(zend_arena **arena, const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, uint32_t *func_flags) /* {{{ */ { 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 tmp, gen, in; int *var = NULL; int i, j, k, changed; zend_dfg dfg; ALLOCA_FLAG(dfg_use_heap); ALLOCA_FLAG(var_use_heap); ssa->rt_constants = (build_flags & ZEND_RT_CONSTANTS); ssa_blocks = zend_arena_calloc(arena, blocks_count, sizeof(zend_ssa_block)); if (!ssa_blocks) { return FAILURE; } 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 * 5 + 1), dfg_use_heap); memset(dfg.tmp, 0, (set_size * sizeof(zend_ulong)) * (blocks_count * 5 + 1)); dfg.gen = dfg.tmp + set_size; dfg.def = dfg.gen + set_size * blocks_count; 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); } tmp = dfg.tmp; gen = dfg.gen; in = dfg.in; /* SSA construction, Step 1: Propagate "gen" sets in merge points */ do { changed = 0; for (j = 0; j < blocks_count; j++) { if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) { continue; } if (j >= 0 && (blocks[j].predecessors_count > 1 || j == 0)) { zend_bitset_copy(tmp, gen + (j * set_size), set_size); 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(tmp, tmp, gen + (i * set_size), in + (j * set_size), set_size); i = blocks[i].idom; } } if (!zend_bitset_equal(gen + (j * set_size), tmp, set_size)) { zend_bitset_copy(gen + (j * set_size), tmp, 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; } zend_bitset_clear(tmp, set_size); for (j = 0; j < blocks_count; j++) { if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) { continue; } if (blocks[j].predecessors_count > 1) { zend_bitset_clear(tmp, set_size); 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_copy(tmp, 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(tmp, tmp, gen + (i * set_size), in + (j * set_size), set_size); i = blocks[i].idom; } } } if (!zend_bitset_empty(tmp, set_size)) { i = op_array->last_var + op_array->T; while (i > 0) { i--; if (zend_bitset_in(tmp, i)) { zend_ssa_phi *phi = zend_arena_calloc(arena, 1, sizeof(zend_ssa_phi) + sizeof(int) * blocks[j].predecessors_count + sizeof(void*) * blocks[j].predecessors_count); if (!phi) { goto failure; } phi->sources = (int*)(((char*)phi) + sizeof(zend_ssa_phi)); memset(phi->sources, 0xff, sizeof(int) * blocks[j].predecessors_count); phi->use_chains = (zend_ssa_phi**)(((char*)phi->sources) + sizeof(int) * ssa->cfg.blocks[j].predecessors_count); phi->pi = -1; phi->var = i; phi->ssa_var = -1; phi->next = ssa_blocks[j].phis; ssa_blocks[j].phis = phi; } } } } } place_essa_pis(arena, op_array, build_flags, ssa, &dfg); /* SSA construction, Step ?: Phi after Pi placement based on Dominance Frontiers */ for (j = 0; j < blocks_count; j++) { if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) { continue; } if (blocks[j].predecessors_count > 1) { zend_bitset_clear(tmp, set_size); 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_copy(tmp, 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_ssa_phi *p = ssa_blocks[i].phis; while (p) { if (p) { if (p->pi >= 0) { if (zend_bitset_in(in + (j * set_size), p->var) && !zend_bitset_in(gen + (i * set_size), p->var)) { zend_bitset_incl(tmp, p->var); } } else { zend_bitset_excl(tmp, p->var); } } p = p->next; } i = blocks[i].idom; } } } if (!zend_bitset_empty(tmp, set_size)) { i = op_array->last_var + op_array->T; while (i > 0) { i--; if (zend_bitset_in(tmp, i)) { zend_ssa_phi **pp = &ssa_blocks[j].phis; while (*pp) { if ((*pp)->pi <= 0 && (*pp)->var == i) { break; } pp = &(*pp)->next; } if (*pp == NULL) { zend_ssa_phi *phi = zend_arena_calloc(arena, 1, sizeof(zend_ssa_phi) + sizeof(int) * blocks[j].predecessors_count + sizeof(void*) * blocks[j].predecessors_count); if (!phi) { goto failure; } phi->sources = (int*)(((char*)phi) + sizeof(zend_ssa_phi)); memset(phi->sources, 0xff, sizeof(int) * blocks[j].predecessors_count); phi->use_chains = (zend_ssa_phi**)(((char*)phi->sources) + sizeof(int) * ssa->cfg.blocks[j].predecessors_count); phi->pi = -1; phi->var = i; phi->ssa_var = -1; phi->next = NULL; *pp = phi; } } } } } } 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, 0xff, (op_array->last_var + 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) { failure: 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; }
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; int var_to_free = -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 (ZEND_RESULT_TYPE(opline) & (IS_VAR | IS_TMP_VAR)) { start_of_T[VAR_NUM(ZEND_RESULT(opline).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 ((ZEND_OP1_TYPE(opline) & (IS_VAR | IS_TMP_VAR))) { currT = VAR_NUM(ZEND_OP1(opline).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); ZEND_OP1(opline).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 temorary 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); } ZEND_OP1(opline).var = NUM_VAR(map_T[currT] + offset); } } if ((ZEND_OP2_TYPE(opline) & (IS_VAR | IS_TMP_VAR))) { currT = VAR_NUM(ZEND_OP2(opline).var) - offset; if (!zend_bitset_in(valid_T, currT)) { GET_AVAILABLE_T(); map_T[currT] = i; zend_bitset_incl(valid_T, currT); } ZEND_OP2(opline).var = NUM_VAR(map_T[currT] + offset); } if (ZEND_RESULT_TYPE(opline) & (IS_VAR | IS_TMP_VAR)) { currT = VAR_NUM(ZEND_RESULT(opline).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]); } } ZEND_RESULT(opline).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); ZEND_RESULT(opline).var = NUM_VAR(i + offset); } } if (var_to_free >= 0) { zend_bitset_excl(taken_T, var_to_free); var_to_free = -1; } opline--; } if (op_array->live_range) { for (i = 0; i < op_array->last_live_range; i++) { op_array->live_range[i].var = NUM_VAR(map_T[VAR_NUM(op_array->live_range[i].var & ~ZEND_LIVE_MASK) - offset] + offset) | (op_array->live_range[i].var & ZEND_LIVE_MASK); } } zend_arena_release(&ctx->arena, checkpoint); op_array->T = max + 1; }
/* 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 TMP references in live ranges */ if (op_array->live_range) { for (i = 0; i < op_array->last_live_range; i++) { op_array->live_range[i].var = (op_array->live_range[i].var & ZEND_LIVE_MASK) | NUM_VAR(vars_map[VAR_NUM(op_array->live_range[i].var & ~ZEND_LIVE_MASK)]); } } /* Update CV name table */ if (num_cvs != op_array->last_var) { 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(op_array->vars[i]); } } efree(op_array->vars); op_array->vars = names; } op_array->last_var = num_cvs; op_array->T = num_tmps; free_alloca(vars_map, use_heap2); }
void scdf_solve(scdf_ctx *scdf, const char *name) { zend_ssa *ssa = scdf->ssa; DEBUG_PRINT("Start SCDF solve (%s)\n", name); while (!zend_bitset_empty(scdf->instr_worklist, scdf->instr_worklist_len) || !zend_bitset_empty(scdf->phi_var_worklist, scdf->phi_var_worklist_len) || !zend_bitset_empty(scdf->block_worklist, scdf->block_worklist_len) ) { int i; while ((i = zend_bitset_pop_first(scdf->phi_var_worklist, scdf->phi_var_worklist_len)) >= 0) { zend_ssa_phi *phi = ssa->vars[i].definition_phi; ZEND_ASSERT(phi); if (zend_bitset_in(scdf->executable_blocks, phi->block)) { scdf->handlers.visit_phi(scdf, phi); } } while ((i = zend_bitset_pop_first(scdf->instr_worklist, scdf->instr_worklist_len)) >= 0) { int block_num = ssa->cfg.map[i]; if (zend_bitset_in(scdf->executable_blocks, block_num)) { zend_basic_block *block = &ssa->cfg.blocks[block_num]; zend_op *opline = &scdf->op_array->opcodes[i]; zend_ssa_op *ssa_op = &ssa->ops[i]; if (opline->opcode == ZEND_OP_DATA) { opline--; ssa_op--; } scdf->handlers.visit_instr(scdf, opline, ssa_op); if (i == block->start + block->len - 1) { if (block->successors_count == 1) { scdf_mark_edge_feasible(scdf, block_num, block->successors[0]); } else if (block->successors_count > 1) { scdf->handlers.mark_feasible_successors(scdf, block_num, block, opline, ssa_op); } } } } while ((i = zend_bitset_pop_first(scdf->block_worklist, scdf->block_worklist_len)) >= 0) { /* This block is now live. Interpret phis and instructions in it. */ zend_basic_block *block = &ssa->cfg.blocks[i]; zend_ssa_block *ssa_block = &ssa->blocks[i]; DEBUG_PRINT("Pop block %d from worklist\n", i); zend_bitset_incl(scdf->executable_blocks, i); { zend_ssa_phi *phi; for (phi = ssa_block->phis; phi; phi = phi->next) { zend_bitset_excl(scdf->phi_var_worklist, phi->ssa_var); scdf->handlers.visit_phi(scdf, phi); } } if (block->len == 0) { /* Zero length blocks don't have a last instruction that would normally do this */ scdf_mark_edge_feasible(scdf, i, block->successors[0]); } else { zend_op *opline; int j, end = block->start + block->len; for (j = block->start; j < end; j++) { opline = &scdf->op_array->opcodes[j]; zend_bitset_excl(scdf->instr_worklist, j); if (opline->opcode != ZEND_OP_DATA) { scdf->handlers.visit_instr(scdf, opline, &ssa->ops[j]); } } if (block->successors_count == 1) { scdf_mark_edge_feasible(scdf, i, block->successors[0]); } else if (block->successors_count > 1) { if (opline->opcode == ZEND_OP_DATA) { opline--; j--; } scdf->handlers.mark_feasible_successors(scdf, i, block, opline, &ssa->ops[j-1]); } } } } }
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); } }