[php-src] Issue #24482: Tracing JIT: second `($d & 1) === 0` in a loop iteration evaluates to true for odd `$d` after side traces are compiled
| From: | gitKBDL | Date: | Wed, 21 Oct 2026 15:36:28 +0000 |
| Subject: | [php-src] Issue #24482: Tracing JIT: second `($d & 1) === 0` in a loop iteration evaluates to true for odd `$d` after side traces are compiled | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-253054@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/24482
Author: gitKBDL
### Description
The following code:
```php
<?php
function f(array $k): array {
$out = [0, 0, 0, 0];
for ($i = 0; $i < 1000; ++$i) {
for ($d = 0; $d < 4; ++$d) {
$a = $k[$d < 2 ? 0 : 1];
$b = $k[($d & 1) === 0 ? 2 : 3];
if (($d & 1) === 0) {
++$out[$d]; // only $d = 0 and $d = 2 can get here
}
}
}
return $out;
}
echo json_encode(f([0, 1, 2, 3])), "\n";
```
Run with:
```
php -d opcache.enable_cli=1 -d opcache.jit=tracing -d opcache.jit_buffer_size=64M test.php
```
Resulted in this output:
```
[1000,0,1000,984]
```
But I expected this output instead:
```
[1000,0,1000,0]
```
The second
($d & 1) === 0 of an iteration comes out true for $d = 3
once the side traces are compiled (984 of the 1000 iterations; the first 16 are right).
What we found while narrowing it down (official Docker images php:8.5-cli,
php:8.4-cli, php:8.3-cli, x86_64):
- PHP 8.5.11 (NTS and ZTS): wrong with opcache.jit=tracing, 1254,
1253, 1255, 1154. Right with 1054 (no register
allocation) and with the JIT off. opcache.optimization_level makes no difference, even
0.
- PHP 8.4.26: wrong too ([1000,983,1000,987]).
- PHP 8.3.35: right.
- The wrong second test is always the same arithmetic result compared with the literal
0: ($d & 1) === 0, !== 0, == 0, >
0, <= 0, 0 === ($d & 1), $d % 2 === 0,
($d ^ 1) === 0, ($d - 1) === 0. It is right when it is compared with
another constant (=== 1), when the result is kept in a variable and that is tested
twice, when the loop variable itself is tested ($d === 0), and when the tests are in
different functions.
- Something must read an array in a branch of the first test ($k[... ? 2 : 3], or
if (...) { $b = $k[2]; } else { $b = $k[3]; }). With $b = (...) ? 2 : 3 or
if (...) { $b = 1; } it is right.
- The $d < 2 branch before it is not always needed: with ($d ^ 1) === 0
the second test is wrong without it.
### PHP Version
```
PHP 8.5.11 (cli) (NTS), official php:8.5-cli image
PHP 8.4.26 (cli) (NTS), official php:8.4-cli image
```
### Operating System
Debian (official Docker images), x86_64