[php-src] Issue #24396: JIT: "reg forwarding" assertion in zend_jit_def_reg (segfault in release) on masked counter loop, opcache.jit=function

From: 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.

« previous php.bugs (#253017) next »