Skip to content

Fix GH-21691: OPcache CFG optimizer eliminates QM_ASSIGN feeding JMPZ with VAR operand#21696

Open
iliaal wants to merge 1 commit intophp:masterfrom
iliaal:fix/gh-21691-opcache-jmpz-var
Open

Fix GH-21691: OPcache CFG optimizer eliminates QM_ASSIGN feeding JMPZ with VAR operand#21696
iliaal wants to merge 1 commit intophp:masterfrom
iliaal:fix/gh-21691-opcache-jmpz-var

Conversation

@iliaal
Copy link
Copy Markdown
Contributor

@iliaal iliaal commented Apr 9, 2026

Fixes #21691

The CFG optimizer (pass 5) removed a QM_ASSIGN that converted IS_VAR to IS_TMP_VAR before JMPZ. Since JMPZ has no handler for IS_VAR operands, this produced "Invalid opcode 43/4/0." The pattern occurs when ASSIGN_REF (which produces IS_VAR) feeds into a conditional via QM_ASSIGN.

Skips the QM_ASSIGN elimination when the source operand is IS_VAR.

@andypost
Copy link
Copy Markdown
Contributor

andypost commented Apr 9, 2026

Confirm it fixes running test for Drupal, used as patch and tests pass https://git.drupalcode.org/project/drupal/-/jobs/9303090

Copy link
Copy Markdown
Member

@dstogov dstogov left a comment

Choose a reason for hiding this comment

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

The fix is right but incomplete.
The similar checks have to be added at least for ZEND_BOOL_NOT above and for ZEND_JMPN?Z_EX below.

I would add these IS_VAR checks into the conditions chains to avoid additional break. (but this is really not critical, and both decisions may have their advantages).

@iluuu1994 please also take a look. This is relate to ""VAR|TMP overhaul (GH-20628)"".

…MPZ_EX with IS_VAR

The CFG optimizer (pass 5) substituted a QM_ASSIGN's source operand
into its consumer without checking the source's operand type. When
ASSIGN_REF produces IS_VAR and a QM_ASSIGN converts it to IS_TMP_VAR
before JMPZ/JMPNZ or JMPZ_EX/JMPNZ_EX, eliminating the QM_ASSIGN
leaves an IS_VAR operand on a consumer whose handler is declared
CONST|TMP|CV, producing "Invalid opcode 43/4/0" (or 46/4/0 for the
_EX variants).

Guard both substitution sites with src->op1_type != IS_VAR, folded
into the enclosing condition (per dstogov's review) so the BOOL_NOT
branch is defensively covered as well. Test exercises both JMPZ and
JMPZ_EX paths.

Closes phpGH-21691
@iliaal iliaal force-pushed the fix/gh-21691-opcache-jmpz-var branch from 90f1c24 to 94bad2a Compare April 13, 2026 23:40
@iliaal
Copy link
Copy Markdown
Contributor Author

iliaal commented Apr 13, 2026

Confirmed. ($data = &$this->getData()) && count($data) fatals with opcode 46/4/0, same shape as the original. Updated patch:

  • Hoisted src->op1_type != IS_VAR into the enclosing condition in both the JMPZ/JMPNZ and JMPZ_EX/JMPNZ_EX handlers, per your suggestion. This also covers the BOOL_NOT branch defensively.
  • Extended gh21691.phpt to exercise both JMPZ and JMPZ_EX paths.

All 880 opcache tests pass. @iluuu1994, worth a look at whether the broader invariant from #20628 warrants auditing other optimizer substitutions.

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.

PHP 8.6 OPcache: DO_UCALL breaks reference returns (segfault / invalid opcode)

3 participants