Repository navigation
JIT: fix lost register-allocated CVs on interrupt at the end of a loop trace - #24610
Merged
arnaud-lb merged 4 commits intoOct 27, 2026
Merged
Conversation
When closing a loop trace that uses the initial IP, the interrupt check either jumps to a deoptimizing exit at the loop header or directly to jit_stub_interrupt_handler. The choice was made with zend_jit_trace_stack_needs_deoptimization(), which only checks STACK_REG() and the slot flags. A register-allocated variable is tracked during compilation by its IR ref only, and STACK_REG() is still ZREG_NONE, so the stub was chosen even when a CV lived only in a register. If EG(vm_interrupt) was raised during the iteration without a function call (e.g. by a signal handler or another thread), the VM resumed the loop with a stale CV value. Also check for refs that were not stored to memory, using the same criterion as the link-to-root path. VmInterruptComparable is backported to zend_test from 06bfe15 to raise the interrupt deterministically.
morrisonlevi
marked this pull request as ready for review
October 23, 2026 17:54
Member
|
This looks right and the fix does look necessary, but I wasn't able to reproduce the issue and the test passes without the fix. Does it rely on a specific VM/version? |
Contributor
Author
|
I hit this in CI on arm64 nodes (I think AWS Graviton?) and reproduced it on my Apple M5. There should be an issue on x86_64 but there are difference in calling conventions and such so maybe this specific test won't reproduce it. I'll see if I can get my agent to alter the test to get it failing on both architectures, or possibly write two tests. |
Contributor
Author
|
@arnaud-lb I've altered the test to make it fail on x86_64 as well and updated its description. I've also added a NEWS entry for PHP 8.4. You should be able to reproduce the issue more easily now! |
arnaud-lb
added a commit
that referenced
this pull request
Oct 27, 2026
* PHP-8.6: JIT: fix lost register-allocated CVs on interrupt at the end of a loop trace (#24610)
Member
|
Thank you! |
Truta446
pushed a commit
to Truta446/php-src
that referenced
this pull request
Oct 27, 2026
* PHP-8.4: JIT: fix lost register-allocated CVs on interrupt at the end of a loop trace (php#24610)
Truta446
pushed a commit
to Truta446/php-src
that referenced
this pull request
Oct 27, 2026
* PHP-8.5: JIT: fix lost register-allocated CVs on interrupt at the end of a loop trace (php#24610)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
AI disclosure: I hit this bug in the Datadog PHP profiler CI when running
ext/opcache/tests/jit/gh20838.phpt, and I used AI to debug it, write the patch, and the test. I am certainly not a JIT expert, but I have reviewed the code to the best of my ability.When closing a loop trace that uses the initial IP, the interrupt check either jumps to a deoptimizing exit at the loop header or directly to
jit_stub_interrupt_handler. The choice was made withzend_jit_trace_stack_needs_deoptimization, which only checksSTACK_REGand the slot flags. A register-allocated variable is tracked during compilation by its IR ref only, andSTACK_REGis stillZREG_NONE, so the stub was chosen even when a CV lived only in a register. IfEG(vm_interrupt)was raised during the iteration without a function call (e.g. by a signal handler or another thread), the VM resumed the loop with a stale CV value.Also check for refs that were not stored to memory, using the same criterion as the link-to-root path.
VmInterruptComparableis backported tozend_testfrom 06bfe15 to raise the interrupt deterministically. This is going to cause a merge-conflict when merging forward to PHP 8.5.