bugc: bracket inlined invoke/return to boundary ops in evmgen#235
Merged
Conversation
Inlined virtual activations mis-rendered in the tracer: the call stack never popped (phantom frames). Root cause was in evmgen lowering, not the optimizer. An IR instruction lowers to N EVM micro-ops, and the generic lowering attaches that instruction's whole operationDebug to every op. That is right for source/variables/transform context (all N ops map to the instruction) but wrong for invoke/return, which are positional activation boundaries: invoke marks one push point, return one pop point. Broadcasting them across the whole op-run makes push/pop reconstruction see every op as both a push and a pop. The optimizer IR is already correctly bracketed (verified per-pass: invoke/return counts are stable through inlining + all L2/L3 passes); the smear appears only after lowering. Real calls are unaffected because their invoke/return ride single-op JUMP/JUMPDEST terminators — only inlined activations put them on multi-op compute instructions. Fix (bracket-activation.ts, wired into block generation): for the ops emitted by one instruction, keep invoke on only the first op and return on only the last op, stripped from the interior; transform markers and source/variables stay on all ops. General evmgen invariant, not inline-specific — a no-op for single-op terminators. Reaches invoke/return nested in pick/gather composites, not just flat leaves. Tests: new inline-bracket.test.ts asserts one push + one pop per site on dbl@2-sites (both=0), invoke-before-return ordering, entry vs exit placement on a multi-instruction body, and unchanged runtime behavior at O0-O3. Full suite green (428 passed).
Contributor
|
Architect's requested safety net for the general invoke/return bracketing invariant: assert it stays a no-op where invoke/return ride single-op carriers. - tailcall back-edge (tailRecursiveSum, O2): the combined invoke+return on the one back-edge JUMP survives (both>=1). - mutually recursive real calls (never inlined): no op carries both, and invoke/return are still present (real call tracing intact).
gnidan
added a commit
that referenced
this pull request
Jul 3, 2026
…d shapes (#239) Add fixtures for both emission shapes the reconstruction must handle: - bracketed (post #235): invoke on the body's first op, return on the last, transform:["inline"] on all — frame visible across every body op INCLUDING the exit op (close-after), gone at the gap. - legacy smeared: every op carries invoke+return+inline — close-after still yields exactly one frame per body, no accumulation across gap-separated bodies. Verified end-to-end on real O2 (dbl@2 sites, #235 bracketed emission): one virtual frame per body incl. the exit op, top level between/after.
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.
Fixes the inlined virtual-activation mis-render (#23): the tracer's call
stack never popped (phantom frames) because every op of an inlined body
carried BOTH
invokeandreturn.Root cause — evmgen lowering, not the optimizer
An IR instruction lowers to N EVM micro-ops, and the generic lowering
attaches that instruction's whole
operationDebugto every op. Right forsource/variables/
transform(all N ops map to the instruction) but wrongfor
invoke/return: those are positional activation boundaries (onepush point, one pop point). Broadcasting them across the op-run makes
push/pop reconstruction see every op as both.
Verified per-pass that the optimizer IR stays correctly bracketed through
inlining and all L2/L3 passes (invoke/return counts stable); the smear
appears only after lowering. Real calls are unaffected — their
invoke/return ride single-op JUMP/JUMPDEST terminators; only inlined
activations put them on multi-op compute instructions.
Fix
bracket-activation.ts(wired into block generation): for the ops emittedby one instruction, keep
invokeon only the first op,returnon onlythe last op, stripped from the interior.
transform:["inline"]+source/variables stay on all ops (membership preserved). A general
evmgen invariant, not inline-specific — no-op for single-op terminators
(confirmed: real-call fixtures unchanged). Reaches invoke/return nested in
pick/gathercomposites, not just flat leaves (per architect's note).Tests
New
inline-bracket.test.ts: one push + one pop per site on dbl@2-sites(
both=0), invoke-before-return ordering, entry-vs-exit placement on amulti-instruction body, unchanged runtime at O0–O3. Full bugc suite green
(428 passed, 22 pre-existing skips).
No schema change, no optimizer change, no inlining.ts change.