Skip to content

Commit e1eb08b

Browse files
committed
Implement implicit move optimisation
1 parent 56f916e commit e1eb08b

File tree

9 files changed

+259
-74
lines changed

9 files changed

+259
-74
lines changed

Zend/Optimizer/dfa_pass.c

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,10 @@ int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa)
466466
int var_num = ssa_op->op1_use;
467467
zend_ssa_var *var = ssa->vars + var_num;
468468

469-
ZEND_ASSERT(ssa_op->op1_def < 0);
469+
if (ssa_op->op1_def >= 0) {
470+
zend_ssa_replace_op1_def_op1_use(ssa, ssa_op);
471+
}
472+
470473
zend_ssa_unlink_use_chain(ssa, op_num, ssa_op->op1_use);
471474
ssa_op->op1_use = -1;
472475
ssa_op->op1_use_chain = -1;
@@ -1066,6 +1069,52 @@ static bool zend_dfa_try_to_replace_result(zend_op_array *op_array, zend_ssa *ss
10661069
return 0;
10671070
}
10681071

1072+
/* Sets a flag on SEND ops when a copy can be a avoided. */
1073+
static void zend_dfa_optimize_send_copies(zend_op_array *op_array, zend_ssa *ssa)
1074+
{
1075+
/* func_get_args(), indirect accesses and exceptions could make the optimization observable.
1076+
* The latter two cases are already tested before applying the DFA pass. */
1077+
if (ssa->cfg.flags & ZEND_FUNC_VARARG) {
1078+
return;
1079+
}
1080+
1081+
for (uint32_t i = 0; i < op_array->last; i++) {
1082+
zend_op *opline = op_array->opcodes + i;
1083+
if ((opline->opcode != ZEND_SEND_VAR && opline->opcode != ZEND_SEND_VAR_EX) || opline->op2_type != IS_UNUSED || opline->op1_type != IS_CV) {
1084+
continue;
1085+
}
1086+
1087+
zend_ssa_op *ssa_op = ssa->ops + i;
1088+
int op1_def = ssa_op->op1_def;
1089+
if (op1_def == -1) {
1090+
continue;
1091+
}
1092+
1093+
int ssa_cv = ssa_op->op1_use;
1094+
1095+
#if 0
1096+
/* Argument move must not be observable in backtraces */
1097+
if (ssa->vars[ssa_cv].var < op_array->num_args) {
1098+
continue;
1099+
}
1100+
#endif
1101+
1102+
/* Unsetting a CV is always fine if it gets overwritten afterwards.
1103+
* Since type inference often infers very wide types, we are very loose in matching types. */
1104+
uint32_t type = ssa->var_info[ssa_cv].type;
1105+
if ((type & (MAY_BE_REF|MAY_BE_UNDEF)) || !(type & MAY_BE_RC1) || !(type & (MAY_BE_STRING|MAY_BE_ARRAY))) {
1106+
continue;
1107+
}
1108+
1109+
zend_ssa_var *ssa_var = ssa->vars + op1_def;
1110+
1111+
if (ssa_var->no_val && !ssa_var->alias) {
1112+
/* Flag will be used by VM type spec handler */
1113+
opline->extended_value = 1;
1114+
}
1115+
}
1116+
}
1117+
10691118
void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, zend_call_info **call_map)
10701119
{
10711120
if (ctx->debug_level & ZEND_DUMP_BEFORE_DFA_PASS) {
@@ -1124,6 +1173,14 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx
11241173
#endif
11251174
}
11261175

1176+
/* Optimization should not be done on main because of globals. */
1177+
if (op_array->function_name) {
1178+
zend_dfa_optimize_send_copies(op_array, ssa);
1179+
#if ZEND_DEBUG_DFA
1180+
ssa_verify_integrity(op_array, ssa, "after optimize send copies");
1181+
#endif
1182+
}
1183+
11271184
for (v = op_array->last_var; v < ssa->vars_count; v++) {
11281185

11291186
op_1 = ssa->vars[v].definition;

Zend/Optimizer/sccp.c

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ typedef struct _sccp_ctx {
9898
#define MAKE_TOP(zv) (Z_TYPE_INFO_P(zv) = TOP)
9999
#define MAKE_BOT(zv) (Z_TYPE_INFO_P(zv) = BOT)
100100

101-
static void scp_dump_value(zval *zv) {
101+
static void scp_dump_value(const zval *zv) {
102102
if (IS_TOP(zv)) {
103103
fprintf(stderr, " top");
104104
} else if (IS_BOT(zv)) {
@@ -1050,6 +1050,12 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
10501050
case ZEND_SEND_VAL:
10511051
case ZEND_SEND_VAR:
10521052
{
1053+
SKIP_IF_TOP(op1);
1054+
1055+
if (opline->opcode == ZEND_SEND_VAR) {
1056+
SET_RESULT(op1, op1);
1057+
}
1058+
10531059
/* If the value of a SEND for an ICALL changes, we need to reconsider the
10541060
* ICALL result value. Otherwise we can ignore the opcode. */
10551061
zend_call_info *call;
@@ -1058,7 +1064,7 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
10581064
}
10591065

10601066
call = ctx->call_map[opline - ctx->scdf.op_array->opcodes];
1061-
if (IS_TOP(op1) || !call || !call->caller_call_opline
1067+
if (!call || !call->caller_call_opline
10621068
|| call->caller_call_opline->opcode != ZEND_DO_ICALL) {
10631069
return;
10641070
}
@@ -2034,8 +2040,14 @@ static int remove_call(sccp_ctx *ctx, zend_op *opline, zend_ssa_op *ssa_op)
20342040
&ssa->ops[call->caller_init_opline - op_array->opcodes]);
20352041

20362042
for (i = 0; i < call->num_args; i++) {
2037-
zend_ssa_remove_instr(ssa, call->arg_info[i].opline,
2038-
&ssa->ops[call->arg_info[i].opline - op_array->opcodes]);
2043+
zend_op *op = call->arg_info[i].opline;
2044+
zend_ssa_op *this_ssa_op = &ssa->ops[op - op_array->opcodes];
2045+
2046+
if (op->opcode == ZEND_SEND_VAR && this_ssa_op->op1_def >= 0) {
2047+
zend_ssa_replace_op1_def_op1_use(ssa, this_ssa_op);
2048+
}
2049+
2050+
zend_ssa_remove_instr(ssa, op, this_ssa_op);
20392051
}
20402052

20412053
// TODO: remove call_info completely???
@@ -2188,6 +2200,10 @@ static int try_remove_definition(sccp_ctx *ctx, int var_num, zend_ssa_var *var,
21882200
return 0;
21892201
}
21902202

2203+
if (opline->opcode == ZEND_SEND_VAR) {
2204+
return 0;
2205+
}
2206+
21912207
/* Compound assign or incdec -> convert to direct ASSIGN */
21922208

21932209
if (!value) {
@@ -2330,6 +2346,9 @@ static int replace_constant_operands(sccp_ctx *ctx) {
23302346
FOREACH_USE(var, use) {
23312347
zend_op *opline = &op_array->opcodes[use];
23322348
zend_ssa_op *ssa_op = &ssa->ops[use];
2349+
if (opline->opcode == ZEND_SEND_VAR && ssa_op->op1_use == i && ssa_op->op1_def >= 0) {
2350+
zend_ssa_replace_op1_def_op1_use(ssa, ssa_op);
2351+
}
23332352
if (try_replace_op1(ctx, opline, ssa_op, i, value)) {
23342353
if (opline->opcode == ZEND_NOP) {
23352354
removed_ops++;

Zend/Optimizer/zend_dfg.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ static zend_always_inline void _zend_dfg_add_use_def_op(const zend_op_array *op_
174174
}
175175
break;
176176
case ZEND_SEND_VAR:
177+
if (opline->op1_type == IS_CV && ((build_flags & ZEND_SSA_RC_INFERENCE) || opline->op2_type == IS_UNUSED)) {
178+
goto add_op1_def;
179+
}
180+
break;
177181
case ZEND_CAST:
178182
case ZEND_QM_ASSIGN:
179183
case ZEND_JMP_SET:

Zend/Optimizer/zend_inference.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2950,6 +2950,9 @@ static zend_always_inline zend_result _zend_update_type_info(
29502950
if (t1 & (MAY_BE_RC1|MAY_BE_REF)) {
29512951
tmp |= MAY_BE_RCN;
29522952
}
2953+
if ((t1 & (MAY_BE_ARRAY|MAY_BE_STRING)) && (t1 & MAY_BE_RC1) && !(t1 & (MAY_BE_UNDEF|MAY_BE_REF)) && ssa_vars[ssa_op->op1_def].no_val && !ssa_vars[ssa_op->op1_def].alias) {
2954+
tmp |= MAY_BE_UNDEF;
2955+
}
29532956
UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
29542957
COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
29552958
}
@@ -2991,6 +2994,9 @@ static zend_always_inline zend_result _zend_update_type_info(
29912994
case ZEND_SEND_FUNC_ARG:
29922995
if (ssa_op->op1_def >= 0) {
29932996
tmp = (t1 & MAY_BE_UNDEF)|MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
2997+
if (opline->opcode == ZEND_SEND_VAR_EX && (t1 & (MAY_BE_ARRAY|MAY_BE_STRING)) && (t1 & MAY_BE_RC1) && !(t1 & (MAY_BE_UNDEF|MAY_BE_REF)) && ssa_vars[ssa_op->op1_def].no_val && !ssa_vars[ssa_op->op1_def].alias) {
2998+
tmp |= MAY_BE_UNDEF;
2999+
}
29943000
UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
29953001
}
29963002
break;

Zend/Optimizer/zend_ssa.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,10 @@ static zend_always_inline int _zend_ssa_rename_op(const zend_op_array *op_array,
703703
}
704704
break;
705705
case ZEND_SEND_VAR:
706+
if (opline->op1_type == IS_CV && ((build_flags & ZEND_SSA_RC_INFERENCE) || opline->op2_type == IS_UNUSED)) {
707+
goto add_op1_def;
708+
}
709+
break;
706710
case ZEND_CAST:
707711
case ZEND_QM_ASSIGN:
708712
case ZEND_JMP_SET:
@@ -1680,3 +1684,13 @@ void zend_ssa_rename_var_uses(zend_ssa *ssa, int old, int new, bool update_types
16801684
old_var->phi_use_chain = NULL;
16811685
}
16821686
/* }}} */
1687+
1688+
void zend_ssa_replace_op1_def_op1_use(zend_ssa *ssa, zend_ssa_op *ssa_op)
1689+
{
1690+
int op1_new = ssa_op->op1_use;
1691+
ZEND_ASSERT(op1_new >= 0);
1692+
ZEND_ASSERT(ssa_op->op1_def >= 0);
1693+
/* zend_ssa_rename_var_uses() clear use_chain & phi_use_chain for us */
1694+
zend_ssa_rename_var_uses(ssa, ssa_op->op1_def, op1_new, true);
1695+
zend_ssa_remove_op1_def(ssa, ssa_op);
1696+
}

Zend/Optimizer/zend_ssa.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ void zend_ssa_remove_uses_of_var(zend_ssa *ssa, int var_num);
159159
void zend_ssa_remove_block(zend_op_array *op_array, zend_ssa *ssa, int b);
160160
void zend_ssa_rename_var_uses(zend_ssa *ssa, int old_var, int new_var, bool update_types);
161161
void zend_ssa_remove_block_from_cfg(zend_ssa *ssa, int b);
162+
void zend_ssa_replace_op1_def_op1_use(zend_ssa *ssa, zend_ssa_op *ssa_op);
162163

163164
static zend_always_inline void _zend_ssa_remove_def(zend_ssa_var *var)
164165
{

Zend/zend_vm_def.h

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9900,7 +9900,7 @@ ZEND_VM_C_LABEL(fetch_dim_r_index_undef):
99009900
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
99019901
}
99029902

9903-
ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR, op->op2_type == IS_UNUSED && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0, ZEND_SEND_VAR_SIMPLE, CV|VAR, NUM)
9903+
ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR, op->op2_type == IS_UNUSED && !op->extended_value && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0, ZEND_SEND_VAR_SIMPLE, CV|VAR, NUM)
99049904
{
99059905
USE_OPLINE
99069906
zval *varptr, *arg;
@@ -9917,7 +9917,21 @@ ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR, op->op2_type == IS_UNUSED && (op1_i
99179917
ZEND_VM_NEXT_OPCODE();
99189918
}
99199919

9920-
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)
9920+
ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR, op->extended_value /* extended_value implies here OP2 UNUSED and OP1 not UNDEF or REF */, ZEND_SEND_VAR_SIMPLE_EXT, CV, NUM)
9921+
{
9922+
USE_OPLINE
9923+
zval *varptr, *arg;
9924+
9925+
varptr = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
9926+
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
9927+
9928+
ZVAL_COPY_VALUE(arg, varptr);
9929+
ZVAL_UNDEF(varptr);
9930+
9931+
ZEND_VM_NEXT_OPCODE();
9932+
}
9933+
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)
99219935
{
99229936
USE_OPLINE
99239937
zval *varptr, *arg;
@@ -9939,6 +9953,25 @@ ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR_EX, op->op2_type == IS_UNUSED && op-
99399953
ZEND_VM_NEXT_OPCODE();
99409954
}
99419955

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+
99429975
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)
99439976
{
99449977
USE_OPLINE

0 commit comments

Comments
 (0)