fix(compiler): prevent phi crashes in EVM SSA stack-lift path - #530
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Fixes multiple verifier/abort crashes in the EVM SSA stack-lift path by ensuring phi placement/incomings match MIR invariants and codegen CFG wiring.
Changes:
- Insert newly created phis contiguously at the start of a block to satisfy MIR verifier constraints.
- Re-resolve stack-merge phi incoming blocks after CFG construction to correctly handle loop back-edges.
- Prevent stack-lifting for dynamic-jump-target blocks when the analyzer’s predecessor enumeration can’t cover codegen’s dispatch edges; adds change documentation.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| src/compiler/evm_frontend/evm_mir_compiler.h | Adds bookkeeping + API to finalize stack-merge phi incoming blocks post-CFG-build. |
| src/compiler/evm_frontend/evm_mir_compiler.cpp | Fixes phi insertion position; adds post-finalization fix-up to re-resolve incoming blocks for stack-merge phis. |
| src/compiler/evm_frontend/evm_analyzer.h | Adds analysis to conservatively disable lifting for dynamic-jump targets when predecessor modeling mismatches codegen edges. |
| docs/changes/2026-06-09-evm-ssa-lift-phi-crashes/README.md | Documents the three fixed crash causes, approach, and validation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
⚡ Performance Regression Check Results✅ Performance Check Passed (interpreter)Performance Benchmark Results (threshold: 25%)
Summary: 194 benchmarks, 0 regressions ✅ Performance Check Passed (multipass)Performance Benchmark Results (threshold: 25%)
Summary: 194 benchmarks, 0 regressions |
The EVM multipass JIT's SSA stack-lift path (ZEN_ENABLE_EVM_STACK_SSA_LIFT, off by default) emitted invalid MIR that the verifier rejected and that crashed the JIT (SIGABRT) on real-world control flow. Three independent defects, each fixed here; all changes are reachable only on the lift path, so the default (flag-off) build is byte-for-byte unaffected. 1. Phi contiguity: createPendingPhi appended phis at the block end, so a multi-slot stack merge interleaved a later slot's phis after an earlier slot's temp-store dassigns, violating the phis-contiguous-at-block-start invariant. Insert each phi after the block's leading phi run instead. 2. Dynamic-jump-target phi undersize: codegen's indirect-jump dispatch wires an edge from every non-constant JUMP to every JUMPDEST, but the analyzer's shape-class predecessor enumeration is a strict subset, so such merge blocks got phis sized below their real predecessor count, causing a downstream out-of-bounds SIGABRT. Exclude these blocks from lifting via a predecessor-coverage check in finalizeLiftability. 3. Loop back-edge phi incoming block: a loop header's merge phi froze its back-edge incoming block before the JUMPI terminator wired the real CFG edge, recording the source block's entry MIR BB instead of the actual predecessor. Re-resolve incoming blocks once the full CFG exists, at the end of finalizeEVMBase (value preserved, only the block pointer fixed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
No behavior change; default-path multipass unittests stay 223/223. - Deduplicate the phi-incoming forward walk: extract the BFS into resolveReachablePredecessorBB(TargetBB, CandidateBB) and have both resolveReachablePhiIncomingPredecessorBB and finalizeStackMergePhiIncomingBlocks use it, so reachability rules live in one place. - Drop the const_cast: add PhiInstruction::setIncomingBlock(Index, Block), which updates only the incoming block and leaves the value untouched. - Make a failed walk explicit: assert that the resolved block is a real predecessor (debug builds) instead of silently leaving a bad incoming block. - Avoid O(N^2) in finalizeLiftability: compute the dynamic-jump dispatch source set once and pass it to the coverage check, which now tests membership against an unordered_set instead of recomputing and linear-scanning per block. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
abmcar
force-pushed
the
fix/evm-ssa-lift-phi-crashes
branch
from
June 9, 2026 13:30
1c6ff0b to
3b7fced
Compare
This was referenced Jun 10, 2026
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.
What
Fixes three independent defects in the EVM multipass JIT's SSA stack-lift
path (
ZEN_ENABLE_EVM_STACK_SSA_LIFT, default OFF, from #395 / #493).With the flag ON, the JIT emitted invalid MIR that the verifier rejects and
that aborts the compiler (SIGABRT) on common control flow (loops,
dynamic-jump targets). All edits are reachable only when a block is lifted,
so the default flag-off build is unaffected.
The three bugs
Phi contiguity —
createPendingPhiappended phis at the block end, so amulti-slot stack merge interleaved a later slot's phis after an earlier
slot's temp-store
dassigns, breaking the "phis contiguous at block start"invariant. Fix: insert each phi after the block's leading phi run.
Dynamic-jump-target phi undersize — codegen's indirect-jump dispatch
wires an edge from every computed-
JUMPblock to everyJUMPDEST, but theanalyzer's shape-class predecessor enumeration (which sizes a merge block's
phis) is a strict subset, so such blocks got phis smaller than their real
predecessor count → downstream out-of-bounds SIGABRT. Fix: a
predecessor-coverage check in
finalizeLiftabilityexcludes these blocksfrom lifting (they fall back to the memory-backed stack path).
Loop back-edge phi incoming block — a loop header's merge phi froze its
back-edge incoming block before the
JUMPIterminator wired the real CFGedge, recording the source block's entry MIR BB instead of the actual
predecessor. Fix: re-resolve incoming blocks once the full CFG exists, at
the end of
finalizeEVMBase(value preserved; only the block pointer fixed).Validation
evmone-unittestsevmone-statetest -k fork_Cancun(EEST)evmone-unittestsmemory_grow_mstore8now passes)tools/format.sh checkThe default-path numbers were produced from this branch rebuilt with the flag
off; they confirm the change is a no-op for the shipping configuration.
Known limitation (disclosed)
This does not make SSA stack-lifting production-ready, and the flag stays
OFF. With the flag ON, at least one further crash remains:
stEIP1153_transientStorage/transStorageOK(EEST) still aborts under SSA-lift(confirmed in isolation; passes with the flag off). That defect is independent
of the three fixed here and will be addressed separately. CI does not build
with
ZEN_ENABLE_EVM_STACK_SSA_LIFT=ON, so it does not exercise this path; thelift-path results above were produced manually.
These are latent correctness/robustness fixes in merged code, worthwhile on
their own merits independent of when the feature is eventually enabled.
🤖 Generated with Claude Code