Skip to content

Commit 1704e44

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. Signed-off-by: Bob Weinand <bobwei9@hotmail.com>
1 parent c412919 commit 1704e44

17 files changed

+1073
-708
lines changed

Zend/Optimizer/sccp.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2211,6 +2211,8 @@ static int try_remove_definition(sccp_ctx *ctx, int var_num, zend_ssa_var *var,
22112211
if (has_op_data) {
22122212
zend_ssa_remove_instr(ssa, opline + 1, ssa_op + 1);
22132213
removed_ops++;
2214+
old_type = opline[1].result_type;
2215+
old_var = opline[1].result.var;
22142216
}
22152217
}
22162218
ssa_op->result_def = var_num;

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;
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_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;

Zend/zend_observer.c

Lines changed: 60 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
+----------------------------------------------------------------------+
1515
| Authors: Levi Morrison <levim@php.net> |
1616
| Sammy Kaye Powers <sammyk@php.net> |
17+
| Bob Weinand <bobwei9@hotmail.com> |
1718
+----------------------------------------------------------------------+
1819
*/
1920

@@ -23,14 +24,8 @@
2324
#include "zend_llist.h"
2425
#include "zend_vm.h"
2526

26-
#define ZEND_OBSERVER_DATA(function) \
27-
ZEND_OP_ARRAY_EXTENSION((&(function)->common), zend_observer_fcall_op_array_extension)
28-
2927
#define ZEND_OBSERVER_NOT_OBSERVED ((void *) 2)
3028

31-
#define ZEND_OBSERVABLE_FN(function) \
32-
(ZEND_MAP_PTR(function->common.run_time_cache) && !(function->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE))
33-
3429
zend_llist zend_observers_fcall_list;
3530
zend_llist zend_observer_function_declared_callbacks;
3631
zend_llist zend_observer_class_linked_callbacks;
@@ -44,8 +39,6 @@ bool zend_observer_errors_observed;
4439
bool zend_observer_function_declared_observed;
4540
bool zend_observer_class_linked_observed;
4641

47-
ZEND_TLS zend_execute_data *current_observed_frame;
48-
4942
// Call during minit/startup ONLY
5043
ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init init)
5144
{
@@ -101,7 +94,7 @@ ZEND_API void zend_observer_post_startup(void)
10194

10295
ZEND_API void zend_observer_activate(void)
10396
{
104-
current_observed_frame = NULL;
97+
EG(current_observed_frame) = NULL;
10598
}
10699

107100
ZEND_API void zend_observer_shutdown(void)
@@ -121,21 +114,24 @@ static void zend_observer_fcall_install(zend_execute_data *execute_data)
121114
zend_function *function = execute_data->func;
122115

123116
ZEND_ASSERT(RUN_TIME_CACHE(&function->common));
124-
zend_observer_fcall_begin_handler *begin_handlers = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(function);
117+
zend_observer_fcall_begin_handler *begin_handlers = ZEND_OBSERVER_DATA(function), *begin_handlers_start = begin_handlers;
125118
zend_observer_fcall_end_handler *end_handlers = (zend_observer_fcall_end_handler *)begin_handlers + list->count, *end_handlers_start = end_handlers;
126119

127120
*begin_handlers = ZEND_OBSERVER_NOT_OBSERVED;
128121
*end_handlers = ZEND_OBSERVER_NOT_OBSERVED;
122+
bool has_handlers = false;
129123

130124
for (zend_llist_element *element = list->head; element; element = element->next) {
131125
zend_observer_fcall_init init;
132126
memcpy(&init, element->data, sizeof init);
133127
zend_observer_fcall_handlers handlers = init(execute_data);
134128
if (handlers.begin) {
135129
*(begin_handlers++) = handlers.begin;
130+
has_handlers = true;
136131
}
137132
if (handlers.end) {
138133
*(end_handlers++) = handlers.end;
134+
has_handlers = true;
139135
}
140136
}
141137

@@ -145,6 +141,10 @@ static void zend_observer_fcall_install(zend_execute_data *execute_data)
145141
*end_handlers = *end_handlers_start;
146142
*end_handlers_start = tmp;
147143
}
144+
145+
if (!has_handlers) {
146+
*begin_handlers_start = ZEND_OBSERVER_NONE_OBSERVED;
147+
}
148148
}
149149

