Skip to content

Commit 23511ec

Browse files
committed
Alternative implementation take 1
Switch to new approach and add extra checks Implement support for ZEND_SEND_VAR_EX Deduplicate dominates() function Fix comment JIT build fix Fix num args comparison
1 parent cafdfd0 commit 23511ec

File tree

8 files changed

+175
-107
lines changed

8 files changed

+175
-107
lines changed

Zend/Optimizer/dfa_pass.c

Lines changed: 60 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,46 +1066,72 @@ static bool zend_dfa_try_to_replace_result(zend_op_array *op_array, zend_ssa *ss
10661066
return 0;
10671067
}
10681068

1069+
static bool op_dominates(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *a, const zend_op *b)
1070+
{
1071+
uint32_t a_block = ssa->cfg.map[a - op_array->opcodes];
1072+
uint32_t b_block = ssa->cfg.map[b - op_array->opcodes];
1073+
if (a_block == b_block) {
1074+
return a < b;
1075+
} else {
1076+
return dominates(ssa->cfg.blocks, a_block, b_block);
1077+
}
1078+
}
1079+
1080+
static bool op_dominates_all_uses(const zend_op_array *op_array, const zend_ssa *ssa, int start) {
1081+
int use;
1082+
FOREACH_USE(ssa->vars + ssa->ops[start].op1_def, use) {
1083+
if (!op_dominates(op_array, ssa, op_array->opcodes + start, op_array->opcodes + use)) {
1084+
return false;
1085+
}
1086+
} FOREACH_USE_END();
1087+
return true;
1088+
}
1089+
10691090
/* Sets a flag on SEND ops when a copy can be a avoided. */
1070-
static void zend_dfa_optimize_send_copies(zend_op_array *op_array, zend_ssa *ssa, zend_call_info **call_map)
1091+
static void zend_dfa_optimize_send_copies(zend_op_array *op_array, const zend_ssa *ssa)
10711092
{
1072-
for (int v = 0; v < ssa->vars_count; v++) {
1073-
int var = ssa->vars[v].var;
1074-
if (var >= op_array->last_var) {
1093+
/* func_get_args() etc could make the optimization observable */
1094+
if (ssa->cfg.flags & ZEND_FUNC_VARARG) {
1095+
return;
1096+
}
1097+
1098+
for (uint32_t i = 0; i < op_array->last; i++) {
1099+
const zend_op *opline = &op_array->opcodes[i];
1100+
if ((opline->opcode != ZEND_SEND_VAR && opline->opcode != ZEND_SEND_VAR_EX) || opline->op2_type != IS_UNUSED || opline->op1_type != IS_CV) {
1101+
continue;
1102+
}
1103+
1104+
/* NULL must not be visible in backtraces */
1105+
int ssa_cv = ssa->ops[i].op1_use;
1106+
if (ssa->vars[ssa_cv].var < op_array->num_args) {
10751107
continue;
10761108
}
10771109

1078-
uint32_t type = ssa->var_info[v].type;
1079-
/* Unsetting a CV is always fine if it gets overwritten afterwards. Since type inference often infers
1080-
* very wide types, we are very loose in matching types. */
1110+
/* Unsetting a CV is always fine if it gets overwritten afterwards.
1111+
* Since type inference often infers very wide types, we are very loose in matching types. */
1112+
uint32_t type = ssa->var_info[ssa_cv].type;
10811113
if ((type & (MAY_BE_REF|MAY_BE_UNDEF)) || !(type & MAY_BE_RC1) || !(type & (MAY_BE_STRING|MAY_BE_ARRAY))) {
10821114
continue;
10831115
}
10841116

1085-
int use = ssa->vars[v].use_chain;
1086-
if (use >= 0
1087-
&& (op_array->opcodes[use].opcode == ZEND_SEND_VAR || op_array->opcodes[use].opcode == ZEND_SEND_VAR_EX) // TODO
1088-
&& op_array->opcodes[use].op2_type == IS_UNUSED) {
1089-
int next_use = zend_ssa_next_use(ssa->ops, v, use);
1090-
1091-
/* The next use must be an assignment of the call result, immediately after the call such that the
1092-
* unset variable can never be observed.
1093-
* It is also safe to optimize if there are no indirect accesses through func_get_args() etc,
1094-
* no more uses, and it is not part of a loop. */
1095-
if ((next_use < 0
1096-
&& var >= op_array->num_args /* NULL must not be visible in backtraces */
1097-
&& !(ssa->cfg.flags & ZEND_FUNC_VARARG)
1098-
&& !(ssa->cfg.blocks[ssa->cfg.map[use]].flags & ZEND_BB_LOOP_HEADER)
1099-
&& ssa->cfg.blocks[ssa->cfg.map[use]].loop_header == -1)
1100-
|| (next_use >= 0
1101-
&& op_array->opcodes[next_use].opcode == ZEND_ASSIGN
1102-
&& ssa->ops[next_use].op1_use == v
1103-
&& ssa->ops[next_use].op2_use >= 0
1104-
&& call_map[use]
1105-
&& call_map[use]->caller_call_opline + 1 == op_array->opcodes + next_use
1106-
&& ssa->ops[call_map[use]->caller_call_opline - op_array->opcodes].result_def == ssa->ops[next_use].op2_use)) {
1107-
ZEND_ASSERT(op_array->opcodes[use].extended_value == 0);
1108-
op_array->opcodes[use].extended_value = 1;
1117+
if (opline->opcode == ZEND_SEND_VAR) {
1118+
/* Check if the call dominates the assignment and the assignment dominates all the future uses of this SSA variable */
1119+
int next_use = ssa->ops[i].op1_use_chain;
1120+
if (next_use >= 0
1121+
&& op_array->opcodes[next_use].opcode == ZEND_ASSIGN
1122+
&& ssa->ops[next_use].op1_use == ssa_cv
1123+
&& ssa->ops[next_use].op2_use >= 0
1124+
&& op_dominates(op_array, ssa, opline, op_array->opcodes + next_use)) {
1125+
if (op_dominates_all_uses(op_array, ssa, next_use)) {
1126+
op_array->opcodes[i].extended_value = 1;
1127+
//fprintf(stderr, "yes optimize 1\n");
1128+
}
1129+
}
1130+
} else /* ZEND_SEND_VAR_EX */ {
1131+
ZEND_ASSERT(ssa->ops[i].op1_def != -1);
1132+
if (ssa->vars[ssa->ops[i].op1_def].no_val) {
1133+
op_array->opcodes[i].extended_value = 1;
1134+
//fprintf(stderr, "yes optimize 2\n");
11091135
}
11101136
}
11111137
}
@@ -1169,9 +1195,9 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx
11691195
#endif
11701196
}
11711197

1172-
/* Optimization should not be done on main because of globals. Pass depends on CFG & call graph. */
1173-
if (call_map && op_array->function_name) {
1174-
zend_dfa_optimize_send_copies(op_array, ssa, call_map);
1198+
/* Optimization should not be done on main because of globals. */
1199+
if (op_array->function_name) {
1200+
zend_dfa_optimize_send_copies(op_array, ssa);
11751201
}
11761202

11771203
for (v = op_array->last_var; v < ssa->vars_count; v++) {

Zend/Optimizer/zend_cfg.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,7 @@ ZEND_API void zend_cfg_compute_dominators_tree(const zend_op_array *op_array, ze
761761
}
762762
/* }}} */
763763

764-
static bool dominates(zend_basic_block *blocks, int a, int b) /* {{{ */
764+
bool dominates(const zend_basic_block *blocks, int a, int b) /* {{{ */
765765
{
766766
while (blocks[b].level > blocks[a].level) {
767767
b = blocks[b].idom;

Zend/Optimizer/zend_cfg.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ void zend_cfg_remark_reachable_blocks(const zend_op_array *op_array, zend_cfg *c
120120
ZEND_API void zend_cfg_build_predecessors(zend_arena **arena, zend_cfg *cfg);
121121
ZEND_API void zend_cfg_compute_dominators_tree(const zend_op_array *op_array, zend_cfg *cfg);
122122
ZEND_API void zend_cfg_identify_loops(const zend_op_array *op_array, zend_cfg *cfg);
123+
bool dominates(const zend_basic_block *blocks, int a, int b);
123124

124125
END_EXTERN_C()
125126

Zend/Optimizer/zend_ssa.c

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,13 @@
1818
*/
1919

2020
#include "zend_compile.h"
21+
#include "zend_cfg.h"
2122
#include "zend_dfg.h"
2223
#include "zend_ssa.h"
2324
#include "zend_dump.h"
2425
#include "zend_inference.h"
2526
#include "Optimizer/zend_optimizer_internal.h"
2627

27-
static bool dominates(const zend_basic_block *blocks, int a, int b) {
28-
while (blocks[b].level > blocks[a].level) {
29-
b = blocks[b].idom;
30-
}
31-
return a == b;
32-
}
33-
3428
static bool will_rejoin(
3529
const zend_cfg *cfg, const zend_dfg *dfg, const zend_basic_block *block,
3630
int other_successor, int exclude, int var) {

Zend/zend_vm_def.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9931,7 +9931,7 @@ ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR, op->extended_value /* extended_valu
99319931
ZEND_VM_NEXT_OPCODE();
99329932
}
99339933

9934-
ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR_EX, op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0, ZEND_SEND_VAR_EX_SIMPLE, CV|VAR, UNUSED|NUM)
9934+
ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR_EX, !op->extended_value && op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0, ZEND_SEND_VAR_EX_SIMPLE, CV|VAR, UNUSED|NUM)
99359935
{
99369936
USE_OPLINE
99379937
zval *varptr, *arg;
@@ -9953,6 +9953,25 @@ ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR_EX, op->op2_type == IS_UNUSED && op-
99539953
ZEND_VM_NEXT_OPCODE();
99549954
}
99559955

9956+
ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR_EX, op->extended_value && op->op2.num <= MAX_ARG_FLAG_NUM /* extended_value implies here OP2 UNUSED and OP1 not UNDEF or REF */, ZEND_SEND_VAR_EX_SIMPLE_EXT, CV, UNUSED|NUM)
9957+
{
9958+
USE_OPLINE
9959+
zval *varptr, *arg;
9960+
uint32_t arg_num = opline->op2.num;
9961+
9962+
if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
9963+
ZEND_VM_DISPATCH_TO_HANDLER(ZEND_SEND_REF);
9964+
}
9965+
9966+
varptr = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
9967+
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
9968+
9969+
ZVAL_COPY_VALUE(arg, varptr);
9970+
ZVAL_UNDEF(varptr);
9971+
9972+
ZEND_VM_NEXT_OPCODE();
9973+
}
9974+
99569975
ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAL, op->op1_type == IS_CONST && op->op2_type == IS_UNUSED && !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1)), ZEND_SEND_VAL_SIMPLE, CONST, NUM)
99579976
{
99589977
USE_OPLINE

Zend/zend_vm_execute.h

Lines changed: 85 additions & 58 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)