Skip to content

Fix GH-23693: JIT guard branches on stale flags across basic blocks - #308

Closed
iliaal wants to merge 3 commits into
PHP-8.5from
fix/gh-23693-guard-jcc-cross-block
Closed

iliaal wants to merge 3 commits into
PHP-8.5from
fix/gh-23693-guard-jcc-cross-block

Conversation

@iliaal

@iliaal iliaal commented Sep 16, 2026

Copy link
Copy Markdown
Owner

The x86 instruction matcher folds v = BINOP(a, b); c = CMP(v, 0); GUARD(c) into a bare conditional jump on the flags the BINOP left behind, checking only that the BINOP precedes the CMP in the IR. Once GCM hoists a loop-invariant BINOP into a dominating block, the jump reads flags the intervening code has clobbered. In phpGH-23693 that drops if ($newY < 0) continue; from a recursive board search, so $newPos becomes -3 and the function throws on an argument it has already proved non-negative.

The fold now requires the BINOP in the guard's own block, with only snapshots between the comparison and the guard, the way ir_match_fuse_load() pairs ir_in_same_block() with ir_match_has_mem_deps(). Across the reporter's script, the new test, Zend/bench.php and Zend/micro_bench.php it fires four times before the patch and twice after; the two it loses are the miscompiles.

PHP-8.4 compiles the same rule but does not hoist the addition under any of the 20 hot-counter combinations tried, so the test is green there and this targets 8.5. Separately, ir_gcm.c:598 loops on n < 0 where upstream IR has n > 0, so the "OVERFLOW must be scheduled into the same block" pin never runs; that one is stale sync drift and the next IR update resolves it.

SQLite3::close() called from within a userland function, aggregate,
collation or authorizer callback freed the registered statements and
functions while sqlite3 was still executing, corrupting the active
statement and crashing the request. Track callback re-entry with a
per-database counter shared by all four callback kinds and throw an
Error from close() while it is non-zero; the database stays usable and
can be closed after the query completes.

Closes phpGH-23650
* PHP-8.4:
  ext/sqlite3: reject close() from inside a callback
The x86 matcher folds v = BINOP(a, b); c = CMP(v, 0); GUARD(c) into
IR_GUARD_JCC_INT, emitting the BINOP, dropping the CMP and branching on
the flags the BINOP left, but it only required the BINOP to precede the
CMP in the IR. Once GCM hoists a loop-invariant BINOP into a dominating
block, the jcc reads flags the intervening code has clobbered and the
guard fires on whatever is in EFLAGS. Require the BINOP to sit in the
guard's block and allow only snapshots between the comparison and the
guard, the way ir_match_fuse_load() pairs ir_in_same_block() with
ir_match_has_mem_deps(). The sibling MEM_BINOP fold already checks the
block, the IF side folds are pinned by full ref adjacency, and
ir_aarch64.dasc has no guard fold.

Mirrors the upstream fix dstogov/ir@51107a3.

Fixes phpGH-23693
@iliaal
iliaal force-pushed the fix/gh-23693-guard-jcc-cross-block branch from d817cc3 to b9bf306 Compare September 16, 2026 17:42
@iliaal

iliaal commented Sep 16, 2026

Copy link
Copy Markdown
Owner Author

Submitted upstream as php#23711.

@iliaal iliaal closed this Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant