Skip to content

ext/opcache: Fix GH-25164 loop liveness across calls - #25178

Open
iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/gh-25164-jit-entry-phi-liveness
Open

iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/gh-25164-jit-entry-phi-liveness

Conversation

@iliaal

@iliaal iliaal commented Nov 3, 2026

Copy link
Copy Markdown
Member

zend_jit_may_be_in_reg() missed Phi uses in ENTRY blocks and loop-carried uses in the definition block, leaving values in clobbered registers after user function calls. The walk now covers both cases while preserving zero-length Pi intervals and following only the real incoming edge for same-block Pi uses. Reproduced with the reporter's loop on PHP-8.4 debug builds. #18392 addresses a separate EX(call) cache issue.

Fixes GH-25164

if (use_block != def_block) {
bb = &ssa->cfg.blocks[use_block];
bb = &ssa->cfg.blocks[p->block];
if (bb->flags & (ZEND_BB_ENTRY|ZEND_BB_RECV_ENTRY)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused. So the block after the DO_UCALL opcode is an entry block, reachable via a fake edge successor? And so the frontend has to make sure values that can go across those fake edges into an entry are not in a register? That's wild.
I wonder if this is the best approach, it's also very difficult to understand imo

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Under ZEND_CFG_STACKLESS the block after DO_UCALL/DO_FCALL/DO_FCALL_BY_NAME (and include/yield) is an ENTRY: for user calls the JIT code tail-calls into the VM and the callee comes back through it, so no register survives that edge. zend_jit_may_be_in_reg() has rejected such values since caf102d, and this only fixes two cases its walk missed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the cleanest way to fix it? At least some comments are necessary since it's difficult to follow

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restructured the Phi-use loop and commented each case. It's the narrowest fix I found: the walk already handles every other use, and this extends it to the two Phi cases it skipped.

@iliaal
iliaal force-pushed the fix/gh-25164-jit-entry-phi-liveness branch from 658d9a8 to 81d3c93 Compare November 3, 2026 21:24
Check the Phi use's ENTRY block and walk incoming edges for uses in the
definition block. This keeps loop-carried values in memory across user
calls, while preserving zero-length Pi intervals. Pi uses follow only
their own incoming edge. DO_UCALL, DO_FCALL, and DO_FCALL_BY_NAME
reproduce the bug; yield and include do not.

Fixes phpGH-25164
@iliaal
iliaal force-pushed the fix/gh-25164-jit-entry-phi-liveness branch from 81d3c93 to b207515 Compare November 3, 2026 21:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants