[php-src] Issue #24389: JIT: segfault when an inlined call's reference argument changes from int to float (zend_jit_trace_exit assertion)
| From: | waroad | Date: | Sat, 17 Oct 2026 22:07:28 +0000 |
| Subject: | [php-src] Issue #24389: JIT: segfault when an inlined call's reference argument changes from int to float (zend_jit_trace_exit assertion) | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-253014@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/24389
Author: waroad
With the tracing JIT, a function called in a hot loop with reference-bound variables crashes once
the operand type changes from int to float: the release build segfaults and a debug build fails an
assertion in
zend_jit_trace_exit.
```php
<?php
function right($x, $y) { $a = $x + $y; return $x - $y; }
function driver($x, $y) {
$xr = &$x; $yr = &$y;
for ($i = 0; $i < 300; $i++) { right($x, $y); }
}
driver(3, 2);
driver(3.5, 2.5);
```
```
php -n -d opcache.enable_cli=1 -d opcache.file_update_protection=0 -d opcache.jit_buffer_size=64M -d
opcache.jit=tracing input.php
```
Debug build (assertions enabled), same command run with USE_ZEND_ALLOC=0
USE_TRACKED_ALLOC=1:
```
php: ext/opcache/jit/zend_jit_trace.c:8858: zend_jit_trace_exit: Assertion
`((execute_data)->opline) >= ((execute_data)->func)->op_array.opcodes &&
((execute_data)->opline) < ((execute_data)->func)->op_array.opcodes +
((execute_data)->func)->op_array.last' failed.
Aborted
```
Release build: exit code 139, "Segmentation fault".
The script has no output and should simply run to completion. A more natural shape that also
segfaults the release build (after printing 44850, where JIT off prints
44850 and 45000): foreach ($data as &$v) { $sum += calc($v, 1);
} over an int array and then a float array, with calc doing the same arithmetic
as right.
It still reproduces at 5ccc56b1e43f (latest upstream when checked): the release build segfaults
(SIGSEGV) with the command above, and the debug build fails the same assertion (at line 8850 there;
checked with the hot thresholds set to 1). The command uses the default JIT thresholds.
Looks like the type guard emitted for the register-allocated callee CVs on
ZEND_JIT_TRACE_ENTER in zend_jit_trace.c
(zend_jit_type_guard(&ctx, NULL, ...)) has no opline, so the exit trusts
EX(opline) of the freshly pushed inlined frame, which is not initialised yet. PR #18392
(JIT: Don't reuse IP register for EX(call)) may be related; I did not test it, its diff does
not apply on the latest commit.
I can send a patch if that helps.
Found with an LLM-based testing tool; I used Claude to reduce it and look for the cause.