150150
static bool zend_observer_remove_handler(void **first_handler, void *old_handler) {
@@ -169,8 +169,8 @@ static bool zend_observer_remove_handler(void **first_handler, void *old_handler
169169

170170
ZEND_API void zend_observer_add_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin) {
171171
size_t registered_observers = zend_observers_fcall_list.count;
172-
zend_observer_fcall_begin_handler *first_handler = (void *)&ZEND_OBSERVER_DATA(function), *last_handler = first_handler + registered_observers - 1;
173-
if (*first_handler == ZEND_OBSERVER_NOT_OBSERVED) {
172+
zend_observer_fcall_begin_handler *first_handler = ZEND_OBSERVER_DATA(function), *last_handler = first_handler + registered_observers - 1;
173+
if (*first_handler == ZEND_OBSERVER_NOT_OBSERVED || *first_handler == ZEND_OBSERVER_NONE_OBSERVED) {
174174
*first_handler = begin;
175175
} else {
176176
for (zend_observer_fcall_begin_handler *cur_handler = first_handler + 1; cur_handler <= last_handler; ++cur_handler) {
@@ -185,24 +185,45 @@ ZEND_API void zend_observer_add_begin_handler(zend_function *function, zend_obse
185185
}
186186

187187
ZEND_API bool zend_observer_remove_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin) {
188-
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(function), begin);
188+
void **begin_handlers = (void **)ZEND_OBSERVER_DATA(function);
189+
if (zend_observer_remove_handler(begin_handlers, begin)) {
190+
if (*begin_handlers == ZEND_OBSERVER_NOT_OBSERVED) {
191+
size_t registered_observers = zend_observers_fcall_list.count;
192+
if (begin_handlers[registered_observers] /* first end handler */ == ZEND_OBSERVER_NOT_OBSERVED) {
193+
*begin_handlers = ZEND_OBSERVER_NONE_OBSERVED;
194+
}
195+
}
196+
return true;
197+
}
198+
return false;
189199
}
190200

191201
ZEND_API void zend_observer_add_end_handler(zend_function *function, zend_observer_fcall_end_handler end) {
192202
size_t registered_observers = zend_observers_fcall_list.count;
193-
zend_observer_fcall_end_handler *end_handler = (zend_observer_fcall_end_handler *)&ZEND_OBSERVER_DATA(function) + registered_observers;
203+
void **begin_handler = (void **)ZEND_OBSERVER_DATA(function);
204+
zend_observer_fcall_end_handler *end_handler = (zend_observer_fcall_end_handler *)begin_handler + registered_observers;
194205
// to allow to preserve the invariant that end handlers are in reverse order of begin handlers, push the new end handler in front
195206
if (*end_handler != ZEND_OBSERVER_NOT_OBSERVED) {
196207
// there's no space for new handlers, then it's forbidden to call this function
197208
ZEND_ASSERT(end_handler[registered_observers - 1] == NULL);
198209
memmove(end_handler + 1, end_handler, sizeof(end_handler) * (registered_observers - 1));
210+
} else if (*begin_handler == ZEND_OBSERVER_NONE_OBSERVED) {
211+
*begin_handler = ZEND_OBSERVER_NOT_OBSERVED;
199212
}
200213
*end_handler = end;
201214
}
202215

203216
ZEND_API bool zend_observer_remove_end_handler(zend_function *function, zend_observer_fcall_end_handler end) {
204217
size_t registered_observers = zend_observers_fcall_list.count;
205-
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(function) + registered_observers, end);
218+
void **begin_handlers = (void **)ZEND_OBSERVER_DATA(function);
219+
void **end_handlers = begin_handlers + registered_observers;
220+
if (zend_observer_remove_handler(end_handlers, end)) {
221+
if (*begin_handlers == ZEND_OBSERVER_NOT_OBSERVED && *end_handlers == ZEND_OBSERVER_NOT_OBSERVED) {
222+
*begin_handlers = ZEND_OBSERVER_NONE_OBSERVED;
223+
}
224+
return true;
225+
}
226+
return false;
206227
}
207228

208229
static inline zend_execute_data **prev_observed_frame(zend_execute_data *execute_data) {
@@ -211,33 +232,33 @@ static inline zend_execute_data **prev_observed_frame(zend_execute_data *execute
211232
return (zend_execute_data **)&Z_PTR_P(EX_VAR_NUM((ZEND_USER_CODE(func->type) ? func->op_array.last_var : ZEND_CALL_NUM_ARGS(execute_data)) + func->common.T - 1));
212233
}
213234

214-
static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_data)
215-
{
235+
static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_data) {
216236
if (!ZEND_OBSERVER_ENABLED) {
217237
return;
218238
}
219239

220-
zend_function *function = execute_data->func;
240+
zend_observer_fcall_begin_specialized(execute_data, true);
241+
}
221242

222-
if (!ZEND_OBSERVABLE_FN(function)) {
223-
return;
224-
}
243+
void ZEND_FASTCALL zend_observer_fcall_begin_prechecked(zend_execute_data *execute_data, zend_observer_fcall_begin_handler *handler)
244+
{
245+
zend_observer_fcall_begin_handler *possible_handlers_end = handler + zend_observers_fcall_list.count;
225246

226-
zend_observer_fcall_begin_handler *handler = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(function);
227247
if (!*handler) {
228248
zend_observer_fcall_install(execute_data);
249+
if (zend_observer_handler_is_unobserved(handler)) {
250+
return;
251+
}
229252
}
230253

231-
zend_observer_fcall_begin_handler *possible_handlers_end = handler + zend_observers_fcall_list.count;
232-
233254
zend_observer_fcall_end_handler *end_handler = (zend_observer_fcall_end_handler *)possible_handlers_end;
234255
if (*end_handler != ZEND_OBSERVER_NOT_OBSERVED) {
235-
*prev_observed_frame(execute_data) = current_observed_frame;
236-
current_observed_frame = execute_data;
237-
}
256+
*prev_observed_frame(execute_data) = EG(current_observed_frame);
257+
EG(current_observed_frame) = execute_data;
238258

239-
if (*handler == ZEND_OBSERVER_NOT_OBSERVED) {
240-
return;
259+
if (*handler == ZEND_OBSERVER_NOT_OBSERVED) { // this function must not be called if ZEND_OBSERVER_NONE_OBSERVED, hence sufficient to check
260+
return;
261+
}
241262
}
242263

243264
do {
@@ -252,17 +273,17 @@ ZEND_API void ZEND_FASTCALL zend_observer_generator_resume(zend_execute_data *ex
252273

253274
ZEND_API void ZEND_FASTCALL zend_observer_fcall_begin(zend_execute_data *execute_data)
254275
{
255-
ZEND_ASSUME(execute_data->func);
256-
if (!(execute_data->func->common.fn_flags & ZEND_ACC_GENERATOR)) {
276+
ZEND_ASSUME(EX(func));
277+
if (!(EX(func)->common.fn_flags & ZEND_ACC_GENERATOR)) {
257278
_zend_observe_fcall_begin(execute_data);
258279
}
259280
}
260281

261282
static inline void call_end_observers(zend_execute_data *execute_data, zval *return_value) {
262-
zend_function *func = execute_data->func;
283+
zend_function *func = EX(func);
263284
ZEND_ASSERT(func);
264285

265-
zend_observer_fcall_end_handler *handler = (zend_observer_fcall_end_handler *)&ZEND_OBSERVER_DATA(func) + zend_observers_fcall_list.count;
286+
zend_observer_fcall_end_handler *handler = (zend_observer_fcall_end_handler *)ZEND_OBSERVER_DATA(func) + zend_observers_fcall_list.count;
266287
// TODO: Fix exceptions from generators
267288
// ZEND_ASSERT(fcall_data);
268289
if (!*handler || *handler == ZEND_OBSERVER_NOT_OBSERVED) {
@@ -275,19 +296,16 @@ static inline void call_end_observers(zend_execute_data *execute_data, zval *ret
275296
} while (++handler != possible_handlers_end && *handler != NULL);
276297
}
277298

278-
ZEND_API void ZEND_FASTCALL zend_observer_fcall_end(zend_execute_data *execute_data, zval *return_value)
299+
ZEND_API void ZEND_FASTCALL zend_observer_fcall_end_prechecked(zend_execute_data *execute_data, zval *return_value)
279300
{
280-
if (execute_data != current_observed_frame) {
281-
return;
282-
}
283301
call_end_observers(execute_data, return_value);
284-
current_observed_frame = *prev_observed_frame(execute_data);
302+
EG(current_observed_frame) = *prev_observed_frame(execute_data);
285303
}
286304

287305
ZEND_API void zend_observer_fcall_end_all(void)
288306
{
289-
zend_execute_data *execute_data = current_observed_frame, *original_execute_data = EG(current_execute_data);
290-
current_observed_frame = NULL;
307+
zend_execute_data *execute_data = EG(current_observed_frame), *original_execute_data = EG(current_execute_data);
308+
EG(current_observed_frame) = NULL;
291309
while (execute_data) {
292310
EG(current_execute_data) = execute_data;
293311
call_end_observers(execute_data, NULL);
@@ -388,8 +406,8 @@ ZEND_API void ZEND_FASTCALL zend_observer_fiber_switch_notify(zend_fiber_context
388406
callback(from, to);
389407
}
390408

391-
from->top_observed_frame = current_observed_frame;
392-
current_observed_frame = to->top_observed_frame;
409+
from->top_observed_frame = EG(current_observed_frame);
410+
EG(current_observed_frame) = to->top_observed_frame;
393411
}
394412

395413
ZEND_API void ZEND_FASTCALL zend_observer_fiber_destroy_notify(zend_fiber_context *destroying)

0 commit comments

Comments
 (0)