Skip to content

Commit 4f01dea

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.
1 parent c412919 commit 4f01dea

16 files changed

+1074
-707
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_observer.c

Lines changed: 61 additions & 41 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,7 +39,7 @@ 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;
42+
ZEND_EXT_TLS zend_execute_data *zend_observer_current_frame;
4843

4944
// Call during minit/startup ONLY
5045
ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init init)
@@ -101,7 +96,7 @@ ZEND_API void zend_observer_post_startup(void)
10196

10297
ZEND_API void zend_observer_activate(void)
10398
{
104-
current_observed_frame = NULL;
99+
zend_observer_current_frame = NULL;
105100
}
106101

107102
ZEND_API void zend_observer_shutdown(void)
@@ -121,21 +116,24 @@ static void zend_observer_fcall_install(zend_execute_data *execute_data)
121116
zend_function *function = execute_data->func;
122117

123118
ZEND_ASSERT(RUN_TIME_CACHE(&function->common));
124-
zend_observer_fcall_begin_handler *begin_handlers = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(function);
119+
zend_observer_fcall_begin_handler *begin_handlers = ZEND_OBSERVER_DATA(function), *begin_handlers_start = begin_handlers;
125120
zend_observer_fcall_end_handler *end_handlers = (zend_observer_fcall_end_handler *)begin_handlers + list->count, *end_handlers_start = end_handlers;
126121

127122
*begin_handlers = ZEND_OBSERVER_NOT_OBSERVED;
128123
*end_handlers = ZEND_OBSERVER_NOT_OBSERVED;
124+
bool has_handlers = false;
129125

130126
for (zend_llist_element *element = list->head; element; element = element->next) {
131127
zend_observer_fcall_init init;
132128
memcpy(&init, element->data, sizeof init);
133129
zend_observer_fcall_handlers handlers = init(execute_data);
134130
if (handlers.begin) {
135131
*(begin_handlers++) = handlers.begin;
132+
has_handlers = true;
136133
}
137134
if (handlers.end) {
138135
*(end_handlers++) = handlers.end;
136+
has_handlers = true;
139137
}
140138
}
141139

@@ -145,6 +143,10 @@ static void zend_observer_fcall_install(zend_execute_data *execute_data)
145143
*end_handlers = *end_handlers_start;
146144
*end_handlers_start = tmp;
147145
}
146+
147+
if (!has_handlers) {
148+
*begin_handlers_start = ZEND_OBSERVER_NONE_OBSERVED;
149+
}
148150
}
149151

