From 0c3b075f4ecef71a50a77313325e257a66d73cde Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Tue, 28 Apr 2026 17:39:01 -0600 Subject: [PATCH 1/4] Add observer VM interrupt JIT regression test --- ext/zend_test/observer.c | 23 +++++++++ ext/zend_test/php_test.h | 1 + .../tests/observer_jit_vm_interrupt.inc | 8 ++++ .../tests/observer_jit_vm_interrupt.phpt | 48 +++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 ext/zend_test/tests/observer_jit_vm_interrupt.inc create mode 100644 ext/zend_test/tests/observer_jit_vm_interrupt.phpt diff --git a/ext/zend_test/observer.c b/ext/zend_test/observer.c index 31052ec830f7..0dfb62723bc4 100644 --- a/ext/zend_test/observer.c +++ b/ext/zend_test/observer.c @@ -78,6 +78,10 @@ static void observer_begin(zend_execute_data *execute_data) { assert_observer_opline(execute_data); + if (ZT_G(observer_set_vm_interrupt_on_begin)) { + zend_atomic_bool_store_ex(&EG(vm_interrupt), true); + } + if (!ZT_G(observer_show_output)) { return; } @@ -146,6 +150,14 @@ static void observer_end(zend_execute_data *execute_data, zval *retval) } } +static void (*zend_test_prev_interrupt_function)(zend_execute_data *execute_data); +static void zend_test_interrupt_function(zend_execute_data *execute_data) +{ + if (zend_test_prev_interrupt_function) { + zend_test_prev_interrupt_function(execute_data); + } +} + static void observer_show_init(zend_function *fbc) { if (fbc->common.function_name) { @@ -361,6 +373,7 @@ PHP_INI_BEGIN() STD_PHP_INI_BOOLEAN("zend_test.observer.show_init_backtrace", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_show_init_backtrace, zend_zend_test_globals, zend_test_globals) STD_PHP_INI_BOOLEAN("zend_test.observer.show_opcode", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_show_opcode, zend_zend_test_globals, zend_test_globals) STD_PHP_INI_ENTRY("zend_test.observer.show_opcode_in_user_handler", "", PHP_INI_SYSTEM, OnUpdateString, observer_show_opcode_in_user_handler, zend_zend_test_globals, zend_test_globals) + STD_PHP_INI_BOOLEAN("zend_test.observer.set_vm_interrupt_on_begin", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_set_vm_interrupt_on_begin, zend_zend_test_globals, zend_test_globals) STD_PHP_INI_BOOLEAN("zend_test.observer.fiber_init", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_fiber_init, zend_zend_test_globals, zend_test_globals) STD_PHP_INI_BOOLEAN("zend_test.observer.fiber_switch", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_fiber_switch, zend_zend_test_globals, zend_test_globals) STD_PHP_INI_BOOLEAN("zend_test.observer.fiber_destroy", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_fiber_destroy, zend_zend_test_globals, zend_test_globals) @@ -398,10 +411,20 @@ void zend_test_observer_init(INIT_FUNC_ARGS) zend_test_prev_execute_internal = zend_execute_internal; zend_execute_internal = zend_test_execute_internal; } + + if (ZT_G(observer_set_vm_interrupt_on_begin)) { + zend_test_prev_interrupt_function = zend_interrupt_function; + zend_interrupt_function = zend_test_interrupt_function; + } } void zend_test_observer_shutdown(SHUTDOWN_FUNC_ARGS) { + if (zend_interrupt_function == zend_test_interrupt_function) { + zend_interrupt_function = zend_test_prev_interrupt_function; + zend_test_prev_interrupt_function = NULL; + } + if (type != MODULE_TEMPORARY) { UNREGISTER_INI_ENTRIES(); } diff --git a/ext/zend_test/php_test.h b/ext/zend_test/php_test.h index 7ec6f5431234..c1310db7bd70 100644 --- a/ext/zend_test/php_test.h +++ b/ext/zend_test/php_test.h @@ -45,6 +45,7 @@ ZEND_BEGIN_MODULE_GLOBALS(zend_test) int observer_show_init_backtrace; int observer_show_opcode; char *observer_show_opcode_in_user_handler; + int observer_set_vm_interrupt_on_begin; int observer_nesting_depth; int observer_fiber_init; int observer_fiber_switch; diff --git a/ext/zend_test/tests/observer_jit_vm_interrupt.inc b/ext/zend_test/tests/observer_jit_vm_interrupt.inc new file mode 100644 index 000000000000..426d9fdc2cb2 --- /dev/null +++ b/ext/zend_test/tests/observer_jit_vm_interrupt.inc @@ -0,0 +1,8 @@ + +--FILE-- + +--EXPECT-- +total=2438400 From 45fbe8a35f1cc3fc93f817c00b887b43080efd7a Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Tue, 28 Apr 2026 17:46:03 -0600 Subject: [PATCH 2/4] Document reason for external fn definition --- ext/zend_test/tests/observer_jit_vm_interrupt.phpt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/zend_test/tests/observer_jit_vm_interrupt.phpt b/ext/zend_test/tests/observer_jit_vm_interrupt.phpt index 0044018cec82..4c9b21c9e9a2 100644 --- a/ext/zend_test/tests/observer_jit_vm_interrupt.phpt +++ b/ext/zend_test/tests/observer_jit_vm_interrupt.phpt @@ -26,6 +26,8 @@ if (ini_get('opcache.jit') === false) die('skip JIT not available'); Date: Tue, 28 Apr 2026 22:01:56 -0600 Subject: [PATCH 3/4] Fix observer VM interrupt during tracing JIT calls --- ext/opcache/jit/zend_jit_ir.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 4251d6b891c9..1346d141754f 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -10337,28 +10337,19 @@ static int zend_jit_do_fcall(zend_jit_ctx *jit, const zend_op *opline, const zen if (ZEND_OBSERVER_ENABLED && (!func || (func->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE | ZEND_ACC_GENERATOR)) == 0)) { ir_ref observer_handler; ir_ref rx = jit_FP(jit); + const zend_op *observer_opline = NULL; struct jit_observer_fcall_is_unobserved_data unobserved_data = jit_observer_fcall_is_unobserved_start(jit, func, &observer_handler, rx, func_ref); if (trace && (trace->op != ZEND_JIT_TRACE_END || trace->stop != ZEND_JIT_TRACE_STOP_INTERPRETER)) { ZEND_ASSERT(trace[1].op == ZEND_JIT_TRACE_VM || trace[1].op == ZEND_JIT_TRACE_END); - jit_SET_EX_OPLINE(jit, trace[1].opline); + observer_opline = trace[1].opline; + jit_SET_EX_OPLINE(jit, observer_opline); } else if (GCC_GLOBAL_REGS) { // EX(opline) = opline 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); + zend_jit_check_timeout(jit, observer_opline, NULL); jit_observer_fcall_is_unobserved_end(jit, &unobserved_data); } From 4e778d64da2482b30ddf1c63ffd8a437450f04bd Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Thu, 30 Apr 2026 10:39:37 -0600 Subject: [PATCH 4/4] Add bugfix to NEWS --- NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS b/NEWS index ef713ddd7255..a6fe8e2136a0 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.4.22 +- Opcache: + . Fixed tracing JIT crash when a VM interrupt is handled during an observed + user function call. (Levi Morrison) + - Standard: . Fixed bug GH-21689 (version_compare() incorrectly handles versions ending with a dot). (timwolla)