Skip to content

Optimize observers #13643

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Zend/Optimizer/sccp.c
Original file line number Diff line number Diff line change
Expand Up @@ -2211,6 +2211,8 @@ static int try_remove_definition(sccp_ctx *ctx, int var_num, zend_ssa_var *var,
if (has_op_data) {
zend_ssa_remove_instr(ssa, opline + 1, ssa_op + 1);
removed_ops++;
old_type = opline[1].result_type;
old_var = opline[1].result.var;
}
}
ssa_op->result_def = var_num;
Expand Down
2 changes: 1 addition & 1 deletion Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -3869,7 +3869,7 @@ static zend_always_inline zend_result _zend_update_type_info(
UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
}
if (opline->opcode == ZEND_FRAMELESS_ICALL_3) {
zend_ssa_op *next_ssa_op = ssa_op + 1;
zend_ssa_op *next_ssa_op = ++ssa_op;
if (next_ssa_op->op1_def >= 0) {
ZEND_ASSERT(next_ssa_op->op1_use >= 0);
tmp = ssa->var_info[next_ssa_op->op1_use].type;
Expand Down
8 changes: 5 additions & 3 deletions Zend/Optimizer/zend_ssa.c
Original file line number Diff line number Diff line change
Expand Up @@ -782,12 +782,14 @@ static zend_always_inline int _zend_ssa_rename_op(const zend_op_array *op_array,
//NEW_SSA_VAR(opline->op2.var)
}
if (opline->opcode == ZEND_FRAMELESS_ICALL_3) {
next = opline + 1;
// Result is on following OP_DATA
next = ++opline;
++k;
if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
ssa_ops[k].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
//USE_SSA_VAR(op_array->last_var + next->op1.var);
if ((build_flags & ZEND_SSA_RC_INFERENCE) && next->op1_type == IS_CV) {
ssa_ops[k + 1].op1_def = ssa_vars_count;
ssa_ops[k].op1_def = ssa_vars_count;
var[EX_VAR_TO_NUM(next->op1.var)] = ssa_vars_count;
ssa_vars_count++;
//NEW_SSA_VAR(next->op1.var)
Expand Down
9 changes: 7 additions & 2 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -4542,7 +4542,7 @@ static uint32_t find_frameless_function_offset(uint32_t arity, void *handler)

static const zend_frameless_function_info *find_frameless_function_info(zend_ast_list *args, zend_function *fbc, uint32_t type)
{
if (ZEND_OBSERVER_ENABLED || zend_execute_internal) {
if (zend_execute_internal) {
return NULL;
}

Expand Down Expand Up @@ -4608,7 +4608,12 @@ static uint32_t zend_compile_frameless_icall_ex(znode *result, zend_ast_list *ar
SET_NODE(opline->op2, &arg_zvs[1]);
}
if (num_args >= 3) {
zend_emit_op_data(&arg_zvs[2]);
// Put result znode on OP_DATA to ensure dispatch in observer fallback can be aligned with the last opcode of the call
zend_op *op_data = zend_emit_op_data(&arg_zvs[2]);
op_data->result = opline->result;
op_data->result_type = opline->result_type;
opline->result_type = IS_UNUSED;
++opnum;
}
return opnum;
}
Expand Down
2 changes: 2 additions & 0 deletions Zend/zend_globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ struct _zend_executor_globals {

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

zend_execute_data *current_observed_frame;

int ticks_count;

zend_long precision;
Expand Down
102 changes: 60 additions & 42 deletions Zend/zend_observer.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
+----------------------------------------------------------------------+
| Authors: Levi Morrison <levim@php.net> |
| Sammy Kaye Powers <sammyk@php.net> |
| Bob Weinand <bobwei9@hotmail.com> |
+----------------------------------------------------------------------+
*/

Expand All @@ -23,14 +24,8 @@
#include "zend_llist.h"
#include "zend_vm.h"

#define ZEND_OBSERVER_DATA(function) \
ZEND_OP_ARRAY_EXTENSION((&(function)->common), zend_observer_fcall_op_array_extension)

#define ZEND_OBSERVER_NOT_OBSERVED ((void *) 2)

#define ZEND_OBSERVABLE_FN(function) \
(ZEND_MAP_PTR(function->common.run_time_cache) && !(function->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE))

zend_llist zend_observers_fcall_list;
zend_llist zend_observer_function_declared_callbacks;
zend_llist zend_observer_class_linked_callbacks;
Expand All @@ -44,8 +39,6 @@ bool zend_observer_errors_observed;
bool zend_observer_function_declared_observed;
bool zend_observer_class_linked_observed;

ZEND_TLS zend_execute_data *current_observed_frame;

// Call during minit/startup ONLY
ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init init)
{
Expand Down Expand Up @@ -101,7 +94,7 @@ ZEND_API void zend_observer_post_startup(void)

ZEND_API void zend_observer_activate(void)
{
current_observed_frame = NULL;
EG(current_observed_frame) = NULL;
}

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

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

*begin_handlers = ZEND_OBSERVER_NOT_OBSERVED;
*end_handlers = ZEND_OBSERVER_NOT_OBSERVED;
bool has_handlers = false;

for (zend_llist_element *element = list->head; element; element = element->next) {
zend_observer_fcall_init init;
memcpy(&init, element->data, sizeof init);
zend_observer_fcall_handlers handlers = init(execute_data);
if (handlers.begin) {
*(begin_handlers++) = handlers.begin;
has_handlers = true;
}
if (handlers.end) {
*(end_handlers++) = handlers.end;
has_handlers = true;
}
}

Expand All @@ -145,6 +141,10 @@ static void zend_observer_fcall_install(zend_execute_data *execute_data)
*end_handlers = *end_handlers_start;
*end_handlers_start = tmp;
}

if (!has_handlers) {
*begin_handlers_start = ZEND_OBSERVER_NONE_OBSERVED;
}
}

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

ZEND_API void zend_observer_add_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin) {
size_t registered_observers = zend_observers_fcall_list.count;
zend_observer_fcall_begin_handler *first_handler = (void *)&ZEND_OBSERVER_DATA(function), *last_handler = first_handler + registered_observers - 1;
if (*first_handler == ZEND_OBSERVER_NOT_OBSERVED) {
zend_observer_fcall_begin_handler *first_handler = ZEND_OBSERVER_DATA(function), *last_handler = first_handler + registered_observers - 1;
if (*first_handler == ZEND_OBSERVER_NOT_OBSERVED || *first_handler == ZEND_OBSERVER_NONE_OBSERVED) {
*first_handler = begin;
} else {
for (zend_observer_fcall_begin_handler *cur_handler = first_handler + 1; cur_handler <= last_handler; ++cur_handler) {
Expand All @@ -185,24 +185,45 @@ ZEND_API void zend_observer_add_begin_handler(zend_function *function, zend_obse
}

ZEND_API bool zend_observer_remove_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin) {
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(function), begin);
void **begin_handlers = (void **)ZEND_OBSERVER_DATA(function);
if (zend_observer_remove_handler(begin_handlers, begin)) {
if (*begin_handlers == ZEND_OBSERVER_NOT_OBSERVED) {
size_t registered_observers = zend_observers_fcall_list.count;
if (begin_handlers[registered_observers] /* first end handler */ == ZEND_OBSERVER_NOT_OBSERVED) {
*begin_handlers = ZEND_OBSERVER_NONE_OBSERVED;
}
}
return true;
}
return false;
}

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

ZEND_API bool zend_observer_remove_end_handler(zend_function *function, zend_observer_fcall_end_handler end) {
size_t registered_observers = zend_observers_fcall_list.count;
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(function) + registered_observers, end);
void **begin_handlers = (void **)ZEND_OBSERVER_DATA(function);
void **end_handlers = begin_handlers + registered_observers;
if (zend_observer_remove_handler(end_handlers, end)) {
if (*begin_handlers == ZEND_OBSERVER_NOT_OBSERVED && *end_handlers == ZEND_OBSERVER_NOT_OBSERVED) {
*begin_handlers = ZEND_OBSERVER_NONE_OBSERVED;
}
return true;
}
return false;
}

static inline zend_execute_data **prev_observed_frame(zend_execute_data *execute_data) {
Expand All @@ -211,33 +232,33 @@ static inline zend_execute_data **prev_observed_frame(zend_execute_data *execute
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));
}

static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_data)
{
static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_data) {
if (!ZEND_OBSERVER_ENABLED) {
return;
}

zend_function *function = execute_data->func;
zend_observer_fcall_begin_specialized(execute_data, true);
}

if (!ZEND_OBSERVABLE_FN(function)) {
return;
}
void ZEND_FASTCALL zend_observer_fcall_begin_prechecked(zend_execute_data *execute_data, zend_observer_fcall_begin_handler *handler)
{
zend_observer_fcall_begin_handler *possible_handlers_end = handler + zend_observers_fcall_list.count;

zend_observer_fcall_begin_handler *handler = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(function);
if (!*handler) {
zend_observer_fcall_install(execute_data);
if (zend_observer_handler_is_unobserved(handler)) {
return;
}
}

zend_observer_fcall_begin_handler *possible_handlers_end = handler + zend_observers_fcall_list.count;

zend_observer_fcall_end_handler *end_handler = (zend_observer_fcall_end_handler *)possible_handlers_end;
if (*end_handler != ZEND_OBSERVER_NOT_OBSERVED) {
*prev_observed_frame(execute_data) = current_observed_frame;
current_observed_frame = execute_data;
}
*prev_observed_frame(execute_data) = EG(current_observed_frame);
EG(current_observed_frame) = execute_data;

if (*handler == ZEND_OBSERVER_NOT_OBSERVED) {
return;
if (*handler == ZEND_OBSERVER_NOT_OBSERVED) { // this function must not be called if ZEND_OBSERVER_NONE_OBSERVED, hence sufficient to check
return;
}
}

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

ZEND_API void ZEND_FASTCALL zend_observer_fcall_begin(zend_execute_data *execute_data)
{
ZEND_ASSUME(execute_data->func);
if (!(execute_data->func->common.fn_flags & ZEND_ACC_GENERATOR)) {
ZEND_ASSUME(EX(func));
if (!(EX(func)->common.fn_flags & ZEND_ACC_GENERATOR)) {
_zend_observe_fcall_begin(execute_data);
}
}

static inline void call_end_observers(zend_execute_data *execute_data, zval *return_value) {
zend_function *func = execute_data->func;
zend_function *func = EX(func);
ZEND_ASSERT(func);

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

ZEND_API void ZEND_FASTCALL zend_observer_fcall_end(zend_execute_data *execute_data, zval *return_value)
ZEND_API void ZEND_FASTCALL zend_observer_fcall_end_prechecked(zend_execute_data *execute_data, zval *return_value)
{
if (execute_data != current_observed_frame) {
return;
}
call_end_observers(execute_data, return_value);
current_observed_frame = *prev_observed_frame(execute_data);
EG(current_observed_frame) = *prev_observed_frame(execute_data);
}

ZEND_API void zend_observer_fcall_end_all(void)
{
zend_execute_data *execute_data = current_observed_frame, *original_execute_data = EG(current_execute_data);
current_observed_frame = NULL;
zend_execute_data *execute_data = EG(current_observed_frame), *original_execute_data = EG(current_execute_data);
EG(current_observed_frame) = NULL;
while (execute_data) {
EG(current_execute_data) = execute_data;
call_end_observers(execute_data, NULL);
Expand Down Expand Up @@ -388,8 +406,8 @@ ZEND_API void ZEND_FASTCALL zend_observer_fiber_switch_notify(zend_fiber_context
callback(from, to);
}

from->top_observed_frame = current_observed_frame;
current_observed_frame = to->top_observed_frame;
from->top_observed_frame = EG(current_observed_frame);
EG(current_observed_frame) = to->top_observed_frame;
}

ZEND_API void ZEND_FASTCALL zend_observer_fiber_destroy_notify(zend_fiber_context *destroying)
Expand Down
Loading