diff --git a/NEWS b/NEWS index 145a1d9a5cb56..c819c21872475 100644 --- a/NEWS +++ b/NEWS @@ -27,6 +27,7 @@ PHP NEWS - Opcache: . Fixed bug GH-15657 (Segmentation fault in dasm_x86.h). (nielsdos) + . Added opcache_jit_blacklist() function. (Bob) - PHPDBG: . Fixed bug GH-15901 (phpdbg: Assertion failure on i funcs). (cmb) diff --git a/UPGRADING b/UPGRADING index c046257074cd4..36ee66c1e085d 100644 --- a/UPGRADING +++ b/UPGRADING @@ -815,6 +815,10 @@ PHP 8.4 UPGRADE NOTES . Added mb_ucfirst and mb_lcfirst functions. RFC: https://wiki.php.net/rfc/mb_ucfirst +- OPCache: + . Added opcache_jit_blacklist function. It allows skipping the tracing JIT + execution of select functions. + - PCNTL: . Added pcntl_setns allowing a process to be reassociated with a namespace in order to share resources with other processes within this context. diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index e247b35f953d2..5657764926706 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -706,7 +706,7 @@ static bool zend_may_be_dynamic_property(zend_class_entry *ce, zend_string *memb # endif #endif -void zend_jit_status(zval *ret) +ZEND_EXT_API void zend_jit_status(zval *ret) { zval stats; array_init(&stats); diff --git a/ext/opcache/jit/zend_jit.h b/ext/opcache/jit/zend_jit.h index 1e0176a4f4f55..0ce6c1a4409a2 100644 --- a/ext/opcache/jit/zend_jit.h +++ b/ext/opcache/jit/zend_jit.h @@ -162,7 +162,8 @@ void zend_jit_startup(void *jit_buffer, size_t size, bool reattached); void zend_jit_shutdown(void); void zend_jit_activate(void); void zend_jit_deactivate(void); -void zend_jit_status(zval *ret); +ZEND_EXT_API void zend_jit_status(zval *ret); +ZEND_EXT_API void zend_jit_blacklist_function(zend_op_array *op_array); void zend_jit_restart(void); #define ZREG_LOAD (1<<0) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 133aa49cd2f56..38ee5c409c322 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -10102,6 +10102,20 @@ static int zend_jit_do_fcall(zend_jit_ctx *jit, const zend_op *opline, const zen ir_STORE(jit_EX(opline), jit_IP(jit)); } jit_observer_fcall_begin(jit, rx, observer_handler); + + if (trace) { + int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM); + + exit_addr = zend_jit_trace_get_exit_addr(exit_point); + if (!exit_addr) { + return 0; + } + } else { + exit_addr = NULL; + } + + zend_jit_check_timeout(jit, NULL /* we're inside the called function */, exit_addr); + jit_observer_fcall_is_unobserved_end(jit, &unobserved_data); } diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 82f360992607e..379eb96174025 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -7656,6 +7656,24 @@ static void zend_jit_blacklist_root_trace(const zend_op *opline, size_t offset) zend_shared_alloc_unlock(); } +ZEND_EXT_API void zend_jit_blacklist_function(zend_op_array *op_array) { + zend_jit_op_array_trace_extension *jit_extension = (zend_jit_op_array_trace_extension *)ZEND_FUNC_INFO(op_array); + if (!jit_extension || !(jit_extension->func_info.flags & ZEND_FUNC_JIT_ON_HOT_TRACE)) { + return; + } + + zend_shared_alloc_lock(); + SHM_UNPROTECT(); + zend_jit_unprotect(); + + zend_jit_stop_persistent_op_array(op_array); + jit_extension->func_info.flags &= ~ZEND_FUNC_JIT_ON_HOT_TRACE; + + zend_jit_protect(); + SHM_PROTECT(); + zend_shared_alloc_unlock(); +} + static bool zend_jit_trace_is_bad_root(const zend_op *opline, zend_jit_trace_stop stop, size_t offset) { const zend_op **cache_opline = JIT_G(bad_root_cache_opline); diff --git a/ext/opcache/jit/zend_jit_vm_helpers.c b/ext/opcache/jit/zend_jit_vm_helpers.c index d93e5fce94780..2eeb43a4f754f 100644 --- a/ext/opcache/jit/zend_jit_vm_helpers.c +++ b/ext/opcache/jit/zend_jit_vm_helpers.c @@ -521,16 +521,17 @@ static int zend_jit_trace_record_fake_init_call_ex(zend_execute_data *call, zend && (func->op_array.fn_flags & (ZEND_ACC_CLOSURE|ZEND_ACC_FAKE_CLOSURE))) { return -1; } - if (func->type == ZEND_USER_FUNCTION - && (func->op_array.fn_flags & ZEND_ACC_CLOSURE)) { + if (func->type == ZEND_USER_FUNCTION) { jit_extension = (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(&func->op_array); - if (UNEXPECTED(!jit_extension - || !(jit_extension->func_info.flags & ZEND_FUNC_JIT_ON_HOT_TRACE) - || (func->op_array.fn_flags & ZEND_ACC_FAKE_CLOSURE))) { + if (UNEXPECTED(!jit_extension && (func->op_array.fn_flags & ZEND_ACC_CLOSURE)) + || (jit_extension && !(jit_extension->func_info.flags & ZEND_FUNC_JIT_ON_HOT_TRACE)) + || (func->op_array.fn_flags & ZEND_ACC_FAKE_CLOSURE)) { return -1; } - func = (zend_function*)jit_extension->op_array; + if (func->op_array.fn_flags & ZEND_ACC_CLOSURE) { + func = (zend_function*)jit_extension->op_array; + } } if (is_megamorphic == ZEND_JIT_EXIT_POLYMORPHISM /* TODO: use more accurate check ??? */ @@ -1100,17 +1101,18 @@ zend_jit_trace_stop ZEND_FASTCALL zend_jit_trace_execute(zend_execute_data *ex, stop = ZEND_JIT_TRACE_STOP_BAD_FUNC; break; } - if (func->type == ZEND_USER_FUNCTION - && (func->op_array.fn_flags & ZEND_ACC_CLOSURE)) { + if (func->type == ZEND_USER_FUNCTION) { jit_extension = (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(&func->op_array); - if (UNEXPECTED(!jit_extension) - || !(jit_extension->func_info.flags & ZEND_FUNC_JIT_ON_HOT_TRACE) + if (UNEXPECTED(!jit_extension && (func->op_array.fn_flags & ZEND_ACC_CLOSURE)) + || (jit_extension && !(jit_extension->func_info.flags & ZEND_FUNC_JIT_ON_HOT_TRACE)) || (func->op_array.fn_flags & ZEND_ACC_FAKE_CLOSURE)) { stop = ZEND_JIT_TRACE_STOP_INTERPRETER; break; } - func = (zend_function*)jit_extension->op_array; + if (func->op_array.fn_flags & ZEND_ACC_CLOSURE) { + func = (zend_function*)jit_extension->op_array; + } } #ifndef HAVE_GCC_GLOBAL_REGS diff --git a/ext/opcache/opcache.stub.php b/ext/opcache/opcache.stub.php index 4eeb76f083bc5..526da238219a4 100644 --- a/ext/opcache/opcache.stub.php +++ b/ext/opcache/opcache.stub.php @@ -14,6 +14,8 @@ function opcache_compile_file(string $filename): bool {} function opcache_invalidate(string $filename, bool $force = false): bool {} +function opcache_jit_blacklist(Closure $closure): void {} + /** * @return array|false * @refcount 1 diff --git a/ext/opcache/opcache_arginfo.h b/ext/opcache/opcache_arginfo.h index b3e893fead62e..b4dc1f33a5fd8 100644 --- a/ext/opcache/opcache_arginfo.h +++ b/ext/opcache/opcache_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 81f337ea4ac5361ca4a0873fcd3b033beaf524c6 */ + * Stub hash: c416c231c5d1270b7e5961f84cc3ca3e29db4959 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_opcache_reset, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() @@ -17,6 +17,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_opcache_invalidate, 0, 1, _IS_BO ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, force, _IS_BOOL, 0, "false") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_opcache_jit_blacklist, 0, 1, IS_VOID, 0) + ZEND_ARG_OBJ_INFO(0, closure, Closure, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_opcache_get_configuration, 0, 0, MAY_BE_ARRAY|MAY_BE_FALSE) ZEND_END_ARG_INFO() @@ -26,6 +30,7 @@ ZEND_FUNCTION(opcache_reset); ZEND_FUNCTION(opcache_get_status); ZEND_FUNCTION(opcache_compile_file); ZEND_FUNCTION(opcache_invalidate); +ZEND_FUNCTION(opcache_jit_blacklist); ZEND_FUNCTION(opcache_get_configuration); ZEND_FUNCTION(opcache_is_script_cached); @@ -34,6 +39,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(opcache_get_status, arginfo_opcache_get_status) ZEND_FE(opcache_compile_file, arginfo_opcache_compile_file) ZEND_FE(opcache_invalidate, arginfo_opcache_invalidate) + ZEND_FE(opcache_jit_blacklist, arginfo_opcache_jit_blacklist) ZEND_FE(opcache_get_configuration, arginfo_opcache_get_configuration) ZEND_FE(opcache_is_script_cached, arginfo_opcache_is_script_cached) ZEND_FE_END diff --git a/ext/opcache/tests/jit/opcache_jit_blacklist.phpt b/ext/opcache/tests/jit/opcache_jit_blacklist.phpt new file mode 100644 index 0000000000000..33db720967555 --- /dev/null +++ b/ext/opcache/tests/jit/opcache_jit_blacklist.phpt @@ -0,0 +1,23 @@ +--TEST-- +Basic usage of opcache_jit_blacklist() +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.protect_memory=1 +opcache.jit=tracing +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECT-- +int(2) diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index cf1e86bb52442..359ba9418e289 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -24,6 +24,7 @@ #include "php.h" #include "ZendAccelerator.h" #include "zend_API.h" +#include "zend_closures.h" #include "zend_shared_alloc.h" #include "zend_accelerator_blacklist.h" #include "php_ini.h" @@ -924,6 +925,21 @@ ZEND_FUNCTION(opcache_invalidate) } } +/* {{{ Prevents JIT on function. Call it before the first invocation of the given function. */ +ZEND_FUNCTION(opcache_jit_blacklist) +{ + zval *closure; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &closure, zend_ce_closure) == FAILURE) { + RETURN_THROWS(); + } + + const zend_function *func = zend_get_closure_method_def(Z_OBJ_P(closure)); + if (ZEND_USER_CODE(func->type)) { + zend_jit_blacklist_function((zend_op_array *)&func->op_array); + } +} + ZEND_FUNCTION(opcache_compile_file) { zend_string *script_name;