Skip to content

Commit 0dd6584

Browse files
committed
Optimize observers
Inline the lookup whether a function is observed at all. This strategy is also used for FRAMELESS calls. If the frameless call is observed, we instead allocate a call frame and push the arguments, to call the the function afterwards. Doing so is still a performance benefit as opposed to executing individual INIT_FCALL+SEND_VAL ops. Thus, even if the frameless call turns out to be observed, the call overhead is slightly lower than before. If the internal function is not observed at all, the unavoidable overhead is fetching the FLF zend_function pointer and the run-time cache needs to be inspected. As part of this work, it turned out to be most viable to put the result operand on the ZEND_OP_DATA instead of ZEND_FRAMELESS_ICALL_3, allowing seamless interoperability with the DO_ICALL opcode. This is a bit unusual in comparison to all other ZEND_OP_DATA usages, but seems to not pose problems overall. There is also a small issue resolved: trampolines would always use the ZEND_CALL_TRAMPOLINE_SPEC_OBSERVER function due to zend_observer_fcall_op_array_extension being set to -1 too late. Signed-off-by: Bob Weinand <bobwei9@hotmail.com>
1 parent c412919 commit 0dd6584

24 files changed

+1236
-733
lines changed

Zend/Optimizer/optimize_temp_vars_5.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ void zend_optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_c
4949
int *map_T; /* Map's the T to its new index */
5050
zend_op *opline, *end;
5151
int currT;
52+
int exclNextT = -1;
5253
int i;
5354
int max = -1;
5455
void *checkpoint = zend_arena_checkpoint(ctx->arena);
@@ -144,6 +145,11 @@ void zend_optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_c
144145
opline->op2.var = NUM_VAR(map_T[currT] + offset);
145146
}
146147

