diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c index f8cbefdaaf2b..a1c758554ef6 100644 --- a/Zend/Optimizer/zend_optimizer.c +++ b/Zend/Optimizer/zend_optimizer.c @@ -1526,52 +1526,9 @@ static bool needs_live_range(const zend_op_array *op_array, const zend_op *def_o return (type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) != 0; } -static void zend_foreach_op_array_helper( - zend_op_array *op_array, zend_op_array_func_t func, void *context) { - func(op_array, context); - for (uint32_t i = 0; i < op_array->num_dynamic_func_defs; i++) { - zend_foreach_op_array_helper(op_array->dynamic_func_defs[i], func, context); - } -} - void zend_foreach_op_array(zend_script *script, zend_op_array_func_t func, void *context) { - zval *zv; - zend_op_array *op_array; - - zend_foreach_op_array_helper(&script->main_op_array, func, context); - - ZEND_HASH_MAP_FOREACH_PTR(&script->function_table, op_array) { - zend_foreach_op_array_helper(op_array, func, context); - } ZEND_HASH_FOREACH_END(); - - ZEND_HASH_MAP_FOREACH_VAL(&script->class_table, zv) { - if (Z_TYPE_P(zv) == IS_ALIAS_PTR) { - continue; - } - const zend_class_entry *ce = Z_CE_P(zv); - ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, op_array) { - if (op_array->scope == ce - && op_array->type == ZEND_USER_FUNCTION - && !(op_array->fn_flags & ZEND_ACC_ABSTRACT) - && !(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) { - zend_foreach_op_array_helper(op_array, func, context); - } - } ZEND_HASH_FOREACH_END(); - - zend_property_info *property; - ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, property) { - zend_function **hooks = property->hooks; - if (property->ce == ce && property->hooks) { - for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { - const zend_function *hook = hooks[i]; - if (hook && hook->common.scope == ce && !(hooks[i]->op_array.fn_flags & ZEND_ACC_TRAIT_CLONE)) { - zend_foreach_op_array_helper(&hooks[i]->op_array, func, context); - } - } - } - } ZEND_HASH_FOREACH_END(); - } ZEND_HASH_FOREACH_END(); + zend_foreach_op_array_ex(&script->main_op_array, &script->function_table, &script->class_table, func, context); } static void step_optimize_op_array(zend_op_array *op_array, void *context) { diff --git a/Zend/tests/first_class_callable/constexpr/namespace_004.phpt b/Zend/tests/first_class_callable/constexpr/namespace_004.phpt index 6fe99593bf95..7908dbe6d52c 100644 --- a/Zend/tests/first_class_callable/constexpr/namespace_004.phpt +++ b/Zend/tests/first_class_callable/constexpr/namespace_004.phpt @@ -1,5 +1,11 @@ --TEST-- Allow defining FCC in const expressions in a namespace with function matching a global function later. +--SKIPIF-- + --FILE-- +--EXPECT-- +int(5) +string(8) "shadow:5" diff --git a/Zend/tests/ns_global_func_assumption_002_shadow.inc b/Zend/tests/ns_global_func_assumption_002_shadow.inc new file mode 100644 index 000000000000..268babc02c9c --- /dev/null +++ b/Zend/tests/ns_global_func_assumption_002_shadow.inc @@ -0,0 +1,7 @@ +function_name = zend_string_init_interned(ptr->fname, fname_len, 1); internal_function->scope = scope; internal_function->prototype = NULL; + internal_function->fn_flags2 = 0; internal_function->prop_info = NULL; internal_function->attributes = NULL; internal_function->frameless_function_infos = ptr->frameless_function_infos; diff --git a/Zend/zend_bitset.h b/Zend/zend_bitset.h index d990b6a2a871..7b6833ecb110 100644 --- a/Zend/zend_bitset.h +++ b/Zend/zend_bitset.h @@ -296,4 +296,14 @@ static inline int zend_bitset_pop_first(zend_bitset set, uint32_t len) { return i; } +static inline bool zend_bitset_has_intersection(zend_bitset set1, zend_bitset set2, uint32_t len) +{ + for (uint32_t i = 0; i < len; i++) { + if (set1[i] & set2[i]) { + return 1; + } + } + return 0; +} + #endif /* _ZEND_BITSET_H_ */ diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 23db72bb4fda..fc9bb64b7b79 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -39,6 +39,7 @@ #include "zend_call_stack.h" #include "zend_frameless_function.h" #include "zend_property_hooks.h" +#include "zend_bitset.h" #define SET_NODE(target, src) do { \ target ## _type = (src)->op_type; \ @@ -343,7 +344,6 @@ void zend_oparray_context_begin(zend_oparray_context *prev_context, zend_op_arra CG(context).has_assigned_to_http_response_header = false; CG(context).brk_cont_array = NULL; CG(context).labels = NULL; - CG(context).in_jmp_frameless_branch = false; CG(context).active_property_info_name = NULL; CG(context).active_property_hook_kind = (zend_property_hook_kind)-1; } @@ -1280,6 +1280,153 @@ ZEND_API void function_add_ref(zend_function *function) /* {{{ */ } /* }}} */ +/* Record that the active op_array assumes the internal function at the given + * arData index is the global (non-shadowed) version. */ +static void zend_record_global_func_assumption(uint32_t ardata_index) +{ + zend_op_array *op_array = CG(active_op_array); + uint32_t len = zend_bitset_len(CG(num_global_internal_funcs)); + if (!op_array->global_func_assumptions) { + op_array->global_func_assumptions = emalloc(len * sizeof(zend_ulong)); + memset(op_array->global_func_assumptions, 0, len * sizeof(zend_ulong)); + } + zend_bitset_incl(op_array->global_func_assumptions, ardata_index); + op_array->fn_flags2 |= ZEND_ACC2_ASSUMPTIONS; +} + +/* Clear all runtime caches so that function lookups are re-evaluated. + * Called when a new namespace shadow is declared. */ +static void zend_clear_op_array_runtime_cache(zend_op_array *op_array, void *context) +{ + (void)context; + void **cache = ZEND_MAP_PTR_GET(op_array->run_time_cache); + if (cache) { + memset(cache, 0, op_array->cache_size); + } +} + +static void zend_clear_all_runtime_caches(void) +{ + // FIXME: Skip internal functions and classes? + zend_foreach_op_array_ex(NULL, EG(function_table), EG(class_table), zend_clear_op_array_runtime_cache, NULL); +} + +/* Check if a namespaced function shadows a global function. + * If so, set the corresponding bit in the global shadow bitmap. */ +static void zend_check_ns_function_shadow(const zend_string *lcname) +{ + const char *backslash = zend_memrchr(ZSTR_VAL(lcname), '\\', ZSTR_LEN(lcname)); + if (!backslash) { + return; + } + const char *unqualified = backslash + 1; + size_t unqualified_len = ZSTR_LEN(lcname) - (unqualified - ZSTR_VAL(lcname)); + zend_string *global_name = zend_string_init(unqualified, unqualified_len, 0); + const zval *global_fbc_zv = zend_hash_find(EG(function_table), global_name); + if (global_fbc_zv) { + const zend_function *global_fbc = Z_PTR_P(global_fbc_zv); + if (!ZEND_USER_CODE(global_fbc->type)) { + const Bucket *bucket = (const Bucket *)((uintptr_t)global_fbc_zv - XtOffsetOf(Bucket, val)); + uint32_t ardata_index = bucket - EG(function_table)->arData; + if (ardata_index < CG(num_global_internal_funcs)) { + if (!EG(shadowed_global_funcs)) { + EG(shadowed_global_funcs_len) = zend_bitset_len(CG(num_global_internal_funcs)); + // FIXME: Arena allocation sufficient? + EG(shadowed_global_funcs) = ecalloc(EG(shadowed_global_funcs_len), sizeof(zend_ulong)); + } + zend_bitset_incl(EG(shadowed_global_funcs), ardata_index); + zend_clear_all_runtime_caches(); + } + } + } + zend_string_release(global_name); +} + +/* Recompile a function without NS global assumptions. + * Returns the deoptimized op_array or NULL on failure. + * The result is cached on func->op_array.deoptimized. */ +ZEND_API zend_function *zend_get_deoptimized_function(zend_function *func) +{ + ZEND_ASSERT(func->type == ZEND_USER_FUNCTION); + + /* Check cache first. */ + if (func->op_array.deoptimized) { + return (zend_function *)func->op_array.deoptimized; + } + + /* Temporarily remove user functions from the same file to avoid + * redeclaration errors during recompilation. */ + HashTable saved_functions; + zend_hash_init(&saved_functions, 8, NULL, NULL, 0); + + // FIXME: This is slow, better to just skip decl of functions in the deoptimization case. + const zend_string *filename = func->op_array.filename; + zend_string *key; + zval *zv; + ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(EG(function_table), key, zv) { + zend_function *f = Z_PTR_P(zv); + if (f->type == ZEND_USER_FUNCTION && f->op_array.filename == filename) { + zend_hash_add_new_ptr(&saved_functions, key, f); + } + } ZEND_HASH_FOREACH_END(); + + dtor_func_t orig_dtor = EG(function_table)->pDestructor; + EG(function_table)->pDestructor = NULL; + ZEND_HASH_MAP_FOREACH_STR_KEY(&saved_functions, key) { + zend_hash_del(EG(function_table), key); + } ZEND_HASH_FOREACH_END(); + EG(function_table)->pDestructor = orig_dtor; + + /* Compile the file without NS global assumptions. */ + uint32_t orig_compiler_options = CG(compiler_options); + CG(compiler_options) |= ZEND_COMPILE_DEOPTIMIZED; + CG(compiler_options) |= ZEND_COMPILE_IGNORE_OTHER_FILES; + CG(compiler_options) |= ZEND_COMPILE_DELAYED_BINDING; + + zend_file_handle file_handle; + zend_stream_init_filename_ex(&file_handle, func->op_array.filename); + zend_op_array *main_op_array = zend_compile_file(&file_handle, ZEND_INCLUDE); + zend_destroy_file_handle(&file_handle); + + CG(compiler_options) = orig_compiler_options; + + /* Find the deoptimized function by name. */ + zend_function *deopt = NULL; + if (main_op_array && func->op_array.function_name) { + zend_string *lc_name = zend_string_tolower(func->op_array.function_name); + deopt = zend_hash_find_ptr(EG(function_table), lc_name); + zend_string_release(lc_name); + } + + /* Remove newly compiled functions and restore originals. */ + EG(function_table)->pDestructor = NULL; + ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(&saved_functions, key, zv) { + zend_hash_update_ptr(EG(function_table), key, Z_PTR_P(zv)); + } ZEND_HASH_FOREACH_END(); + EG(function_table)->pDestructor = orig_dtor; + zend_hash_destroy(&saved_functions); + + if (main_op_array) { + destroy_op_array(main_op_array); + efree(main_op_array); + } + + /* Cache the result on the original op_array. */ + // FIXME: We should either somehow only compile the relevant function, or at + // least link all deoptimized functions to their optimized counterparts. + // Otherwise we'll trigger a deoptimization for each function, while also + // persisting the entire script every time. + if (deopt) { + if (!zend_store_deoptimized_op_array) { + func->op_array.deoptimized = &deopt->op_array; + } else { + zend_store_deoptimized_op_array(&func->op_array, &deopt->op_array); + } + } + + return deopt; +} + static zend_never_inline ZEND_COLD ZEND_NORETURN void do_bind_function_error(const zend_string *lcname, const zend_op_array *op_array, bool compile_time) /* {{{ */ { const zval *zv = zend_hash_find_known_hash(compile_time ? CG(function_table) : EG(function_table), lcname); @@ -1315,6 +1462,7 @@ ZEND_API zend_result do_bind_function(zend_function *func, const zval *lcname) / zend_string_addref(func->common.function_name); } zend_observer_function_declared_notify(&func->op_array, Z_STR_P(lcname)); + zend_check_ns_function_shadow(Z_STR_P(lcname)); return SUCCESS; } /* }}} */ @@ -4817,28 +4965,28 @@ static void zend_compile_ns_call(znode *result, const znode *name_node, zend_ast { int name_constants = zend_add_ns_func_name_literal(Z_STR(name_node->u.constant)); - /* Find frameless function with same name. */ - const zend_function *frameless_function = NULL; - if (args_ast->kind != ZEND_AST_CALLABLE_CONVERT - && !zend_args_contain_unpack_or_named(zend_ast_get_list(args_ast)) - /* Avoid blowing up op count with nested frameless branches. */ - && !CG(context).in_jmp_frameless_branch) { + /* When compiling without the deoptimization flag, use frameless calls + * directly. If a shadow is later declared, the function will be + * deoptimized and recompiled without frameless. */ + if (!(CG(compiler_options) & ZEND_COMPILE_DEOPTIMIZED) + && args_ast->kind != ZEND_AST_CALLABLE_CONVERT + && !zend_args_contain_unpack_or_named(zend_ast_get_list(args_ast))) { + zend_string *lc_ns_name = Z_STR_P(CT_CONSTANT_EX(CG(active_op_array), name_constants + 1)); zend_string *lc_func_name = Z_STR_P(CT_CONSTANT_EX(CG(active_op_array), name_constants + 2)); - frameless_function = zend_hash_find_ptr(CG(function_table), lc_func_name); - } - - /* Check whether any frameless handler may actually be used. */ - uint32_t jmp_fl_opnum = 0; - const zend_frameless_function_info *frameless_function_info = NULL; - if (frameless_function) { - frameless_function_info = find_frameless_function_info(zend_ast_get_list(args_ast), frameless_function, type); - if (frameless_function_info) { - CG(context).in_jmp_frameless_branch = true; - znode op1; - op1.op_type = IS_CONST; - ZVAL_COPY(&op1.u.constant, CT_CONSTANT_EX(CG(active_op_array), name_constants + 1)); - jmp_fl_opnum = get_next_op_number(); - zend_emit_op(NULL, ZEND_JMP_FRAMELESS, &op1, NULL); + if (!zend_hash_exists(CG(function_table), lc_ns_name) + && !zend_have_seen_symbol(lc_ns_name, ZEND_SYMBOL_FUNCTION)) { + const zval *frameless_fbc_zv = zend_hash_find(CG(function_table), lc_func_name); + if (frameless_fbc_zv) { + const zend_function *frameless_function = Z_PTR_P(frameless_fbc_zv); + const zend_frameless_function_info *info = find_frameless_function_info(zend_ast_get_list(args_ast), frameless_function, type); + if (info) { + const Bucket *fbc_bucket = (const Bucket *)((uintptr_t)frameless_fbc_zv - XtOffsetOf(Bucket, val)); + uint32_t ardata_index = fbc_bucket - CG(function_table)->arData; + zend_record_global_func_assumption(ardata_index); + zend_compile_frameless_icall_ex(result, zend_ast_get_list(args_ast), frameless_function, info, type); + return; + } + } } } @@ -4849,25 +4997,6 @@ static void zend_compile_ns_call(znode *result, const znode *name_node, zend_ast opline->op2.constant = name_constants; opline->result.num = zend_alloc_cache_slot(); zend_compile_call_common(result, args_ast, NULL, lineno, type); - - /* Compile frameless call. */ - if (frameless_function_info) { - CG(zend_lineno) = lineno; - - uint32_t jmp_end_opnum = zend_emit_jump(0); - uint32_t jmp_fl_target = get_next_op_number(); - - uint32_t flf_icall_opnum = zend_compile_frameless_icall_ex(NULL, zend_ast_get_list(args_ast), frameless_function, frameless_function_info, type); - - zend_op *jmp_fl = &CG(active_op_array)->opcodes[jmp_fl_opnum]; - jmp_fl->op2.opline_num = jmp_fl_target; - jmp_fl->extended_value = zend_alloc_cache_slot(); - zend_op *flf_icall = &CG(active_op_array)->opcodes[flf_icall_opnum]; - SET_NODE(flf_icall->result, result); - zend_update_jump_target_to_next(jmp_end_opnum); - - CG(context).in_jmp_frameless_branch = false; - } } /* }}} */ @@ -5424,13 +5553,48 @@ static void zend_compile_call(znode *result, const zend_ast *ast, uint32_t type) if (zend_string_equals_literal_ci(zend_ast_get_str(name_ast), "assert") && !is_callable_convert) { zend_compile_assert(result, zend_ast_get_list(args_ast), Z_STR(name_node.u.constant), NULL, ast->lineno, type); - } else { - zend_compile_ns_call(result, &name_node, args_ast, ast->lineno, type); + return; } + + if (!is_callable_convert && !(CG(compiler_options) & ZEND_COMPILE_DEOPTIMIZED)) { + /* Check if the unqualified name matches a known global function + * that is not shadowed by a NS-local function at compile time. */ + zend_string *orig_name = zend_ast_get_str(name_ast); + zend_string *lc_orig_name = zend_string_tolower(orig_name); + zend_string *lc_ns_name = zend_string_tolower(Z_STR(name_node.u.constant)); + const zval *global_fbc_zv = zend_hash_find(CG(function_table), lc_orig_name); + const zend_function *global_fbc = global_fbc_zv ? Z_PTR_P(global_fbc_zv) : NULL; + bool ns_func_exists = zend_hash_exists(CG(function_table), lc_ns_name) + || zend_have_seen_symbol(lc_ns_name, ZEND_SYMBOL_FUNCTION); + zend_string_release(lc_ns_name); + + if (global_fbc + && !ns_func_exists + && !ZEND_USER_CODE(global_fbc->type) + && !zend_string_equals_literal(lc_orig_name, "call_user_func") + && !zend_string_equals_literal(lc_orig_name, "call_user_func_array") + && !zend_compile_ignore_function(global_fbc, CG(active_op_array)->filename)) { + /* Assume the unqualified call resolves to the global function. + * Replace NS-qualified name with unqualified name and fall + * through to the fully-qualified compilation path. */ + const Bucket *fbc_bucket = (const Bucket *)((uintptr_t)global_fbc_zv - XtOffsetOf(Bucket, val)); + uint32_t ardata_index = fbc_bucket - CG(function_table)->arData; + zval_ptr_dtor(&name_node.u.constant); + ZVAL_STR_COPY(&name_node.u.constant, orig_name); + zend_string_release(lc_orig_name); + zend_record_global_func_assumption(ardata_index); + goto resolve_as_global; + } + + zend_string_release(lc_orig_name); + } + + zend_compile_ns_call(result, &name_node, args_ast, ast->lineno, type); return; } } +resolve_as_global:; { const zval *name = &name_node.u.constant; zend_string *lcname = zend_string_tolower(Z_STR_P(name)); @@ -8898,6 +9062,7 @@ static zend_op_array *zend_compile_func_decl_ex( CG(zend_lineno) = decl->start_lineno; do_bind_function_error(lcname, op_array, true); } + zend_check_ns_function_shadow(lcname); } /* put the implicit return on the really last line */ diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 0b38084a107c..774266e29991 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -207,7 +207,6 @@ typedef struct _zend_oparray_context { HashTable *labels; zend_string *active_property_info_name; zend_property_hook_kind active_property_hook_kind; - bool in_jmp_frameless_branch; bool has_assigned_to_http_response_header; } zend_oparray_context; @@ -413,10 +412,12 @@ typedef struct _zend_oparray_context { /* op_array uses strict mode types | | | */ #define ZEND_ACC_STRICT_TYPES (1U << 31) /* | X | | */ /* | | | */ -/* Function Flags 2 (fn_flags2) (unused: 0-31) | | | */ +/* Function Flags 2 (fn_flags2) (unused: 1-31) | | | */ /* ============================ | | | */ /* | | | */ -/* #define ZEND_ACC2_EXAMPLE (1 << 0) | X | | */ +/* op_array was compiled assuming possibly global calls | | | */ +/* are necessarily global | | | */ +#define ZEND_ACC2_ASSUMPTIONS (1 << 0) /* | X | | */ #define ZEND_ACC_PPP_MASK (ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE) #define ZEND_ACC_PPP_SET_MASK (ZEND_ACC_PUBLIC_SET | ZEND_ACC_PROTECTED_SET | ZEND_ACC_PRIVATE_SET) @@ -575,6 +576,9 @@ struct _zend_op_array { * referenced by index from opcodes. */ zend_op_array **dynamic_func_defs; + zend_ulong *global_func_assumptions; /* bitset: which internal funcs are assumed global */ + struct _zend_op_array *deoptimized; /* recompiled version without NS global assumptions */ + void *reserved[ZEND_MAX_RESERVED_RESOURCES]; }; @@ -964,6 +968,7 @@ ZEND_API zend_result zend_execute_script(int type, zval *retval, zend_file_handl ZEND_API zend_result open_file_for_scanning(zend_file_handle *file_handle); ZEND_API void init_op_array(zend_op_array *op_array, zend_function_type type, int initial_ops_size); ZEND_API void destroy_op_array(zend_op_array *op_array); +ZEND_API zend_function *zend_get_deoptimized_function(zend_function *func); ZEND_API void zend_destroy_static_vars(zend_op_array *op_array); ZEND_API void zend_destroy_file_handle(zend_file_handle *file_handle); ZEND_API void zend_cleanup_mutable_class_data(zend_class_entry *ce); @@ -1311,6 +1316,9 @@ END_EXTERN_C() /* ignore observer notifications, e.g. to manually notify afterwards in a post-processing step after compilation */ #define ZEND_COMPILE_IGNORE_OBSERVER (1<<18) +/* Disable assumption that any possible global function call is global */ +#define ZEND_COMPILE_DEOPTIMIZED (1<<19) + /* The default value for CG(compiler_options) */ #define ZEND_COMPILE_DEFAULT ZEND_COMPILE_HANDLE_OP_ARRAY @@ -1325,4 +1333,7 @@ bool zend_try_ct_eval_cast(zval *result, uint32_t type, zval *op1); bool zend_op_may_elide_result(uint8_t opcode); +typedef void (*zend_op_array_func_t)(zend_op_array *, void *context); +void zend_foreach_op_array_ex(zend_op_array *main_op_array, HashTable *function_table, HashTable *class_table, zend_op_array_func_t func, void *context); + #endif /* ZEND_COMPILE_H */ diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 37278c5cb9a2..6ac4750c08b4 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -25,6 +25,7 @@ #include "zend.h" #include "zend_compile.h" +#include "zend_bitset.h" #include "zend_execute.h" #include "zend_API.h" #include "zend_ptr_stack.h" @@ -5897,6 +5898,32 @@ static zend_always_inline zend_execute_data *_zend_vm_stack_push_call_frame(uint /* This callback disables optimization of "vm_stack_data" variable in VM */ ZEND_API void (ZEND_FASTCALL *zend_touch_vm_stack_data)(void *vm_stack_data) = NULL; +static zend_never_inline ZEND_COLD void zend_maybe_deoptimize_func(zend_function **fbc_ptr, uint32_t *used_stack OPLINE_DC) +{ + zend_function *fbc = *fbc_ptr; + zend_bitset assumptions = fbc->op_array.global_func_assumptions; + ZEND_ASSERT(assumptions && EG(shadowed_global_funcs)); + if (!zend_bitset_has_intersection(assumptions, EG(shadowed_global_funcs), EG(shadowed_global_funcs_len))) { + return; + } + + zend_op_array *deopt = fbc->op_array.deoptimized; + if (!deopt) { + deopt = (zend_op_array *)zend_get_deoptimized_function(fbc); + if (!deopt) { + return; + } + } + + *fbc_ptr = fbc = (zend_function *)deopt; + if (UNEXPECTED(!RUN_TIME_CACHE(deopt))) { + init_func_run_time_cache(deopt); + } + if (used_stack) { + *used_stack = zend_vm_calc_used_stack(opline->extended_value, fbc); + } +} + #include "zend_vm_execute.h" ZEND_API zend_result zend_set_user_opcode_handler(zend_uchar opcode, user_opcode_handler_t handler) diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 89a9e79143a8..09d18d4020d8 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -36,6 +36,7 @@ ZEND_API extern void (*zend_execute_internal)(zend_execute_data *execute_data, z /* The lc_name may be stack allocated! */ ZEND_API extern zend_class_entry *(*zend_autoload)(zend_string *name, zend_string *lc_name); +ZEND_API extern void (*zend_store_deoptimized_op_array)(zend_op_array *optimized, zend_op_array *deoptimized); void init_executor(void); void shutdown_executor(void); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index dbd2a9039cfc..dd16a58df214 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -24,6 +24,7 @@ #include "zend.h" #include "zend_compile.h" #include "zend_execute.h" +#include "zend_bitset.h" #include "zend_API.h" #include "zend_stack.h" #include "zend_constants.h" @@ -52,6 +53,7 @@ ZEND_API void (*zend_execute_ex)(zend_execute_data *execute_data); ZEND_API void (*zend_execute_internal)(zend_execute_data *execute_data, zval *return_value); ZEND_API zend_class_entry *(*zend_autoload)(zend_string *name, zend_string *lc_name); +ZEND_API void (*zend_store_deoptimized_op_array)(zend_op_array *optimized, zend_op_array *deoptimized); #ifdef ZEND_WIN32 ZEND_TLS HANDLE tq_timer = NULL; @@ -204,6 +206,9 @@ void init_executor(void) /* {{{ */ zend_hash_init(&EG(callable_convert_cache), 8, NULL, ZVAL_PTR_DTOR, 0); + EG(shadowed_global_funcs_len) = 0; + EG(shadowed_global_funcs) = NULL; + EG(active) = 1; } /* }}} */ @@ -516,6 +521,10 @@ void shutdown_executor(void) /* {{{ */ } zend_hash_destroy(&EG(callable_convert_cache)); + + if (EG(shadowed_global_funcs)) { + efree(EG(shadowed_global_funcs)); + } } #if ZEND_DEBUG diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 31f54cd8284b..e071ff9e60bb 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -95,6 +95,8 @@ struct _zend_compiler_globals { HashTable *function_table; /* function symbol table */ HashTable *class_table; /* class table */ + uint32_t num_global_internal_funcs; /* count of internal funcs at startup, determines bitmap size */ + HashTable *auto_globals; /* Refer to zend_yytnamerr() in zend_language_parser.y for meaning of values */ @@ -321,6 +323,11 @@ struct _zend_executor_globals { HashTable callable_convert_cache; + /* Bitmap of internal functions (by arData index) that have been shadowed + * by a namespaced function. Compared against per-op_array assumption bitmaps. */ + zend_ulong *shadowed_global_funcs; + uint32_t shadowed_global_funcs_len; /* length in zend_ulong words */ + void *reserved[ZEND_MAX_RESERVED_RESOURCES]; }; diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 24b480ad71e6..124fd25c13e7 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -85,6 +85,8 @@ void init_op_array(zend_op_array *op_array, zend_function_type type, int initial op_array->fn_flags = 0; op_array->fn_flags2 = 0; + op_array->global_func_assumptions = NULL; + op_array->deoptimized = NULL; op_array->last_literal = 0; op_array->literals = NULL; @@ -661,6 +663,13 @@ ZEND_API void destroy_op_array(zend_op_array *op_array) } efree(op_array->dynamic_func_defs); } + if (op_array->global_func_assumptions) { + efree(op_array->global_func_assumptions); + } + if (op_array->deoptimized) { + destroy_op_array(op_array->deoptimized); + /* op_array->deoptimized is allocated on CG(arena), not individually emalloc'd */ + } } static void zend_update_extended_stmts(zend_op_array *op_array) @@ -1319,3 +1328,55 @@ ZEND_API binary_op_type get_binary_op(int opcode) return (binary_op_type) NULL; } } + +static void zend_foreach_op_array_helper( + zend_op_array *op_array, zend_op_array_func_t func, void *context) { + func(op_array, context); + for (uint32_t i = 0; i < op_array->num_dynamic_func_defs; i++) { + zend_foreach_op_array_helper(op_array->dynamic_func_defs[i], func, context); + } + if (op_array->deoptimized) { + zend_foreach_op_array_helper(op_array->deoptimized, func, context); + } +} + +void zend_foreach_op_array_ex(zend_op_array *main_op_array, HashTable *function_table, HashTable *class_table, zend_op_array_func_t func, void *context) +{ + if (main_op_array) { + zend_foreach_op_array_helper(main_op_array, func, context); + } + + ZEND_HASH_MAP_FOREACH_PTR(function_table, zend_op_array *op_array) { + if (op_array->type == ZEND_USER_FUNCTION) { + zend_foreach_op_array_helper(op_array, func, context); + } + } ZEND_HASH_FOREACH_END(); + + ZEND_HASH_MAP_FOREACH_VAL(class_table, zval *zv) { + if (Z_TYPE_P(zv) == IS_ALIAS_PTR) { + continue; + } + + const zend_class_entry *ce = Z_CE_P(zv); + ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, zend_op_array *op_array) { + if (op_array->scope == ce + && op_array->type == ZEND_USER_FUNCTION + && !(op_array->fn_flags & ZEND_ACC_ABSTRACT) + && !(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) { + zend_foreach_op_array_helper(op_array, func, context); + } + } ZEND_HASH_FOREACH_END(); + + zend_property_info *property; + ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, property) { + if (property->ce == ce && property->hooks) { + for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { + zend_function *hook = property->hooks[i]; + if (hook && hook->common.scope == ce && !(hook->op_array.fn_flags & ZEND_ACC_TRAIT_CLONE)) { + zend_foreach_op_array_helper(&hook->op_array, func, context); + } + } + } + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FOREACH_END(); +} diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 86708f8c97a2..a10fd1bcd98f 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3910,8 +3910,12 @@ ZEND_VM_HOT_HANDLER(59, ZEND_INIT_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); } + if (UNEXPECTED(EG(shadowed_global_funcs)) && fbc->common.fn_flags2 & ZEND_ACC2_ASSUMPTIONS) { + zend_maybe_deoptimize_func(&fbc, NULL OPLINE_CC); + } CACHE_PTR(opline->result.num, fbc); } + call = _zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, NULL); call->prev_execute_data = EX(call); @@ -4098,8 +4102,13 @@ ZEND_VM_HOT_HANDLER(61, ZEND_INIT_FCALL, NUM, CONST, NUM|CACHE_SLOT) CACHE_PTR(opline->result.num, fbc); } + uint32_t used_stack = opline->op1.num; + if (UNEXPECTED(EG(shadowed_global_funcs)) && fbc->common.fn_flags2 & ZEND_ACC2_ASSUMPTIONS) { + zend_maybe_deoptimize_func(&fbc, &used_stack OPLINE_CC); + } + call = _zend_vm_stack_push_call_frame_ex( - opline->op1.num, ZEND_CALL_NESTED_FUNCTION, + used_stack, ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, NULL); call->prev_execute_data = EX(call); EX(call) = call; @@ -4117,8 +4126,12 @@ ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_INIT_FCALL, Z_EXTRA_P(RT_CONSTANT(op, op->op2 fbc = Z_PTR(EG(function_table)->arData[Z_EXTRA_P(RT_CONSTANT(opline, opline->op2))].val); CACHE_PTR(opline->result.num, fbc); } + uint32_t used_stack = opline->op1.num; + if (UNEXPECTED(EG(shadowed_global_funcs)) && fbc->common.fn_flags2 & ZEND_ACC2_ASSUMPTIONS) { + zend_maybe_deoptimize_func(&fbc, &used_stack OPLINE_CC); + } call = _zend_vm_stack_push_call_frame_ex( - opline->op1.num, ZEND_CALL_NESTED_FUNCTION, + used_stack, ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, NULL); call->prev_execute_data = EX(call); EX(call) = call; diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index cbfae90802cf..94ec00bdacd1 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -4025,8 +4025,12 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); } + if (UNEXPECTED(EG(shadowed_global_funcs)) && fbc->common.fn_flags2 & ZEND_ACC2_ASSUMPTIONS) { + zend_maybe_deoptimize_func(&fbc, NULL OPLINE_CC); + } CACHE_PTR(opline->result.num, fbc); } + call = _zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, NULL); call->prev_execute_data = EX(call); @@ -4142,8 +4146,13 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I CACHE_PTR(opline->result.num, fbc); } + uint32_t used_stack = opline->op1.num; + if (UNEXPECTED(EG(shadowed_global_funcs)) && fbc->common.fn_flags2 & ZEND_ACC2_ASSUMPTIONS) { + zend_maybe_deoptimize_func(&fbc, &used_stack OPLINE_CC); + } + call = _zend_vm_stack_push_call_frame_ex( - opline->op1.num, ZEND_CALL_NESTED_FUNCTION, + used_stack, ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, NULL); call->prev_execute_data = EX(call); EX(call) = call; @@ -4161,8 +4170,12 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_I fbc = Z_PTR(EG(function_table)->arData[Z_EXTRA_P(RT_CONSTANT(opline, opline->op2))].val); CACHE_PTR(opline->result.num, fbc); } + uint32_t used_stack = opline->op1.num; + if (UNEXPECTED(EG(shadowed_global_funcs)) && fbc->common.fn_flags2 & ZEND_ACC2_ASSUMPTIONS) { + zend_maybe_deoptimize_func(&fbc, &used_stack OPLINE_CC); + } call = _zend_vm_stack_push_call_frame_ex( - opline->op1.num, ZEND_CALL_NESTED_FUNCTION, + used_stack, ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, NULL); call->prev_execute_data = EX(call); EX(call) = call; @@ -56629,8 +56642,12 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_F if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); } + if (UNEXPECTED(EG(shadowed_global_funcs)) && fbc->common.fn_flags2 & ZEND_ACC2_ASSUMPTIONS) { + zend_maybe_deoptimize_func(&fbc, NULL OPLINE_CC); + } CACHE_PTR(opline->result.num, fbc); } + call = _zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, NULL); call->prev_execute_data = EX(call); @@ -56746,8 +56763,13 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_F CACHE_PTR(opline->result.num, fbc); } + uint32_t used_stack = opline->op1.num; + if (UNEXPECTED(EG(shadowed_global_funcs)) && fbc->common.fn_flags2 & ZEND_ACC2_ASSUMPTIONS) { + zend_maybe_deoptimize_func(&fbc, &used_stack OPLINE_CC); + } + call = _zend_vm_stack_push_call_frame_ex( - opline->op1.num, ZEND_CALL_NESTED_FUNCTION, + used_stack, ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, NULL); call->prev_execute_data = EX(call); EX(call) = call; @@ -56765,8 +56787,12 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_F fbc = Z_PTR(EG(function_table)->arData[Z_EXTRA_P(RT_CONSTANT(opline, opline->op2))].val); CACHE_PTR(opline->result.num, fbc); } + uint32_t used_stack = opline->op1.num; + if (UNEXPECTED(EG(shadowed_global_funcs)) && fbc->common.fn_flags2 & ZEND_ACC2_ASSUMPTIONS) { + zend_maybe_deoptimize_func(&fbc, &used_stack OPLINE_CC); + } call = _zend_vm_stack_push_call_frame_ex( - opline->op1.num, ZEND_CALL_NESTED_FUNCTION, + used_stack, ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, NULL); call->prev_execute_data = EX(call); EX(call) = call; diff --git a/benchmark/benchmark.php b/benchmark/benchmark.php index 0c2ac4c6010a..12a12ff76121 100644 --- a/benchmark/benchmark.php +++ b/benchmark/benchmark.php @@ -31,8 +31,8 @@ function main() { $data['Zend/bench.php JIT'] = runBench(true); $data['Symfony Demo 2.2.3'] = runSymfonyDemo(false); $data['Symfony Demo 2.2.3 JIT'] = runSymfonyDemo(true); - $data['Wordpress 6.2'] = runWordpress(false); - $data['Wordpress 6.2 JIT'] = runWordpress(true); + // $data['Wordpress 6.2'] = runWordpress(false); + // $data['Wordpress 6.2 JIT'] = runWordpress(true); $result = json_encode($data, JSON_PRETTY_PRINT) . "\n"; fwrite(STDOUT, $result); diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index f4134212bc4e..7f7ed124968b 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -1611,25 +1611,27 @@ static zend_persistent_script *cache_script_in_shared_memory(zend_persistent_scr /* Check if we still need to put the file into the cache (may be it was * already stored by another process. This final check is done under * exclusive lock) */ - bucket = zend_accel_hash_find_entry(&ZCSG(hash), new_persistent_script->script.filename); - if (bucket) { - zend_persistent_script *existing_persistent_script = (zend_persistent_script *)bucket->data; - - if (!existing_persistent_script->corrupted) { - if (key && - (!ZCG(accel_directives).validate_timestamps || - (new_persistent_script->timestamp == existing_persistent_script->timestamp))) { - zend_accel_add_key(key, bucket); - } - zend_shared_alloc_unlock(); + if (!(CG(compiler_options) & ZEND_COMPILE_DEOPTIMIZED)) { + bucket = zend_accel_hash_find_entry(&ZCSG(hash), new_persistent_script->script.filename); + if (bucket) { + zend_persistent_script *existing_persistent_script = (zend_persistent_script *)bucket->data; + + if (!existing_persistent_script->corrupted) { + if (key && + (!ZCG(accel_directives).validate_timestamps || + (new_persistent_script->timestamp == existing_persistent_script->timestamp))) { + zend_accel_add_key(key, bucket); + } + zend_shared_alloc_unlock(); #if 1 - /* prefer the script already stored in SHM */ - free_persistent_script(new_persistent_script, 1); - *from_shared_memory = true; - return existing_persistent_script; + /* prefer the script already stored in SHM */ + free_persistent_script(new_persistent_script, 1); + *from_shared_memory = true; + return existing_persistent_script; #else - return new_persistent_script; + return new_persistent_script; #endif + } } } @@ -1686,26 +1688,28 @@ static zend_persistent_script *cache_script_in_shared_memory(zend_persistent_scr } /* store script structure in the hash table */ - bucket = zend_accel_hash_update(&ZCSG(hash), new_persistent_script->script.filename, 0, new_persistent_script); - if (bucket) { - zend_accel_error(ACCEL_LOG_INFO, "Cached script '%s'", ZSTR_VAL(new_persistent_script->script.filename)); - if (key && - /* key may contain non-persistent PHAR aliases (see issues #115 and #149) */ - !zend_string_starts_with_literal(key, "phar://") && - !zend_string_equals(new_persistent_script->script.filename, key)) { - /* link key to the same persistent script in hash table */ - zend_string *new_key = accel_new_interned_key(key); - - if (new_key) { - if (zend_accel_hash_update(&ZCSG(hash), new_key, 1, bucket)) { - zend_accel_error(ACCEL_LOG_INFO, "Added key '%s'", ZSTR_VAL(key)); + if (!(CG(compiler_options) & ZEND_COMPILE_DEOPTIMIZED)) { + bucket = zend_accel_hash_update(&ZCSG(hash), new_persistent_script->script.filename, 0, new_persistent_script); + if (bucket) { + zend_accel_error(ACCEL_LOG_INFO, "Cached script '%s'", ZSTR_VAL(new_persistent_script->script.filename)); + if (key && + /* key may contain non-persistent PHAR aliases (see issues #115 and #149) */ + !zend_string_starts_with_literal(key, "phar://") && + !zend_string_equals(new_persistent_script->script.filename, key)) { + /* link key to the same persistent script in hash table */ + zend_string *new_key = accel_new_interned_key(key); + + if (new_key) { + if (zend_accel_hash_update(&ZCSG(hash), new_key, 1, bucket)) { + zend_accel_error(ACCEL_LOG_INFO, "Added key '%s'", ZSTR_VAL(key)); + } else { + zend_accel_error(ACCEL_LOG_DEBUG, "No more entries in hash table!"); + ZSMMG(memory_exhausted) = true; + zend_accel_schedule_restart_if_necessary(ACCEL_RESTART_HASH); + } } else { - zend_accel_error(ACCEL_LOG_DEBUG, "No more entries in hash table!"); - ZSMMG(memory_exhausted) = true; - zend_accel_schedule_restart_if_necessary(ACCEL_RESTART_HASH); + zend_accel_schedule_restart_if_necessary(ACCEL_RESTART_OOM); } - } else { - zend_accel_schedule_restart_if_necessary(ACCEL_RESTART_OOM); } } } @@ -1837,6 +1841,7 @@ static zend_persistent_script *opcache_compile_file(zend_file_handle *file_handl CG(compiler_options) |= ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION; CG(compiler_options) |= ZEND_COMPILE_IGNORE_OTHER_FILES; CG(compiler_options) |= ZEND_COMPILE_IGNORE_OBSERVER; + CG(compiler_options) |= (orig_compiler_options & ZEND_COMPILE_DEOPTIMIZED); #ifdef ZEND_WIN32 /* On Windows, don't compile with internal classes. Shm may be attached from different * processes with internal classes living in different addresses. */ @@ -2021,6 +2026,12 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) zend_string *key = NULL; bool from_shared_memory; /* if the script we've got is stored in SHM */ + if (CG(compiler_options) & ZEND_COMPILE_DEOPTIMIZED) { + HANDLE_BLOCK_INTERRUPTIONS(); + SHM_UNPROTECT(); + goto skip_caching; + } + if (!file_handle->filename || !ZCG(accelerator_enabled)) { /* The Accelerator is disabled, act as if without the Accelerator */ ZCG(cache_opline) = NULL; @@ -2169,6 +2180,8 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) /* If script was not found or invalidated by validate_timestamps */ if (!persistent_script) { + // FIXME: Skip miss counter? +skip_caching:; uint32_t old_const_num = zend_hash_next_free_element(EG(zend_constants)); zend_op_array *op_array; @@ -3249,6 +3262,13 @@ static int accel_startup(zend_extension *extension) return SUCCESS; } +static void store_deoptimized_op_array(zend_op_array *optimized, zend_op_array *deoptimized) +{ + SHM_UNPROTECT(); + optimized->deoptimized = deoptimized; + SHM_PROTECT(); +} + static zend_result accel_post_startup(void) { zend_function *func; @@ -3424,6 +3444,7 @@ static zend_result accel_post_startup(void) /* Override compiler */ accelerator_orig_compile_file = zend_compile_file; zend_compile_file = persistent_compile_file; + zend_store_deoptimized_op_array = store_deoptimized_op_array; /* Override stream opener function (to eliminate open() call caused by * include/require statements ) */ diff --git a/ext/opcache/tests/ns_global_func_assumption_001.inc b/ext/opcache/tests/ns_global_func_assumption_001.inc new file mode 100644 index 000000000000..a0d5b43d665b --- /dev/null +++ b/ext/opcache/tests/ns_global_func_assumption_001.inc @@ -0,0 +1,7 @@ + +--EXPECTF-- +$_main: + ; (lines=6, args=0, vars=0, tmps=%d) + ; (after optimizer) + ; %sns_global_func_assumption_001.php:%s +0000 INIT_FCALL 0 %d string("ns\\test") +0001 DO_UCALL +0002 INCLUDE_OR_EVAL (require) string("%sns_global_func_assumption_001.inc") +0003 INIT_FCALL 0 %d string("ns\\test") +0004 DO_UCALL +0005 RETURN int(1) + +Ns\test: + ; (lines=4, args=0, vars=0, tmps=%d) + ; (after optimizer) + ; %sns_global_func_assumption_001.php:%s +0000 INIT_FCALL 1 %d string("var_dump") +0001 SEND_VAL int(11) 1 +0002 DO_ICALL +0003 RETURN null +int(11) + +$_main: + ; (lines=1, args=0, vars=0, tmps=%d) + ; (after optimizer) + ; %sns_global_func_assumption_001.inc:%s +0000 RETURN int(1) + +Ns\strlen: + ; (lines=4, args=1, vars=1, tmps=%d) + ; (after optimizer) + ; %sns_global_func_assumption_001.inc:%s +0000 CV0($string) = RECV 1 +0001 T2 = STRLEN CV0($string) +0002 T1 = ADD T2 T2 +0003 RETURN T1 + +$_main: + ; (lines=6, args=0, vars=0, tmps=%d) + ; (after optimizer) + ; %sns_global_func_assumption_001.php:%s +0000 INIT_FCALL 0 %d string("ns\\test") +0001 DO_UCALL +0002 INCLUDE_OR_EVAL (require) string("%sns_global_func_assumption_001.inc") +0003 INIT_FCALL 0 %d string("ns\\test") +0004 DO_UCALL +0005 RETURN int(1) + +Ns\test: + ; (lines=7, args=0, vars=0, tmps=%d) + ; (after optimizer) + ; %sns_global_func_assumption_001.php:%s +0000 INIT_NS_FCALL_BY_NAME 1 string("Ns\\var_dump") +0001 INIT_NS_FCALL_BY_NAME 1 string("Ns\\strlen") +0002 SEND_VAL_EX string("Hello world") 1 +0003 V0 = DO_FCALL_BY_NAME +0004 SEND_VAR_NO_REF_EX V0 1 +0005 DO_FCALL_BY_NAME +0006 RETURN null +int(22) diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 9bc2496837ce..a1eda807d4f5 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -29,6 +29,7 @@ #include "zend_operators.h" #include "zend_interfaces.h" #include "zend_attributes.h" +#include "zend_bitset.h" #ifdef HAVE_JIT # include "Optimizer/zend_func_info.h" @@ -699,6 +700,13 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc } } + if (op_array->global_func_assumptions) { + op_array->global_func_assumptions = zend_shared_memdup_put_free(op_array->global_func_assumptions, sizeof(zend_ulong) * zend_bitset_len(CG(num_global_internal_funcs))); + } + + /* Function has never been run, deoptimized must still be NULL. */ + ZEND_ASSERT(!op_array->deoptimized); + ZCG(mem) = (void*)((char*)ZCG(mem) + ZEND_ALIGNED_SIZE(zend_extensions_op_array_persist(op_array, ZCG(mem)))); } diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 0b0ff51d0d4d..2b434a2ddc92 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -27,6 +27,7 @@ #include "zend_operators.h" #include "zend_attributes.h" #include "zend_constants.h" +#include "zend_bitset.h" #define ADD_DUP_SIZE(m,s) ZCG(current_persistent_script)->size += zend_shared_memdup_size((void*)m, s) #define ADD_SIZE(m) ZCG(current_persistent_script)->size += ZEND_ALIGNED_SIZE(m) @@ -343,6 +344,13 @@ static void zend_persist_op_array_calc_ex(zend_op_array *op_array) } } + if (op_array->global_func_assumptions) { + ADD_SIZE(sizeof(zend_ulong) * zend_bitset_len(CG(num_global_internal_funcs))); + } + + /* Function has never been run, deoptimized must still be NULL. */ + ZEND_ASSERT(!op_array->deoptimized); + ADD_SIZE(ZEND_ALIGNED_SIZE(zend_extensions_op_array_persist_calc(op_array))); } diff --git a/main/main.c b/main/main.c index bbd2d18ba187..3c4969590a12 100644 --- a/main/main.c +++ b/main/main.c @@ -2373,6 +2373,9 @@ zend_result php_module_startup(sapi_module_struct *sf, zend_module_entry *additi } } + /* Record the number of internal functions for deoptimization bitmap sizing. */ + CG(num_global_internal_funcs) = CG(function_table)->nNumUsed; + /* disable certain functions as requested by php.ini */ zend_disable_functions(INI_STR("disable_functions")); diff --git a/sapi/phpdbg/tests/print_001.phpt b/sapi/phpdbg/tests/print_001.phpt index 031b4d5a961b..6bab58a58ea4 100644 --- a/sapi/phpdbg/tests/print_001.phpt +++ b/sapi/phpdbg/tests/print_001.phpt @@ -29,9 +29,9 @@ Foo\Bar::Foo: ; (lines=5, args=1, vars=1, tmps=%d) ; %s:5-7 L0005 0000 CV0($bar) = RECV 1 -L0006 0001 INIT_NS_FCALL_BY_NAME 1 string("Foo\\var_dump") -L0006 0002 SEND_VAR_EX CV0($bar) 1 -L0006 0003 DO_FCALL +L0006 0001 INIT_FCALL 1 %d string("var_dump") +L0006 0002 SEND_VAR CV0($bar) 1 +L0006 0003 DO_ICALL L0007 0004 RETURN null Foo\Bar::baz: