[php-src] Issue #24396: JIT: "reg forwarding" assertion in zend_jit_def_reg (segfault in release) on masked counter loop, opcache.jit=function
| From: | waroad | Date: | Sun, 18 Oct 2026 06:23:01 +0000 |
| Subject: | [php-src] Issue #24396: JIT: "reg forwarding" assertion in zend_jit_def_reg (segfault in release) on masked counter loop, opcache.jit=function | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-253017@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/24396
Author: waroad
With
opcache.jit=function, a do/while loop whose counter is masked with
& 255 and tested with two ||-ed comparisons makes the JIT compiler hit
an assertion in zend_jit_def_reg (debug build) or segfault (release build) while the
script is loaded, even if the function is never called.
```php
<?php
function f() { $i = 0; do { $i = ($i + 1) & 255; } while ($i < 2 || $i > 200); return $i;
}
echo f(), "\n";
```
```
php -n -d opcache.enable_cli=1 -d opcache.file_update_protection=0 -d opcache.jit_buffer_size=64M -d
opcache.jit=function reduced.php
```
Debug build (assertions enabled):
```
php: ext/opcache/jit/zend_jit_ir.c:1357: zend_jit_def_reg: Assertion
`!zend_ssa_next_use_phi(jit->ssa, src_var, dst_phi) && "reg forwarding"'
failed.
```
Release build: segfault (exit 139) at script load, no output.
Expected output is 2, which is what the script prints with opcache.jit=off
and with opcache.jit=tracing.
Reproduced at 17b6575a3dc4965e0e3fb40046e4575663a76863 (assertion in the debug build, exit 139 in
the release build). $i++ without the mask, or a single comparison, does not trigger it.
This looks like the Pi/Phi register forwarding in
zend_jit_gen_phi/zend_jit_def_reg (ZREG_FORWARD): the code
assumes a forwarded Pi value feeds exactly one Phi, but here the loop header Phi has several Pi
sources and one feeds more than one Phi. Letting zend_jit_def_reg walk all Phi uses
instead of asserting a single one is not enough: a PHI operand still stays unset and
ir_check reports "ops[4] missing reference (0)"; in a release build that
unset operand is presumably what gets dereferenced.
I can send a patch if that helps.
The loop comes from a generated test, not from a real program.
Found with an LLM-based testing tool; I used Claude to reduce it and look for the cause.