Skip to content

Commit 665ed84

Browse files
committed
Improved DCE performance, by avoiding redundand checks and repeatable iterations.
1 parent 3f42ce1 commit 665ed84

File tree

1 file changed

+63
-49
lines changed
  • ext/opcache/Optimizer

1 file changed

+63
-49
lines changed

ext/opcache/Optimizer/dce.c

Lines changed: 63 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ typedef struct {
5151
zend_bitset phi_dead;
5252
zend_bitset instr_worklist;
5353
zend_bitset phi_worklist;
54+
zend_bitset phi_worklist_no_val;
5455
uint32_t instr_worklist_len;
5556
uint32_t phi_worklist_len;
5657
unsigned reorder_dtor_effects : 1;
@@ -213,43 +214,51 @@ static inline zend_bool may_have_side_effects(
213214
}
214215
}
215216

216-
static inline void add_to_worklists(context *ctx, int var_num) {
217+
static zend_always_inline void add_to_worklists(context *ctx, int var_num, int check) {
217218
zend_ssa_var *var = &ctx->ssa->vars[var_num];
218219
if (var->definition >= 0) {
219-
if (zend_bitset_in(ctx->instr_dead, var->definition)) {
220+
if (!check || zend_bitset_in(ctx->instr_dead, var->definition)) {
220221
zend_bitset_incl(ctx->instr_worklist, var->definition);
221222
}
222223
} else if (var->definition_phi) {
223-
if (zend_bitset_in(ctx->phi_dead, var_num)) {
224+
if (!check || zend_bitset_in(ctx->phi_dead, var_num)) {
224225
zend_bitset_incl(ctx->phi_worklist, var_num);
225226
}
226227
}
227228
}
228229

229-
static inline void add_to_phi_worklist_only(context *ctx, int var_num) {
230+
static inline void add_to_phi_worklist_no_val(context *ctx, int var_num) {
230231
zend_ssa_var *var = &ctx->ssa->vars[var_num];
231232
if (var->definition_phi && zend_bitset_in(ctx->phi_dead, var_num)) {
232-
zend_bitset_incl(ctx->phi_worklist, var_num);
233+
zend_bitset_incl(ctx->phi_worklist_no_val, var_num);
233234
}
234235
}
235236

236-
static inline void add_operands_to_worklists(context *ctx, zend_op *opline, zend_ssa_op *ssa_op) {
237+
static zend_always_inline void add_operands_to_worklists(context *ctx, zend_op *opline, zend_ssa_op *ssa_op, int check) {
237238
if (ssa_op->result_use >= 0) {
238-
add_to_worklists(ctx, ssa_op->result_use);
239+
add_to_worklists(ctx, ssa_op->result_use, check);
239240
}
240-
if (ssa_op->op1_use >= 0 && !zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op1_use)) {
241-
add_to_worklists(ctx, ssa_op->op1_use);
241+
if (ssa_op->op1_use >= 0) {
242+
if (!zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op1_use)) {
243+
add_to_worklists(ctx, ssa_op->op1_use, check);
244+
} else {
245+
add_to_phi_worklist_no_val(ctx, ssa_op->op1_use);
246+
}
242247
}
243-
if (ssa_op->op2_use >= 0 && !zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op2_use)) {
244-
add_to_worklists(ctx, ssa_op->op2_use);
248+
if (ssa_op->op2_use >= 0) {
249+
if (!zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op2_use)) {
250+
add_to_worklists(ctx, ssa_op->op2_use, check);
251+
} else {
252+
add_to_phi_worklist_no_val(ctx, ssa_op->op2_use);
253+
}
245254
}
246255
}
247256

248-
static inline void add_phi_sources_to_worklists(context *ctx, zend_ssa_phi *phi) {
257+
static zend_always_inline void add_phi_sources_to_worklists(context *ctx, zend_ssa_phi *phi, int check) {
249258
zend_ssa *ssa = ctx->ssa;
250259
int source;
251260
FOREACH_PHI_SOURCE(phi, source) {
252-
add_to_worklists(ctx, source);
261+
add_to_worklists(ctx, source, check);
253262
} FOREACH_PHI_SOURCE_END();
254263
}
255264

@@ -303,6 +312,7 @@ static zend_bool dce_instr(context *ctx, zend_op *opline, zend_ssa_op *ssa_op) {
303312
ssa_op->op1_use = free_var;
304313
ssa_op->op1_use_chain = ssa->vars[free_var].use_chain;
305314
ssa->vars[free_var].use_chain = ssa_op - ssa->ops;
315+
return 0;
306316
}
307317
return 1;
308318
}
@@ -468,64 +478,70 @@ int dce_optimize_op_array(zend_op_array *op_array, zend_ssa *ssa, zend_bool reor
468478
ctx.phi_worklist_len = zend_bitset_len(ssa->vars_count);
469479
ctx.phi_worklist = alloca(sizeof(zend_ulong) * ctx.phi_worklist_len);
470480
memset(ctx.phi_worklist, 0, sizeof(zend_ulong) * ctx.phi_worklist_len);
481+
ctx.phi_worklist_no_val = alloca(sizeof(zend_ulong) * ctx.phi_worklist_len);
482+
memset(ctx.phi_worklist_no_val, 0, sizeof(zend_ulong) * ctx.phi_worklist_len);
471483

472484
/* Optimistically assume all instructions and phis to be dead */
473485
ctx.instr_dead = alloca(sizeof(zend_ulong) * ctx.instr_worklist_len);
474-
memset(ctx.instr_dead, 0xff, sizeof(zend_ulong) * ctx.instr_worklist_len);
486+
memset(ctx.instr_dead, 0, sizeof(zend_ulong) * ctx.instr_worklist_len);
475487
ctx.phi_dead = alloca(sizeof(zend_ulong) * ctx.phi_worklist_len);
476488
memset(ctx.phi_dead, 0xff, sizeof(zend_ulong) * ctx.phi_worklist_len);
477489

478-
/* Mark instruction with side effects as live */
479-
FOREACH_INSTR_NUM(i) {
480-
if (may_have_side_effects(op_array, ssa, &op_array->opcodes[i], &ssa->ops[i], ctx.reorder_dtor_effects)
481-
|| zend_may_throw(&op_array->opcodes[i], op_array, ssa)
482-
|| has_varargs) {
483-
zend_bitset_excl(ctx.instr_dead, i);
484-
add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i]);
490+
/* Mark reacable instruction without side effects as dead */
491+
int b = ssa->cfg.blocks_count;
492+
while (b > 0) {
493+
b--;
494+
zend_basic_block *block = &ssa->cfg.blocks[b];
495+
if (!(block->flags & ZEND_BB_REACHABLE)) {
496+
continue;
485497
}
486-
} FOREACH_INSTR_NUM_END();
498+
i = block->start + block->len;
499+
while (i > block->start) {
500+
i--;
501+
502+
if (zend_bitset_in(ctx.instr_worklist, i)) {
503+
zend_bitset_excl(ctx.instr_worklist, i);
504+
add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], 0);
505+
} else if (may_have_side_effects(op_array, ssa, &op_array->opcodes[i], &ssa->ops[i], ctx.reorder_dtor_effects)
506+
|| zend_may_throw(&op_array->opcodes[i], op_array, ssa)
507+
|| has_varargs) {
508+
add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], 0);
509+
} else {
510+
zend_bitset_incl(ctx.instr_dead, i);
511+
}
512+
513+
}
514+
}
487515