150152
static bool zend_observer_remove_handler(void **first_handler, void *old_handler) {
@@ -169,8 +171,8 @@ static bool zend_observer_remove_handler(void **first_handler, void *old_handler
169171

170172
ZEND_API void zend_observer_add_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin) {
171173
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) {
174+
zend_observer_fcall_begin_handler *first_handler = ZEND_OBSERVER_DATA(function), *last_handler = first_handler + registered_observers - 1;
175+
if (*first_handler == ZEND_OBSERVER_NOT_OBSERVED || *first_handler == ZEND_OBSERVER_NONE_OBSERVED) {
174176
*first_handler = begin;
175177
} else {
176178
for (zend_observer_fcall_begin_handler *cur_handler = first_handler + 1; cur_handler <= last_handler; ++cur_handler) {
@@ -185,24 +187,45 @@ ZEND_API void zend_observer_add_begin_handler(zend_function *function, zend_obse
185187
}
186188

187189
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);
190+
void **begin_handlers = (void **)ZEND_OBSERVER_DATA(function);
191+
if (zend_observer_remove_handler(begin_handlers, begin)) {
192+
if (*begin_handlers == ZEND_OBSERVER_NOT_OBSERVED) {
193+
size_t registered_observers = zend_observers_fcall_list.count;
194+
if (begin_handlers[registered_observers] /* first end handler */ == ZEND_OBSERVER_NOT_OBSERVED) {
195+
*begin_handlers = ZEND_OBSERVER_NONE_OBSERVED;
196+
}
197+
}
198+
return true;
199+
}
200+
return false;
189201
}
190202

191203
ZEND_API void zend_observer_add_end_handler(zend_function *function, zend_observer_fcall_end_handler end) {
192204
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;
205+
void **begin_handler = (void **)ZEND_OBSERVER_DATA(function);
206+
zend_observer_fcall_end_handler *end_handler = (zend_observer_fcall_end_handler *)begin_handler + registered_observers;
194207
// to allow to preserve the invariant that end handlers are in reverse order of begin handlers, push the new end handler in front
195208
if (*end_handler != ZEND_OBSERVER_NOT_OBSERVED) {
196209
// there's no space for new handlers, then it's forbidden to call this function
197210
ZEND_ASSERT(end_handler[registered_observers - 1] == NULL);
198211
memmove(end_handler + 1, end_handler, sizeof(end_handler) * (registered_observers - 1));
212+
} else if (*begin_handler == ZEND_OBSERVER_NONE_OBSERVED) {
213+
*begin_handler = ZEND_OBSERVER_NOT_OBSERVED;
199214
}
200215
*end_handler = end;
201216
}
202217

203218
ZEND_API bool zend_observer_remove_end_handler(zend_function *function, zend_observer_fcall_end_handler end) {
204219
size_t registered_observers = zend_observers_fcall_list.count;
205-
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(function) + registered_observers, end);
220+
void **begin_handlers = (void **)ZEND_OBSERVER_DATA(function);
221+
void **end_handlers = begin_handlers + registered_observers;
222+
if (zend_observer_remove_handler(end_handlers, end)) {
223+
if (*begin_handlers == ZEND_OBSERVER_NOT_OBSERVED && *end_handlers == ZEND_OBSERVER_NOT_OBSERVED) {
224+
*begin_handlers = ZEND_OBSERVER_NONE_OBSERVED;
225+
}
226+
return true;
227+
}
228+
return false;
206229
}
207230

208231
static inline zend_execute_data **prev_observed_frame(zend_execute_data *execute_data) {
@@ -211,33 +234,33 @@ static inline zend_execute_data **prev_observed_frame(zend_execute_data *execute
211234
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));
212235
}
213236

214-
static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_data)
215-
{
237+
static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_data) {
216238
if (!ZEND_OBSERVER_ENABLED) {
217239
return;
218240
}
219241

220-
zend_function *function = execute_data->func;
242+
zend_observer_fcall_begin_specialized(execute_data, true);
243+
}
221244

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

226-
zend_observer_fcall_begin_handler *handler = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(function);
227249
if (!*handler) {
228250
zend_observer_fcall_install(execute_data);
251+
if (zend_observer_handler_is_unobserved(handler)) {
252+
return;
253+
}
229254
}
230255

231-
zend_observer_fcall_begin_handler *possible_handlers_end = handler + zend_observers_fcall_list.count;
232-
233256
zend_observer_fcall_end_handler *end_handler = (zend_observer_fcall_end_handler *)possible_handlers_end;
234257
if (*end_handler != ZEND_OBSERVER_NOT_OBSERVED) {
235-
*prev_observed_frame(execute_data) = current_observed_frame;
236-
current_observed_frame = execute_data;
237-
}
258+
*prev_observed_frame(execute_data) = zend_observer_current_frame;
259+
zend_observer_current_frame = execute_data;
238260

239-
if (*handler == ZEND_OBSERVER_NOT_OBSERVED) {
240-
return;
261+
if (*handler == ZEND_OBSERVER_NOT_OBSERVED) { // this function must not be called if ZEND_OBSERVER_NONE_OBSERVED, hence sufficient to check
262+
return;
263+
}
241264
}
242265

