Commit 896f76d2c72 for php
commit 896f76d2c72e4aa215dd55d40aef2be55149f376
Author: Jorg Sowa <jorg.sowa@gmail.com>
Date: Sat Oct 31 15:59:47 2026 +0100
Fix preloaded functions not JIT-compiled again after OPcache restart
zend_jit_restart_hot_counters() restored the hot counters but kept
ZEND_FUNC_JITED, so zend_jit_hot_func() never compiled the function again.
With opcache.jit_hot_func=1 this loops forever.
Closes GH-25043
diff --git a/NEWS b/NEWS
index ab9d9476c5e..030ea531fd3 100644
--- a/NEWS
+++ b/NEWS
@@ -202,6 +202,8 @@ PHP NEWS
(Pavel Gushcha)
. Fixed potential infinite recursion/hang (HYBRID/TAILCALL respectively)
when zend_interrupt_function resets EG(vm_interrupt). (Levi Morrison)
+ . Fixed preloaded functions not JIT-compiled again after OPcache restart.
+ (Jorg Sowa)
- OpenSSL:
. Fixed bug GH-24784 (openssl_pkcs7_read() and openssl_cms_read() overwrite
diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c
index 30169921173..1185ed77e86 100644
--- a/ext/opcache/jit/zend_jit.c
+++ b/ext/opcache/jit/zend_jit.c
@@ -3124,6 +3124,8 @@ static int zend_jit_restart_hot_counters(zend_op_array *op_array)
uint32_t i;
jit_extension = (zend_jit_op_array_hot_extension*)ZEND_FUNC_INFO(op_array);
+ /* The JIT-ed code is gone, so allow compiling it again */
+ jit_extension->func_info.flags &= ~ZEND_FUNC_JITED;
for (i = 0; i < op_array->last; i++) {
op_array->opcodes[i].handler = jit_extension->orig_handlers[i];
}
diff --git a/ext/opcache/tests/jit/preload_restart_hot_counters.phpt b/ext/opcache/tests/jit/preload_restart_hot_counters.phpt
new file mode 100644
index 00000000000..8f6af56b167
--- /dev/null
+++ b/ext/opcache/tests/jit/preload_restart_hot_counters.phpt
@@ -0,0 +1,76 @@
+--TEST--
+Preloaded functions JIT-compiled by hot counters must be JIT-compiled again after an OPcache restart
+--EXTENSIONS--
+opcache
+--SKIPIF--
+<?php
+if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows');
+if (ini_get('opcache.jit') === false) die('skip PHP is compiled without JIT');
+?>
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.jit=1235
+opcache.jit_buffer_size=16M
+--CONFLICTS--
+server
+--FILE--
+<?php
+$dir = __DIR__ . '/preload_restart_hot_counters';
+@mkdir($dir);
+
+file_put_contents($dir . '/preload.php', <<<'PHP'
+<?php
+function preloaded_sum(int $n): int {
+ $s = 0;
+ for ($i = 0; $i < $n; $i++) {
+ $s += ($i * 3) ^ ($i >> 2);
+ }
+ return $s;
+}
+PHP);
+
+/* A dynamic call keeps the function out of the call graph of the calling script */
+file_put_contents($dir . '/call.php', <<<'PHP'
+<?php
+$f = 'preloaded_sum';
+echo $f(1000), "\n";
+PHP);
+
+file_put_contents($dir . '/reset.php', <<<'PHP'
+<?php
+var_dump(opcache_get_status()['jit']['on']);
+var_dump(opcache_reset());
+PHP);
+
+include __DIR__ . '/../php_cli_server.inc';
+
+$ini = '-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.jit=1235 -d opcache.jit_buffer_size=16M'
+ . ' -d opcache.jit_hot_func=1 -d opcache.validate_timestamps=0 -d opcache.file_update_protection=0'
+ . ' -d opcache.preload_user=root -d opcache.preload=' . $dir . '/preload.php';
+$opcache = ini_get('extension_dir') . '/opcache.' . PHP_SHLIB_SUFFIX;
+if (file_exists($opcache)) {
+ $ini .= ' -d zend_extension=' . $opcache;
+}
+php_cli_server_start($ini);
+
+$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/jit/preload_restart_hot_counters/';
+$context = stream_context_create(['http' => ['timeout' => 5]]);
+foreach (['call', 'reset', 'call', 'call'] as $script) {
+ echo @file_get_contents($base . $script . '.php', false, $context) ?: "no response\n";
+}
+?>
+--CLEAN--
+<?php
+$dir = __DIR__ . '/preload_restart_hot_counters';
+foreach (['preload', 'call', 'reset'] as $script) {
+ @unlink("$dir/$script.php");
+}
+@rmdir($dir);
+?>
+--EXPECT--
+1502868
+bool(true)
+bool(true)
+1502868
+1502868