bugc: fix recursive and branch miscompilation with canonical block-boundary stack discipline - #283
Open
gnidan wants to merge 1 commit into
Open
bugc: fix recursive and branch miscompilation with canonical block-boundary stack discipline#283gnidan wants to merge 1 commit into
gnidan wants to merge 1 commit into
Conversation
Contributor
|
gnidan
force-pushed
the
compiler-cfg-stack-tracking
branch
2 times, most recently
from
August 6, 2026 01:06
02a72f5 to
78968f6
Compare
gnidan
force-pushed
the
compiler-cfg-stack-tracking
branch
from
August 6, 2026 01:24
78968f6 to
e139563
Compare
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.
Recursive functions were miscompiled at every optimization level: a self-recursive call set up its callee with a corrupted operand stack and the callee read garbage arguments. The root cause was that evmgen tracked the operand stack by threading it through block layout order rather than the control-flow graph, so the compiler's model of the stack desynced from the runtime stack wherever a block's layout neighbor was not its actual predecessor. Leftover block-local scratch went unaccounted, the pre-call cleanup undercounted, and the callee's arguments were wrong.
This replaces layout-order threading with a canonical block-boundary stack invariant. Every block is entered with a statically known operand stack determined by its role in the control-flow graph: a call continuation is entered with the callee's return value on top, and every other block is entered empty. Each block then canonicalizes its stack on exit so the invariant holds for its successors — a jump clears any leftover scratch, and a branch loads its condition and drops the scratch beneath it, so both successors are entered empty. Return and call terminators already reduced the stack to their canonical hand-off. With the entry stack reconstructed from the CFG and the exit stack canonicalized, the tracked model matches the runtime stack again and the existing per-instruction and terminator logic is exact.
The fix turned out simpler than the design anticipated: no memory spilling of branch conditions or return values is needed. Loading the operand and then dropping the scratch beneath it keeps the change self-contained in the terminator lowering, and the branch condition is recovered by the ordinary value loader whether it lives on the stack or in memory.
The new tests verify results by executing the compiled bytecode. Tail, mutual, and tree-shaped recursion all compute correctly at levels 0 through 3, as do if/else diamonds and a function whose return block has multiple predecessors leaving different scratch depths (the multi-predecessor case that the layout-order model could desync). for-loops are covered through level 2.
Known gap, not addressed here: for-loops revert at optimization level 3 (they are correct at levels 0–2). This is pre-existing on main — the same revert reproduces with this change reverted — and has a distinct root cause in level-3 loop lowering (tail-call back-edge plus block-merging), unrelated to the stack-tracking model corrected here. It is being tracked separately.
Fixes #275.