[php-src] Issue #24678: Tracing JIT with opcache.jit_max_polymorphic_calls=0: local variable undefined after a call in a side trace

From: Date: Sun, 25 Oct 2026 11:09:22 +0000
Subject: [php-src] Issue #24678: Tracing JIT with opcache.jit_max_polymorphic_calls=0: local variable undefined after a call in a side trace
Groups: php.bugs 
Request: Send a blank email to php-bugs+get-253176@lists.php.net to get a copy of this message
Issue: https://github.com/php/php-src/issues/24678 Author: ChrisOrlando ### Description With the tracing JIT and opcache.jit_max_polymorphic_calls=0, a local that is always assigned becomes undefined after a method call, once a side trace has been compiled for that code path. It shows up as Warning: Undefined variable $p (or, with typed parameters, as TypeError: ... must be of type int, null given where the local is passed on). The following code: ```php <?php // php -d opcache.enable_cli=1 -d opcache.jit=tracing -d opcache.jit_buffer_size=64M \ // -d opcache.jit_max_polymorphic_calls=0 -d opcache.file_update_protection=0 repro.php // // The calls need to be straight-line code rather than a loop, so the script // writes them out and includes the result. $code = <<<'PHP' <?php final class Fast { public function get(int $x): int { return 1; } } final class Slow { public function get(int $x): int { return 0; } } final class Router { public function __construct(private Fast $fast = new Fast(), private Slow $slow = new Slow()) {} public function route(int $addr): int { $p = $addr & 0x1FFFFFFF; if ($p < 0x00800000) { return $this->fast->get($p & 0x1FFFFF); } return $this->slow->get($p - 0x1F801070) + $p; } } $r = new Router(); PHP; // 127 fast-path calls make route() hot (opcache.jit_hot_func). The slow path then // leaves the trace, and its 9th exit (opcache.jit_hot_side_exit = 8) is compiled. $code .= str_repeat("\$r->route(0x801FFA70);\n", 127); $code .= str_repeat("echo \$r->route(0x1F801070), \"\\n\";\n", 9); file_put_contents(__DIR__ . '/generated.php', $code); include __DIR__ . '/generated.php'; ``` Resulted in this output (the 9th call): ``` 528486512 ... (7 more lines of 528486512) Warning: Undefined variable $p in .../generated.php on line 15 0 ``` But I expected this output instead (as with the JIT disabled, or jit_max_polymorphic_calls 1, 2 or 8): ``` 528486512 ... (9 lines of 528486512 in total) ``` ### Observations - The failure starts exactly when the counts line up with the default thresholds: 127 calls on the fast path make route() hot (opcache.jit_hot_func=127), and the slow path fails on its 9th call, its first after its side exit becomes hot (opcache.jit_hot_side_exit=8). With 126 fast-path calls it doesn't happen. - Only opcache.jit_max_polymorphic_calls=0 triggers it; 1, 2 (the default) and 8 are fine. - Both calls need an *expression* of $p as their argument ($p & 0x1FFFFF, $p - 0x1F801070). Passing $p itself to the fast path, or to the slow path, makes it go away. That suggests $p only exists in a register in the parent trace, and isn't stored back to its slot before the non-inlined call on the side trace. - Found while reducing a crash in a PHP emulator: its bus read() method lost its $p the same way, through Bus::extract(): Argument #2 ($p) must be of type int, null given. ### PHP Version ``` PHP 8.5.12 (cli) (built: Oct 19 2026 17:02:46) (NTS) [Homebrew] ``` ### Operating System macOS (Darwin 27.0.0), Apple M1 (arm64)

« previous php.bugs (#253176) next »