488516
/* Propagate liveness backwards to all definitions of used vars */
489517
while (!zend_bitset_empty(ctx.instr_worklist, ctx.instr_worklist_len)
490518
|| !zend_bitset_empty(ctx.phi_worklist, ctx.phi_worklist_len)) {
491519
while ((i = zend_bitset_pop_first(ctx.instr_worklist, ctx.instr_worklist_len)) >= 0) {
492520
zend_bitset_excl(ctx.instr_dead, i);
493-
add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i]);
521+
add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], 1);
494522
}
495523
while ((i = zend_bitset_pop_first(ctx.phi_worklist, ctx.phi_worklist_len)) >= 0) {
496524
zend_bitset_excl(ctx.phi_dead, i);
497-
add_phi_sources_to_worklists(&ctx, ssa->vars[i].definition_phi);
525+
zend_bitset_excl(ctx.phi_worklist_no_val, i);
526+
add_phi_sources_to_worklists(&ctx, ssa->vars[i].definition_phi, 1);
498527
}
499528
}
500529

501530
/* Eliminate dead instructions */
502-
FOREACH_INSTR_NUM(i) {
503-
if (zend_bitset_in(ctx.instr_dead, i)) {
504-
if (dce_instr(&ctx, &op_array->opcodes[i], &ssa->ops[i])) {
505-
removed_ops++;
506-
}
507-
}
508-
} FOREACH_INSTR_NUM_END();
531+
ZEND_BITSET_FOREACH(ctx.instr_dead, ctx.instr_worklist_len, i) {
532+
removed_ops += dce_instr(&ctx, &op_array->opcodes[i], &ssa->ops[i]);
533+
} ZEND_BITSET_FOREACH_END();
509534

