From c1594f226d6d8a13f359c96146b59f8fd519e4c3 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 21 Feb 2020 13:13:36 +0100 Subject: [PATCH] Allow using prototypes when optimizing arg passing --- ext/opcache/Optimizer/optimize_func_calls.c | 8 ++++++-- ext/opcache/Optimizer/zend_call_graph.c | 6 ++++-- ext/opcache/Optimizer/zend_optimizer.c | 16 +++++++++++----- ext/opcache/Optimizer/zend_optimizer_internal.h | 2 +- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/ext/opcache/Optimizer/optimize_func_calls.c b/ext/opcache/Optimizer/optimize_func_calls.c index 2894ca89f4d54..309a1401671ae 100644 --- a/ext/opcache/Optimizer/optimize_func_calls.c +++ b/ext/opcache/Optimizer/optimize_func_calls.c @@ -39,6 +39,7 @@ typedef struct _optimizer_call_info { zend_function *func; zend_op *opline; + zend_bool is_prototype; zend_bool try_inline; uint32_t func_arg_num; } optimizer_call_info; @@ -172,9 +173,12 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) case ZEND_INIT_METHOD_CALL: case ZEND_INIT_FCALL: case ZEND_NEW: + /* The argument passing optimizations are valid for prototypes as well, + * as inheritance cannot change between ref <-> non-ref arguments. */ call_stack[call].func = zend_optimizer_get_called_func( - ctx->script, op_array, opline); - call_stack[call].try_inline = opline->opcode != ZEND_NEW; + ctx->script, op_array, opline, &call_stack[call].is_prototype); + call_stack[call].try_inline = + !call_stack[call].is_prototype && opline->opcode != ZEND_NEW; /* break missing intentionally */ case ZEND_INIT_DYNAMIC_CALL: case ZEND_INIT_USER_CALL: diff --git a/ext/opcache/Optimizer/zend_call_graph.c b/ext/opcache/Optimizer/zend_call_graph.c index 28b20d10b804e..d9ee3d3b972c2 100644 --- a/ext/opcache/Optimizer/zend_call_graph.c +++ b/ext/opcache/Optimizer/zend_call_graph.c @@ -93,6 +93,7 @@ int zend_analyze_calls(zend_arena **arena, zend_script *script, uint32_t build_f int call = 0; zend_call_info **call_stack; ALLOCA_FLAG(use_heap); + zend_bool is_prototype; call_stack = do_alloca((op_array->last / 2) * sizeof(zend_call_info*), use_heap); call_info = NULL; @@ -103,8 +104,9 @@ int zend_analyze_calls(zend_arena **arena, zend_script *script, uint32_t build_f case ZEND_INIT_STATIC_METHOD_CALL: call_stack[call] = call_info; func = zend_optimizer_get_called_func( - script, op_array, opline); - if (func) { + script, op_array, opline, &is_prototype); + /* TODO: Support prototypes? */ + if (func && !is_prototype) { call_info = zend_arena_calloc(arena, 1, sizeof(zend_call_info) + (sizeof(zend_send_arg_info) * ((int)opline->extended_value - 1))); call_info->caller_op_array = op_array; call_info->caller_init_opline = opline; diff --git a/ext/opcache/Optimizer/zend_optimizer.c b/ext/opcache/Optimizer/zend_optimizer.c index 05221b1587da6..313ca45bd3690 100644 --- a/ext/opcache/Optimizer/zend_optimizer.c +++ b/ext/opcache/Optimizer/zend_optimizer.c @@ -814,8 +814,9 @@ static zend_class_entry *get_class_entry_from_op1( } zend_function *zend_optimizer_get_called_func( - zend_script *script, zend_op_array *op_array, zend_op *opline) + zend_script *script, zend_op_array *op_array, zend_op *opline, zend_bool *is_prototype) { + *is_prototype = 0; switch (opline->opcode) { case ZEND_INIT_FCALL: { @@ -862,7 +863,7 @@ zend_function *zend_optimizer_get_called_func( if (fbc) { zend_bool is_public = (fbc->common.fn_flags & ZEND_ACC_PUBLIC) != 0; zend_bool same_scope = fbc->common.scope == op_array->scope; - if (is_public|| same_scope) { + if (is_public || same_scope) { return fbc; } } @@ -880,10 +881,15 @@ zend_function *zend_optimizer_get_called_func( zend_bool is_private = (fbc->common.fn_flags & ZEND_ACC_PRIVATE) != 0; zend_bool is_final = (fbc->common.fn_flags & ZEND_ACC_FINAL) != 0; zend_bool same_scope = fbc->common.scope == op_array->scope; - if ((is_private && same_scope) - || (is_final && (!is_private || same_scope))) { - return fbc; + if (is_private) { + /* Only use private method if in the same scope. We can't even use it + * as a prototype, as it may be overridden with changed signature. */ + return same_scope ? fbc : NULL; } + /* If the method is non-final, it may be overriden, + * but only with a compatible method signature. */ + *is_prototype = !is_final; + return fbc; } } break; diff --git a/ext/opcache/Optimizer/zend_optimizer_internal.h b/ext/opcache/Optimizer/zend_optimizer_internal.h index 270a85c89ae0f..354050c942fad 100644 --- a/ext/opcache/Optimizer/zend_optimizer_internal.h +++ b/ext/opcache/Optimizer/zend_optimizer_internal.h @@ -110,7 +110,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx void zend_optimizer_compact_vars(zend_op_array *op_array); int zend_optimizer_is_disabled_func(const char *name, size_t len); zend_function *zend_optimizer_get_called_func( - zend_script *script, zend_op_array *op_array, zend_op *opline); + zend_script *script, zend_op_array *op_array, zend_op *opline, zend_bool *is_prototype); uint32_t zend_optimizer_classify_function(zend_string *name, uint32_t num_args); void zend_optimizer_migrate_jump(zend_op_array *op_array, zend_op *new_opline, zend_op *opline); void zend_optimizer_shift_jump(zend_op_array *op_array, zend_op *opline, uint32_t *shiftlist);