Skip to content

Commit dc5475c

Browse files
committedAug 4, 2022
Save previous observer on the VM stack
This avoids a possible significant performance penalty, when some leaf function was observed, deep in the stack. As a side effect, we are not iterating over prev_execute_data anymore and thus, non-observed fake frames, possibly on stack, cannot have any impact on the observer anymore (especially within zend_observer_fcall_end_all). Saving the previous observer happens now directly on the VM stack. If there is any observer, function frames are allocated an extra zval (the last temporary), which will, on observed frames, contain the previous observed frame address.
1 parent 30ed8fb commit dc5475c

11 files changed

+62
-44
lines changed
 

‎Zend/Optimizer/zend_optimizer.c

+13
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "zend_inference.h"
3232
#include "zend_dump.h"
3333
#include "php.h"
34+
#include "zend_observer.h"
3435

3536
#ifndef ZEND_OPTIMIZER_MAX_REGISTERED_PASSES
3637
# define ZEND_OPTIMIZER_MAX_REGISTERED_PASSES 32
@@ -1094,6 +1095,8 @@ static void zend_revert_pass_two(zend_op_array *op_array)
10941095
}
10951096
#endif
10961097

1098+
op_array->T -= ZEND_OBSERVER_ENABLED;
1099+
10971100
op_array->fn_flags &= ~ZEND_ACC_DONE_PASS_TWO;
10981101
}
10991102

@@ -1123,6 +1126,8 @@ static void zend_redo_pass_two(zend_op_array *op_array)
11231126
}
11241127
#endif
11251128

1129+
op_array->T += ZEND_OBSERVER_ENABLED; // reserve last temporary for observers if enabled
1130+
11261131
opline = op_array->opcodes;
11271132
end = opline + op_array->last;
11281133
while (opline < end) {
@@ -1550,6 +1555,12 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l
15501555
}
15511556
}
15521557

1558+
if (ZEND_OBSERVER_ENABLED) {
1559+
for (i = 0; i < call_graph.op_arrays_count; i++) {
1560+
++call_graph.op_arrays[i]->T; // ensure accurate temporary count for stack size precalculation
1561+
}
1562+
}
1563+
15531564
if (ZEND_OPTIMIZER_PASS_12 & optimization_level) {
15541565
for (i = 0; i < call_graph.op_arrays_count; i++) {
15551566
zend_adjust_fcall_stack_size_graph(call_graph.op_arrays[i]);
@@ -1565,6 +1576,8 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l
15651576
zend_recalc_live_ranges(op_array, needs_live_range);
15661577
}
15671578
} else {
1579+
op_array->T -= ZEND_OBSERVER_ENABLED; // redo_pass_two will re-increment it
1580+
15681581
zend_redo_pass_two(op_array);
15691582
if (op_array->live_range) {
15701583
zend_recalc_live_ranges(op_array, NULL);

‎Zend/zend_API.c

+1
Original file line numberDiff line numberDiff line change
@@ -2685,6 +2685,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
26852685
}
26862686
internal_function->type = ZEND_INTERNAL_FUNCTION;
26872687
internal_function->module = EG(current_module);
2688+
internal_function->T = 0;
26882689
memset(internal_function->reserved, 0, ZEND_MAX_RESERVED_RESOURCES * sizeof(void*));
26892690

26902691
while (ptr->fname) {

‎Zend/zend_compile.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -448,12 +448,12 @@ struct _zend_op_array {
448448
uint32_t required_num_args;
449449
zend_arg_info *arg_info;
450450
HashTable *attributes;
451+
uint32_t T; /* number of temporary variables */
451452
ZEND_MAP_PTR_DEF(void **, run_time_cache);
452453
/* END of common elements */
453454

454455
int cache_size; /* number of run_time_cache_slots * sizeof(void*) */
455456
int last_var; /* number of CV variables */
456-
uint32_t T; /* number of temporary variables */
457457
uint32_t last; /* number of opcodes */
458458

459459
zend_op *opcodes;
@@ -503,6 +503,7 @@ typedef struct _zend_internal_function {
503503
uint32_t required_num_args;
504504
zend_internal_arg_info *arg_info;
505505
HashTable *attributes;
506+
uint32_t T; /* number of temporary variables */
506507
ZEND_MAP_PTR_DEF(void **, run_time_cache);
507508
/* END of common elements */
508509

@@ -528,6 +529,7 @@ union _zend_function {
528529
uint32_t required_num_args;
529530
zend_arg_info *arg_info; /* index -1 represents the return value info, if any */
530531
HashTable *attributes;
532+
uint32_t T; /* number of temporary variables */
531533
ZEND_MAP_PTR_DEF(void **, run_time_cache);
532534
} common;
533535

‎Zend/zend_enum.c

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "zend_interfaces.h"
2424
#include "zend_enum.h"
2525
#include "zend_extensions.h"
26+
#include "zend_observer.h"
2627

2728
#define ZEND_ENUM_DISALLOW_MAGIC_METHOD(propertyName, methodName) \
2829
do { \
@@ -407,6 +408,7 @@ static void zend_enum_register_func(zend_class_entry *ce, zend_known_string_id n
407408
zif->type = ZEND_INTERNAL_FUNCTION;
408409
zif->module = EG(current_module);
409410
zif->scope = ce;
411+
zif->T = ZEND_OBSERVER_ENABLED;
410412
ZEND_MAP_PTR_NEW(zif->run_time_cache);
411413
ZEND_MAP_PTR_SET(zif->run_time_cache, zend_arena_alloc(&CG(arena), zend_internal_run_time_cache_reserved_size()));
412414

‎Zend/zend_execute.c

+1
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ ZEND_API const zend_internal_function zend_pass_function = {
144144
0, /* required_num_args */
145145
(zend_internal_arg_info *) zend_pass_function_arg_info + 1, /* arg_info */
146146
NULL, /* attributes */
147+
0, /* T */
147148
NULL, /* run_time_cache */
148149
ZEND_FN(pass), /* handler */
149150
NULL, /* module */

‎Zend/zend_execute.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -245,10 +245,10 @@ static zend_always_inline zend_execute_data *zend_vm_stack_push_call_frame_ex(ui
245245

246246
static zend_always_inline uint32_t zend_vm_calc_used_stack(uint32_t num_args, zend_function *func)
247247
{
248-
uint32_t used_stack = ZEND_CALL_FRAME_SLOT + num_args;
248+
uint32_t used_stack = ZEND_CALL_FRAME_SLOT + num_args + func->common.T;
249249

250250
if (EXPECTED(ZEND_USER_CODE(func->type))) {
251-
used_stack += func->op_array.last_var + func->op_array.T - MIN(func->op_array.num_args, num_args);
251+
used_stack += func->op_array.last_var - MIN(func->op_array.num_args, num_args);
252252
}
253253
return used_stack * sizeof(zval);
254254
}

‎Zend/zend_observer.c

+33-39
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,18 @@ ZEND_API void zend_observer_post_startup(void)
7878
ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op));
7979
ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op) + 1);
8080
ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op) + 2);
81+
82+
// Add an observer temporary to store previous observed frames
83+
zend_internal_function *zif;
84+
ZEND_HASH_FOREACH_PTR(CG(function_table), zif) {
85+
++zif->T;
86+
} ZEND_HASH_FOREACH_END();
87+
zend_class_entry *ce;
88+
ZEND_HASH_MAP_FOREACH_PTR(CG(class_table), ce) {
89+
ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, zif) {
90+
++zif->T;
91+
} ZEND_HASH_FOREACH_END();
92+
} ZEND_HASH_FOREACH_END();
8193
}
8294
}
8395