510535
/* Improper uses don't count as "uses" for the purpose of instruction elimination,
511-
* but we have to retain phis defining them. Push those phis to the worklist. */
512-
FOREACH_INSTR_NUM(i) {
513-
if (ssa->ops[i].op1_use >= 0 && zend_ssa_is_no_val_use(&op_array->opcodes[i], &ssa->ops[i], ssa->ops[i].op1_use)) {
514-
add_to_phi_worklist_only(&ctx, ssa->ops[i].op1_use);
515-
}
516-
if (ssa->ops[i].op2_use >= 0 && zend_ssa_is_no_val_use(&op_array->opcodes[i], &ssa->ops[i], ssa->ops[i].op2_use)) {
517-
add_to_phi_worklist_only(&ctx, ssa->ops[i].op2_use);
518-
}
519-
} FOREACH_INSTR_NUM_END();
520-
521-
/* Propagate this information backwards, marking any phi with an improperly used
536+
* but we have to retain phis defining them.
537+
* Propagate this information backwards, marking any phi with an improperly used
522538
* target as non-dead. */
523-
while ((i = zend_bitset_pop_first(ctx.phi_worklist, ctx.phi_worklist_len)) >= 0) {
539+
while ((i = zend_bitset_pop_first(ctx.phi_worklist_no_val, ctx.phi_worklist_len)) >= 0) {
524540
zend_ssa_phi *phi = ssa->vars[i].definition_phi;
525541
int source;
526542
zend_bitset_excl(ctx.phi_dead, i);
527543
FOREACH_PHI_SOURCE(phi, source) {
528-
add_to_phi_worklist_only(&ctx, source);
544+
add_to_phi_worklist_no_val(&ctx, source);
529545
} FOREACH_PHI_SOURCE_END();
530546
}
531547

@@ -534,14 +550,12 @@ int dce_optimize_op_array(zend_op_array *op_array, zend_ssa *ssa, zend_bool reor
534550
if (zend_bitset_in(ctx.phi_dead, phi->ssa_var)) {
535551
zend_ssa_remove_uses_of_var(ssa, phi->ssa_var);
536552
zend_ssa_remove_phi(ssa, phi);
553+
} else {
554+
/* Remove trivial phis (phis with identical source operands) */
555+
try_remove_trivial_phi(&ctx, phi);
537556
}
538557
} FOREACH_PHI_END();
539558

540-
/* Remove trivial phis (phis with identical source operands) */
541-
FOREACH_PHI(phi) {
542-
try_remove_trivial_phi(&ctx, phi);
543-
} FOREACH_PHI_END();
544-
545559
removed_ops += simplify_jumps(ssa, op_array);
546560

547561
return removed_ops;

0 commit comments

Comments
 (0)