148+
if (exclNextT != -1) {
149+
zend_bitset_excl(taken_T, exclNextT);
150+
exclNextT = -1;
151+
}
152+
147153
if (opline->result_type & (IS_VAR | IS_TMP_VAR)) {
148154
currT = VAR_NUM(opline->result.var) - offset;
149155
if (map_T[currT] == INVALID_VAR) {
@@ -157,7 +163,12 @@ void zend_optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_c
157163
* since the fast_var could also be set by ZEND_HANDLE_EXCEPTION
158164
* which could be ahead of it */
159165
if (opline->opcode != ZEND_FAST_CALL) {
160-
zend_bitset_excl(taken_T, map_T[currT]);
166+
if (opline->opcode == ZEND_OP_DATA) {
167+
/* ZEND_OP_DATA may contain a result, which must not be equal to any of its prior opcode vars */
168+
exclNextT = map_T[currT];
169+
} else {
170+
zend_bitset_excl(taken_T, map_T[currT]);
171+
}
161172
}
162173
if (opline->opcode == ZEND_ROPE_INIT) {
163174
uint32_t num = ((opline->extended_value * sizeof(zend_string*)) + (sizeof(zval) - 1)) / sizeof(zval);

Zend/Optimizer/sccp.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2139,6 +2139,10 @@ static int try_remove_definition(sccp_ctx *ctx, int var_num, zend_ssa_var *var,
21392139
}
21402140
return 0;
21412141
}
2142+
if (opline->opcode == ZEND_OP_DATA) {
2143+
/* Consider the primary opline for matching */
2144+
--opline;
2145+
}
21422146
if (ssa_op->op1_def >= 0 || ssa_op->op2_def >= 0) {
21432147
if (var->use_chain < 0 && var->phi_use_chain == NULL) {
21442148
switch (opline->opcode) {
@@ -2209,6 +2213,8 @@ static int try_remove_definition(sccp_ctx *ctx, int var_num, zend_ssa_var *var,
22092213
zend_ssa_remove_instr(ssa, opline, ssa_op);
22102214
removed_ops++;
22112215
if (has_op_data) {
2216+
old_type = opline[1].result_type;
2217+
old_var = opline[1].result.var;
22122218
zend_ssa_remove_instr(ssa, opline + 1, ssa_op + 1);
22132219
removed_ops++;
22142220
}

Zend/Optimizer/scdf.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,10 @@ void scdf_solve(scdf_ctx *scdf, const char *name) {
124124
zend_ssa_op *ssa_op = &ssa->ops[i];
125125
if (opline->opcode == ZEND_OP_DATA) {
126126
opline--;
127-
ssa_op--;
127+
if (opline->opcode != ZEND_FRAMELESS_ICALL_3) {
128+
// result op not on OP_DATA
129+
ssa_op--;
130+
}
128131
}
129132
scdf->handlers.visit_instr(scdf, opline, ssa_op);
130133
if (i == block->start + block->len - 1) {
@@ -162,7 +165,7 @@ void scdf_solve(scdf_ctx *scdf, const char *name) {
162165
for (j = block->start; j < end; j++) {
163166
opline = &scdf->op_array->opcodes[j];
164167
zend_bitset_excl(scdf->instr_worklist, j);
165-
if (opline->opcode != ZEND_OP_DATA) {
168+
if (opline->opcode != ZEND_OP_DATA || opline[-1].opcode == ZEND_FRAMELESS_ICALL_3) {
166169
scdf->handlers.visit_instr(scdf, opline, &ssa->ops[j]);
167170
}
168171
}
@@ -172,7 +175,10 @@ void scdf_solve(scdf_ctx *scdf, const char *name) {
172175
ZEND_ASSERT(opline && "Should have opline in non-empty block");
173176
if (opline->opcode == ZEND_OP_DATA) {
174177
opline--;
175-
j--;
178+
if (opline->opcode != ZEND_FRAMELESS_ICALL_3) {
179+
// result op not on OP_DATA
180+
j--;
181+
}
176182
}
177183
scdf->handlers.mark_feasible_successors(scdf, i, block, opline, &ssa->ops[j-1]);
178184
}

Zend/Optimizer/zend_dfg.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ static zend_always_inline void _zend_dfg_add_use_def_op(const zend_op_array *op_
130130
zend_bitset_incl(use, var_num);
131131
}
132132
}
133+
if (opline->opcode == ZEND_FRAMELESS_ICALL_3) {
134+
++opline; // OP_DATA has result
135+
}
133136
break;
134137
case ZEND_ASSIGN_DIM_OP:
135138
case ZEND_ASSIGN_OBJ_OP:

Zend/Optimizer/zend_inference.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3869,7 +3869,7 @@ static zend_always_inline zend_result _zend_update_type_info(
38693869
UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
38703870
}
38713871
if (opline->opcode == ZEND_FRAMELESS_ICALL_3) {
3872-
zend_ssa_op *next_ssa_op = ssa_op + 1;
3872+
zend_ssa_op *next_ssa_op = ++ssa_op; // OP_DATA has result op
38733873
if (next_ssa_op->op1_def >= 0) {
38743874
ZEND_ASSERT(next_ssa_op->op1_use >= 0);
38753875
tmp = ssa->var_info[next_ssa_op->op1_use].type;

Zend/Optimizer/zend_ssa.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -782,12 +782,14 @@ static zend_always_inline int _zend_ssa_rename_op(const zend_op_array *op_array,
782782
//NEW_SSA_VAR(opline->op2.var)
783783
}
784784
if (opline->opcode == ZEND_FRAMELESS_ICALL_3) {
785-
next = opline + 1;
785+
// Result is on following OP_DATA
786+
next = ++opline;
787+
++k;
786788
if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
787-
ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
789+
ssa_ops[k].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
788790
//USE_SSA_VAR(op_array->last_var + next->op1.var);
789791
if ((build_flags & ZEND_SSA_RC_INFERENCE) && next->op1_type == IS_CV) {
790-
ssa_ops[k + 1].op1_def = ssa_vars_count;
792+
ssa_ops[k].op1_def = ssa_vars_count;
791793
var[EX_VAR_TO_NUM(next->op1.var)] = ssa_vars_count;
792794
ssa_vars_count++;
793795
//NEW_SSA_VAR(next->op1.var)

Zend/zend_compile.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4542,7 +4542,7 @@ static uint32_t find_frameless_function_offset(uint32_t arity, void *handler)
45424542

45434543
static const zend_frameless_function_info *find_frameless_function_info(zend_ast_list *args, zend_function *fbc, uint32_t type)
45444544
{
4545-
if (ZEND_OBSERVER_ENABLED || zend_execute_internal) {
4545+
if (zend_execute_internal) {
45464546
return NULL;
45474547
}
45484548

@@ -4608,7 +4608,12 @@ static uint32_t zend_compile_frameless_icall_ex(znode *result, zend_ast_list *ar
46084608
SET_NODE(opline->op2, &arg_zvs[1]);
46094609
}
46104610
if (num_args >= 3) {
4611-
zend_emit_op_data(&arg_zvs[2]);
4611+
// Put result znode on OP_DATA to ensure dispatch in observer fallback can be aligned with the last opcode of the call
4612+
zend_op *op_data = zend_emit_op_data(&arg_zvs[2]);
4613+
op_data->result = opline->result;
4614+
op_data->result_type = opline->result_type;
4615+
opline->result_type = IS_UNUSED;
4616+
++opnum;
46124617
}
46134618
return opnum;
46144619
}

Zend/zend_frameless_function.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ typedef void (*zend_frameless_function_3)(zval *return_value, zval *op1, zval *o
115115
extern size_t zend_flf_count;
116116
extern size_t zend_flf_capacity;
117117
ZEND_API extern void **zend_flf_handlers;
118-
extern zend_function **zend_flf_functions;
118+
ZEND_API extern zend_function **zend_flf_functions;
119119

120120
typedef struct {
121121
void *handler;

Zend/zend_globals.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ struct _zend_executor_globals {
193193

194194
uint32_t jit_trace_num; /* Used by tracing JIT to reference the currently running trace */
195195

196+
zend_execute_data *current_observed_frame;
197+
196198
int ticks_count;
197199

198200
zend_long precision;

0 commit comments

Comments
 (0)