243266
do {
@@ -252,17 +275,17 @@ ZEND_API void ZEND_FASTCALL zend_observer_generator_resume(zend_execute_data *ex
252275

253276
ZEND_API void ZEND_FASTCALL zend_observer_fcall_begin(zend_execute_data *execute_data)
254277
{
255-
ZEND_ASSUME(execute_data->func);
256-
if (!(execute_data->func->common.fn_flags & ZEND_ACC_GENERATOR)) {
278+
ZEND_ASSUME(EX(func));
279+
if (!(EX(func)->common.fn_flags & ZEND_ACC_GENERATOR)) {
257280
_zend_observe_fcall_begin(execute_data);
258281
}
259282
}
260283

261284
static inline void call_end_observers(zend_execute_data *execute_data, zval *return_value) {
262-
zend_function *func = execute_data->func;
285+
zend_function *func = EX(func);
263286
ZEND_ASSERT(func);
264287

265-
zend_observer_fcall_end_handler *handler = (zend_observer_fcall_end_handler *)&ZEND_OBSERVER_DATA(func) + zend_observers_fcall_list.count;
288+
zend_observer_fcall_end_handler *handler = (zend_observer_fcall_end_handler *)ZEND_OBSERVER_DATA(func) + zend_observers_fcall_list.count;
266289
// TODO: Fix exceptions from generators
267290
// ZEND_ASSERT(fcall_data);
268291
if (!*handler || *handler == ZEND_OBSERVER_NOT_OBSERVED) {
@@ -275,19 +298,16 @@ static inline void call_end_observers(zend_execute_data *execute_data, zval *ret
275298
} while (++handler != possible_handlers_end && *handler != NULL);
276299
}
277300

278-
ZEND_API void ZEND_FASTCALL zend_observer_fcall_end(zend_execute_data *execute_data, zval *return_value)
301+
ZEND_API void ZEND_FASTCALL zend_observer_fcall_end_prechecked(zend_execute_data *execute_data, zval *return_value)
279302
{
280-
if (execute_data != current_observed_frame) {
281-
return;
282-
}
283303
call_end_observers(execute_data, return_value);
284-
current_observed_frame = *prev_observed_frame(execute_data);
304+
zend_observer_current_frame = *prev_observed_frame(execute_data);
285305
}
286306

287307
ZEND_API void zend_observer_fcall_end_all(void)
288308
{
289-
zend_execute_data *execute_data = current_observed_frame, *original_execute_data = EG(current_execute_data);
290-
current_observed_frame = NULL;
309+
zend_execute_data *execute_data = zend_observer_current_frame, *original_execute_data = EG(current_execute_data);
310+
zend_observer_current_frame = NULL;
291311
while (execute_data) {
292312
EG(current_execute_data) = execute_data;
293313
call_end_observers(execute_data, NULL);
@@ -388,8 +408,8 @@ ZEND_API void ZEND_FASTCALL zend_observer_fiber_switch_notify(zend_fiber_context
388408
callback(from, to);
389409
}
390410

391-
from->top_observed_frame = current_observed_frame;
392-
current_observed_frame = to->top_observed_frame;
411+
from->top_observed_frame = zend_observer_current_frame;
412+
zend_observer_current_frame = to->top_observed_frame;
393413
}
394414

395415
ZEND_API void ZEND_FASTCALL zend_observer_fiber_destroy_notify(zend_fiber_context *destroying)

Zend/zend_observer.h

Lines changed: 52 additions & 15 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

@@ -31,6 +32,21 @@ extern ZEND_API bool zend_observer_errors_observed;
3132
extern ZEND_API bool zend_observer_function_declared_observed;
3233
extern ZEND_API bool zend_observer_class_linked_observed;
3334

35+
extern ZEND_API ZEND_EXT_TLS zend_execute_data *zend_observer_current_frame;
36+
37+
typedef void (*zend_observer_fcall_begin_handler)(zend_execute_data *execute_data);
38+
typedef void (*zend_observer_fcall_end_handler)(zend_execute_data *execute_data, zval *retval);
39+
40+
typedef struct _zend_observer_fcall_handlers {
41+
zend_observer_fcall_begin_handler begin;
42+
zend_observer_fcall_end_handler end;
43+
} zend_observer_fcall_handlers;
44+
45+
#define ZEND_OBSERVER_DATA(function) \
46+
((zend_observer_fcall_begin_handler *)&ZEND_OP_ARRAY_EXTENSION((&(function)->common), zend_observer_fcall_op_array_extension))
47+
48+
#define ZEND_OBSERVER_NONE_OBSERVED ((void *) 3)
49+
3450
#define ZEND_OBSERVER_ENABLED (zend_observer_fcall_op_array_extension != -1)
3551

3652
#define ZEND_OBSERVER_FCALL_BEGIN(execute_data) do { \
@@ -45,14 +61,6 @@ extern ZEND_API bool zend_observer_class_linked_observed;
4561
} \
4662
} while (0)
4763

48-
typedef void (*zend_observer_fcall_begin_handler)(zend_execute_data *execute_data);
49-
typedef void (*zend_observer_fcall_end_handler)(zend_execute_data *execute_data, zval *retval);
50-
51-
typedef struct _zend_observer_fcall_handlers {
52-
zend_observer_fcall_begin_handler begin;
53-
zend_observer_fcall_end_handler end;
54-
} zend_observer_fcall_handlers;
55-
5664
/* If the fn should not be observed then return {NULL, NULL} */
5765
typedef zend_observer_fcall_handlers (*zend_observer_fcall_init)(zend_execute_data *execute_data);
5866

@@ -71,15 +79,44 @@ ZEND_API void zend_observer_post_startup(void); // Called by engine after MINITs
7179
ZEND_API void zend_observer_activate(void);
7280
ZEND_API void zend_observer_shutdown(void);
7381

74-
ZEND_API void ZEND_FASTCALL zend_observer_fcall_begin(
75-
zend_execute_data *execute_data);
82+
ZEND_API void ZEND_FASTCALL zend_observer_fcall_begin(zend_execute_data *execute_data);
83+
void ZEND_FASTCALL zend_observer_fcall_begin_prechecked(zend_execute_data *execute_data, zend_observer_fcall_begin_handler *observer_data);
84+
85+
static zend_always_inline bool zend_observer_handler_is_unobserved(zend_observer_fcall_begin_handler *handler) {
86+
return *handler == ZEND_OBSERVER_NONE_OBSERVED;
87+
}
88+
89+
static zend_always_inline bool zend_observer_fcall_has_no_observers(zend_execute_data *execute_data, bool allow_generator, zend_observer_fcall_begin_handler **handler) {
90+
zend_function *function = EX(func);
91+
void *ZEND_MAP_PTR(runtime_cache) = ZEND_MAP_PTR((function)->common.run_time_cache);
92+
93+
if (function->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE | (allow_generator ? 0 : ZEND_ACC_GENERATOR))) {
94+
return true;
95+
}
96+
97+
if (!ZEND_MAP_PTR(runtime_cache)) {
98+
return true;
99+
}
100+
101+
*handler = (zend_observer_fcall_begin_handler *)ZEND_MAP_PTR_GET(runtime_cache) + zend_observer_fcall_op_array_extension;
102+
return zend_observer_handler_is_unobserved(*handler);
103+
}
104+
105+
static zend_always_inline void zend_observer_fcall_begin_specialized(zend_execute_data *execute_data, bool allow_generator) {
106+
zend_observer_fcall_begin_handler *handler;
107+
if (!zend_observer_fcall_has_no_observers(execute_data, allow_generator, &handler)) {
108+
zend_observer_fcall_begin_prechecked(execute_data, handler);
109+
}
110+
}
76111

77-
ZEND_API void ZEND_FASTCALL zend_observer_generator_resume(
78-
zend_execute_data *execute_data);
112+
ZEND_API void ZEND_FASTCALL zend_observer_generator_resume(zend_execute_data *execute_data);
79113

80-
ZEND_API void ZEND_FASTCALL zend_observer_fcall_end(
81-
zend_execute_data *execute_data,
82-
zval *return_value);
114+
ZEND_API void ZEND_FASTCALL zend_observer_fcall_end_prechecked(zend_execute_data *execute_data, zval *return_value);
115+
static zend_always_inline void zend_observer_fcall_end(zend_execute_data *execute_data, zval *return_value) {
116+
if (execute_data == zend_observer_current_frame) {
117+
zend_observer_fcall_end_prechecked(execute_data, return_value);
118+
}
119+
}
83120

84121
ZEND_API void zend_observer_fcall_end_all(void);
85122

0 commit comments

Comments
 (0)