@@ -187,6 +199,11 @@ ZEND_API bool zend_observer_remove_end_handler(zend_function *function, zend_obs
187199
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(function) + registered_observers, end);
188200
}
189201

202+
static inline zend_execute_data **prev_observed_frame(zend_execute_data *execute_data) {
203+
zend_function *func = EX(func);
204+
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));
205+
}
206+
190207
static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_data)
191208
{
192209
if (!ZEND_OBSERVER_ENABLED) {
@@ -208,9 +225,7 @@ static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_d
208225

209226
zend_observer_fcall_end_handler *end_handler = (zend_observer_fcall_end_handler *)possible_handlers_end;
210227
if (*end_handler != ZEND_OBSERVER_NOT_OBSERVED) {
211-
if (first_observed_frame == NULL) {
212-
first_observed_frame = execute_data;
213-
}
228+
*prev_observed_frame(execute_data) = current_observed_frame;
214229
current_observed_frame = execute_data;
215230
}
216231

@@ -236,29 +251,9 @@ ZEND_API void ZEND_FASTCALL zend_observer_fcall_begin(zend_execute_data *execute
236251
}
237252
}
238253

239-
static inline bool zend_observer_is_skipped_frame(zend_execute_data *execute_data) {
240-
zend_function *func = execute_data->func;
241-
242-
if (!func || !ZEND_OBSERVABLE_FN(func)) {
243-
return true;
244-
}
245-
246-
zend_observer_fcall_end_handler end_handler = (&ZEND_OBSERVER_DATA(func))[zend_observers_fcall_list.count];
247-
if (end_handler == NULL || end_handler == ZEND_OBSERVER_NOT_OBSERVED) {
248-
return true;
249-
}
250-
251-
return false;
252-
}
253-
254-
ZEND_API void ZEND_FASTCALL zend_observer_fcall_end(zend_execute_data *execute_data, zval *return_value)
255-
{
254+
static inline void call_end_observers(zend_execute_data *execute_data, zval *return_value) {
256255
zend_function *func = execute_data->func;
257256

258-
if (!ZEND_OBSERVER_ENABLED || !ZEND_OBSERVABLE_FN(func)) {
259-
return;
260-
}
261-
262257
zend_observer_fcall_end_handler *handler = (zend_observer_fcall_end_handler *)&ZEND_OBSERVER_DATA(func) + zend_observers_fcall_list.count;
263258
// TODO: Fix exceptions from generators
264259
// ZEND_ASSERT(fcall_data);
@@ -270,28 +265,27 @@ ZEND_API void ZEND_FASTCALL zend_observer_fcall_end(zend_execute_data *execute_d
270265
do {
271266
(*handler)(execute_data, return_value);
272267
} while (++handler != possible_handlers_end && *handler != NULL);
268+
}
273269

274-
if (first_observed_frame == execute_data) {
275-
first_observed_frame = NULL;
276-
current_observed_frame = NULL;
277-
} else {
278-
zend_execute_data *ex = execute_data->prev_execute_data;
279-
while (ex && zend_observer_is_skipped_frame(ex)) {
280-
ex = ex->prev_execute_data;
281-
}
282-
current_observed_frame = ex;
270+
ZEND_API void ZEND_FASTCALL zend_observer_fcall_end(zend_execute_data *execute_data, zval *return_value)
271+
{
272+
if (execute_data != current_observed_frame) {
273+
return;
283274
}
275+
call_end_observers(execute_data, return_value);
276+
current_observed_frame = *prev_observed_frame(execute_data);
284277
}
285278

286279
ZEND_API void zend_observer_fcall_end_all(void)
287280
{
288-
zend_execute_data *ex = current_observed_frame;
289-
while (ex != NULL) {
290-
if (ex->func) {
291-
zend_observer_fcall_end(ex, NULL);
292-
}
293-
ex = ex->prev_execute_data;
281+
zend_execute_data *execute_data = current_observed_frame, *original_execute_data = EG(current_execute_data);
282+
current_observed_frame = NULL;
283+
while (execute_data) {
284+
EG(current_execute_data) = execute_data;
285+
call_end_observers(execute_data, NULL);
286+
execute_data = *prev_observed_frame(execute_data);
294287
}
288+
EG(current_execute_data) = original_execute_data;
295289
}
296290

297291
ZEND_API void zend_observer_error_register(zend_observer_error_cb cb)

‎Zend/zend_opcode.c

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "zend_API.h"
2828
#include "zend_sort.h"
2929
#include "zend_constants.h"
30+
#include "zend_observer.h"
3031

3132
#include "zend_vm.h"
3233

@@ -1049,6 +1050,8 @@ ZEND_API void pass_two(zend_op_array *op_array)
10491050
CG(context).literals_size = op_array->last_literal;
10501051
#endif
10511052

1053+
op_array->T += ZEND_OBSERVER_ENABLED; // reserve last temporary for observers if enabled
1054+
10521055
/* Needs to be set directly after the opcode/literal reallocation, to ensure destruction
10531056
* happens correctly if any of the following fixups generate a fatal error. */
10541057
op_array->fn_flags |= ZEND_ACC_DONE_PASS_TWO;

‎ext/opcache/jit/zend_jit_arm64.dasc

+1-1
Original file line numberDiff line numberDiff line change
@@ -8407,7 +8407,7 @@ static int zend_jit_push_call_frame(dasm_State **Dst, const zend_op *opline, con
84078407
stack_check = 0;
84088408
}
84098409
} else {
8410-
used_stack = (ZEND_CALL_FRAME_SLOT + opline->extended_value) * sizeof(zval);
8410+
used_stack = (ZEND_CALL_FRAME_SLOT + opline->extended_value + ZEND_OBSERVER_ENABLED) * sizeof(zval);
84118411

84128412
| // if (EXPECTED(ZEND_USER_CODE(func->type))) {
84138413
if (!is_closure) {

‎ext/opcache/jit/zend_jit_x86.dasc

+1-1
Original file line numberDiff line numberDiff line change
@@ -9010,7 +9010,7 @@ static int zend_jit_push_call_frame(dasm_State **Dst, const zend_op *opline, con
90109010
stack_check = 0;
90119011
}
90129012
} else {
9013-
used_stack = (ZEND_CALL_FRAME_SLOT + opline->extended_value) * sizeof(zval);
9013+
used_stack = (ZEND_CALL_FRAME_SLOT + opline->extended_value + ZEND_OBSERVER_ENABLED) * sizeof(zval);
90149014

90159015
| // if (EXPECTED(ZEND_USER_CODE(func->type))) {
90169016
if (!is_closure) {

‎ext/pdo/pdo_dbh.c

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "zend_object_handlers.h"
3434
#include "zend_hash.h"
3535
#include "pdo_dbh_arginfo.h"
36+
#include "zend_observer.h"
3637

3738
static bool pdo_dbh_attribute_set(pdo_dbh_t *dbh, zend_long attr, zval *value);
3839

@@ -1246,6 +1247,7 @@ bool pdo_hash_methods(pdo_dbh_object_t *dbh_obj, int kind)
12461247
func.scope = dbh_obj->std.ce;
12471248
func.prototype = NULL;
12481249
ZEND_MAP_PTR(func.run_time_cache) = NULL;
1250+
func.T = ZEND_OBSERVER_ENABLED;
12491251
if (funcs->flags) {
12501252
func.fn_flags = funcs->flags | ZEND_ACC_NEVER_CACHE;
12511253
} else {

0 commit comments

Comments
 (0)
Please sign in to comment.