You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Segfault with tracing JIT on 2nd call of eval'd closure #21746
I keep running into this when attempting to benchmark PHP Handlebars. I reproduced the issue with two different laptops, one running Windows 11 (Intel Core Ultra 7 268V) and the other Ubuntu 24.04 (Intel Core i9-13900HX). I don't know if the processor matters, but those are the two I tested with.
On line 88 of the tests/benchmark.php script, there is a warmup loop with 50 iterations to warm up the JIT before measuring compilation time. So it is actually on 132 or more total iterations (50 + 82) when the segmentation fault occurs. If this warmup loop is changed to only have 1 iteration rather than 50, then the script has to be run with a final argument of 131 or higher to trigger the segmentation fault.
But if the warmup loop is removed, or its body replaced with simply strlen("warmup");, then the segfault never occurs, no matter how many iterations the benchmark runs with. So somehow having this preliminary loop with the same function calls as the loop following it causes a segfault later the second time that the $renderer closure is invoked.
Please let me know if anything more is needed to reproduce the issue. On Ubuntu, a "Sorry, the application php8.5 has stopped unexpectedly" popup is shown which has more details and a stacktrace:
PHP Version
PHP 8.5.5 (cli) (built: Apr 7 2026 19:24:35) (NTS Visual C++ 2022 x64)
Copyright (c) The PHP Group
Built by The PHP Group
Zend Engine v4.5.5, Copyright (c) Zend Technologies
with Zend OPcache v8.5.5, Copyright (c), by Zend Technologies
With the current feature branch I've been working on, I'm now running into this segfault even without any warmup or measurement loops in the benchmark. After just a single compilation, the segfault occurs on the second call to the eval'd closure. I pushed a new segfault2 branch demonstrating this, and again reproduced it on both Windows and Ubuntu.
changed the title [-]Segfault with tracing JIT on 2nd call of eval'd closure after preceding loops[/-][+]Segfault with tracing JIT on 2nd call of eval'd closure[/+]on Apr 16, 2026
I've been trying to make a more minimal reproduction, and ran into a case where rather than a segmentation fault occurring, a function is incorrectly called with an integer (apparently a heap pointer?) rather than a Closure, causing a PHP TypeError.
I added a reproduction script to my segfault2 branch mentioned above, which can be run via:
Render 58...OK
Render 59...OK
Render 60...OK
Render 61...
Fatal error: Uncaught TypeError: LR::hbbch(): Argument #2 ($cb) must be of type ?Closure, int given, called in C:\code\GitHub\php-handlebars\tests\jit_crash_code.php on line 15 and defined in C:\code\GitHub\php-handlebars\tests\jit_type_error.php:17
Stack trace:
#0 C:\code\GitHub\php-handlebars\tests\jit_crash_code.php(15): LR::hbbch(Array, 2353254736256)
#1 C:\code\GitHub\php-handlebars\tests\jit_type_error.php(52): {closure:C:\code\GitHub\php-handlebars\tests\jit_crash_code.php:2}()
#2 {main}
thrown in C:\code\GitHub\php-handlebars\tests\jit_type_error.php on line 17
Notice that on Render 61, the second argument to hbbch() was changed to a large integer rather than a Closure like is actually stored in the passed $g variable. Every time I run the script, the specific value of this integer changes (e.g. 2109548896640, 1896625054080, 2234430103936).
If I run the script without the tracing JIT (e.g. php tests/jit_type_error.php) it completes without any errors.
Interestingly, neither the segfault nor the TypeError occur with PHP 8.4, so it seems that the issue must have been introduced in master after PHP 8.4 entered feature freeze.
With the help of an LLM agent, I added various print statements to the OPcache JIT code, and then ran my jit_type_error.php script to generate a debug log of the JIT state during its execution. The LLM then read this log and identified "missing runtime IR guards before unconditional scalar writes" as the bug, and created a patch which does seem to resolve the issue for me (the segfault and TypeError no longer occur after building PHP 8.5 with this patch): theodorejb@fb6e58b
I also asked the agent to compare the differences in the JIT code between the PHP-8.4 and PHP-8.5 branches, to find which commit started the problem. The LLM identified 097edc8 as introducing the issue, though I haven't tried building PHP before/after this specific commit to confirm.
I'm not familiar enough with the JIT code to know whether the above patch is the right approach for fixing the issue, so I'm hesitant to open a PR unless someone more knowledgeable than me thinks the patch and commit message look okay.
I kept whittling away at the jit_type_error.php script, until I got it down to a much more minimal reproduction which doesn't depend on any package code:
Running this via php -d opcache.enable_cli=1 -d opcache.jit=tracing jit_type_error.php results in an unexpected TypeError after the $renderer closure is invoked 61 times. I updated the patch linked in my previous comment to include this test case.
Activity
devnexen commentedon Apr 14, 2026
can reproduce I believe
theodorejb commentedon Apr 16, 2026
With the current feature branch I've been working on, I'm now running into this segfault even without any warmup or measurement loops in the benchmark. After just a single compilation, the segfault occurs on the second call to the eval'd closure. I pushed a new
segfault2branch demonstrating this, and again reproduced it on both Windows and Ubuntu.https://github.com/devtheorem/php-handlebars/tree/segfault2
To reproduce check out the branch, run
composer install, and then run:[-]Segfault with tracing JIT on 2nd call of eval'd closure after preceding loops[/-][+]Segfault with tracing JIT on 2nd call of eval'd closure[/+]theodorejb commentedon Apr 17, 2026
I've been trying to make a more minimal reproduction, and ran into a case where rather than a segmentation fault occurring, a function is incorrectly called with an integer (apparently a heap pointer?) rather than a
Closure, causing a PHPTypeError.I added a reproduction script to my segfault2 branch mentioned above, which can be run via:
Last 10 lines of output:
Notice that on Render 61, the second argument to
hbbch()was changed to a large integer rather than aClosurelike is actually stored in the passed$gvariable. Every time I run the script, the specific value of this integer changes (e.g.2109548896640,1896625054080,2234430103936).If I run the script without the tracing JIT (e.g.
php tests/jit_type_error.php) it completes without any errors.theodorejb commentedon Apr 19, 2026
Interestingly, neither the segfault nor the TypeError occur with PHP 8.4, so it seems that the issue must have been introduced in master after PHP 8.4 entered feature freeze.
With the help of an LLM agent, I added various print statements to the OPcache JIT code, and then ran my
jit_type_error.phpscript to generate a debug log of the JIT state during its execution. The LLM then read this log and identified "missing runtime IR guards before unconditional scalar writes" as the bug, and created a patch which does seem to resolve the issue for me (the segfault and TypeError no longer occur after building PHP 8.5 with this patch): theodorejb@fb6e58bI also asked the agent to compare the differences in the JIT code between the PHP-8.4 and PHP-8.5 branches, to find which commit started the problem. The LLM identified 097edc8 as introducing the issue, though I haven't tried building PHP before/after this specific commit to confirm.
I'm not familiar enough with the JIT code to know whether the above patch is the right approach for fixing the issue, so I'm hesitant to open a PR unless someone more knowledgeable than me thinks the patch and commit message look okay.
theodorejb commentedon Apr 20, 2026
I kept whittling away at the
jit_type_error.phpscript, until I got it down to a much more minimal reproduction which doesn't depend on any package code:Running this via
php -d opcache.enable_cli=1 -d opcache.jit=tracing jit_type_error.phpresults in an unexpectedTypeErrorafter the$rendererclosure is invoked 61 times. I updated the patch linked in my previous comment to include this test case.Fix incorrect trace stop type
arnaud-lb commentedon May 5, 2026
Thank you @theodorejb!