Description
Description
In a (pretty old) PHP projet, is used a non-typed function moins
(minus in french) with this simple following code:
<?php
function moins($a, $b) {
return $a - $b;
}
We wanted to activate JIT (tracing) on PHP 8.4 (now with PHP 8.4.8), and after some unpredictable minutes or hours (depending of the website traffic ?) , the result of this function moins
becomes wrong :
<?php
echo 'Moins:' . PHP_EOL;
echo moins(1, 1) . PHP_EOL;
echo moins(2, 1) . PHP_EOL;
echo moins(3, 1) . PHP_EOL;
echo PHP_EOL;
echo "with string:" . PHP_EOL;
echo moins('1', '1') . PHP_EOL;
echo moins('2', '1') . PHP_EOL;
echo moins('3', '1') . PHP_EOL;
Resulted in this output:
Moins:
0
4.9406564584125E-324
9.8813129168249E-324
with string:
0
4.9406564584125E-324
9.8813129168249E-324
But we expected this output instead:
Moins:
0
1
2
with string:
0
1
2
We didn’t test JIT before on previous PHP branches, so I don’t know if the result is the same on PHP 8.3 or before. This bug doesn’t appear without JIT enabled.
As you can see, some float with exponent is returned, and it seems related with floating point IEEE 754 64-bit binary format ; these numbers are the same as
<?php
(2 - 1)*2**-1074; // 4.9406564584125E-324
(3 - 1)*2**-1074; // 9.8813129168249E-324
And with all that, I was unable to reproduce that in CLI with JIT tracing on (we show that with FPM), nor in my local (DDEV) environment (and with different JIT configuration option).
The bug disapear (for some time) as soon as we reload that PHP version.
We show that at least on 2 different servers, with intel CPU
- BIOS Model name: Intel(R) Xeon(R) E-2136 CPU @ 3.30GHz To Be Filled By O.E.M. CPU @ 3.3GHz
- BIOS Model name: Intel(R) Xeon(R) E-2288G CPU @ 3.70GHz To Be Filled By O.E.M. CPU @ 3.7GHz
Opcache.jit* FPM configuration:
opcache.jit tracing tracing
opcache.jit_bisect_limit 0 0
opcache.jit_blacklist_root_trace 16 16
opcache.jit_blacklist_side_trace 8 8
opcache.jit_buffer_size 128M 128M
opcache.jit_debug 0 0
opcache.jit_hot_func 127 127
opcache.jit_hot_loop 64 64
opcache.jit_hot_return 8 8
opcache.jit_hot_side_exit 8 8
opcache.jit_max_exit_counters 8192 8192
opcache.jit_max_loop_unrolls 8 8
opcache.jit_max_polymorphic_calls 2 2
opcache.jit_max_recursive_calls 2 2
opcache.jit_max_recursive_returns 2 2
opcache.jit_max_root_traces 1024 1024
opcache.jit_max_side_traces 128 128
opcache.jit_max_trace_length 1024 1024
opcache.jit_prof_threshold 0.005
Is it something that talk to someone ?
Obviously, this moins
function is used in a more complex context, in a web templating system notably, that function may also be call inside an eval()
(I don’t know if that the cause of the problem) ; but for now I’m not able to reproduce a systematic small file code which trigger the bug, outside the full web project.
But from the moment the function bugs, it bugs in whatever script the function is called (that includes the file with the function declaration).
One example tried among other in CLI to reproduce vainly
php8.4 -d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.jit=tracing -d opcache.jit_buffer_size=1G -d opcache.jit_max_root_traces=1000000 -d opcache.jit_max_side_traces=1000000 -d opcache.jit_max_exit_counters=1000000 -d opcache.jit_hot_loop=0 -d opcache.jit_hot_func=0 -d opcache.jit_hot_return=0 -d opcache.jit_hot_side_exit=0 -d memory_limit=-1 test.php
- Is there some open or closed bugs with JIT that are similar to that ?
Note: I’m not so familiar with php-src, please apologize if I’m not write things the rigth way.
PHP Version
php-fpm8.4 -v
PHP 8.4.8 (fpm-fcgi) (built: Jun 9 2025 13:42:27) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.4.8, Copyright (c) Zend Technologies
with Zend OPcache v8.4.8, Copyright (c), by Zend Technologies
php8.4 -v
PHP 8.4.8 (cli) (built: Jun 9 2025 13:42:27) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.4.8, Copyright (c) Zend Technologies
with Zend OPcache v8.4.8, Copyright (c), by Zend Technologies
Operating System
Debian GNU/Linux 12 (bookworm)