Skip to content

bugc: fix optimizer for nested call arguments in recursion#208

Merged
gnidan merged 1 commit intocall-returnfrom
compiler-fix-optimizer-recursion
Apr 2, 2026
Merged

bugc: fix optimizer for nested call arguments in recursion#208
gnidan merged 1 commit intocall-returnfrom
compiler-fix-optimizer-recursion

Conversation

@gnidan
Copy link
Copy Markdown
Member

@gnidan gnidan commented Apr 2, 2026

Summary

  • Moves phi resolution from target blocks to predecessor blocks (standard phi deconstruction), fixing back-edge resolution for TCO loops and for-loops
  • Fixes jump optimization eliminating phi-bearing blocks
  • Rewrites TCO to use a pre_entry trampoline with phis on the original entry block

Fixes the level 2+ optimizer failure with nested call arguments like count(succ(n), target).

Move phi resolution from target blocks to predecessor blocks
(standard phi deconstruction). The previous approach resolved
phis at the target using the layout-order predecessor, which
broke for back-edges where the runtime predecessor differs.

Also fixes jump optimization eliminating phi-bearing blocks
and rewrites TCO to use a pre_entry trampoline with phis on
the original entry block.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 2, 2026

PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-04-02 12:36 UTC

@gnidan gnidan merged commit 8e257a5 into call-return Apr 2, 2026
4 checks passed
@gnidan gnidan deleted the compiler-fix-optimizer-recursion branch April 2, 2026 12:32
gnidan added a commit that referenced this pull request Apr 16, 2026
* Draft function call context schemas

* Organize schema a bit

* Disable unevaluated properties

* Allow additionalProperties at top-level

* Add function identity fields to invoke/return/revert contexts

Introduce a shared context/function schema defining identifier,
declaration, and type properties (mirroring Variable's pattern)
and compose it into invoke, return, and revert via allOf. Switch
return and revert from additionalProperties to
unevaluatedProperties to support allOf composition.

* Fix Playground crash on page refresh in dev server

Wrap the Monaco-based Playground component in BrowserOnly with
React.lazy so it only loads client-side, preventing SSR-related
initialization errors that caused [object Object] crashes on refresh.

* Nest invoke/return/revert under function/ category

Move function call lifecycle contexts (invoke, return, revert) into
a function/ subcategory and extract shared identity fields
(identifier, declaration, type) into a base function schema that
each context extends via $ref.

* Rename invoke discriminant fields: internal->jump, call->message

Reduces ambiguity: `call` could mean any function call, and
`internal` vs `create` doesn't clearly convey the mechanism.
The new names (jump/message/create) map directly to the three
EVM invocation mechanisms.

Also enforces mutual exclusivity of delegate/static on external
calls via `not: required: [delegate, static]`.

* Rewrite function context descriptions

Frame descriptions from the perspective of contexts marking
instructions rather than "representing" things. A context
indicates association with a function lifecycle event; it does
not represent the event itself.

* Rewrite function context examples with realistic scenarios

Each example now describes a concrete EVM execution scenario:
which instruction the context marks, what the stack and memory
layout looks like, and why each pointer points where it does.

* Add type specifier schema and use it across the format

Introduce schema:ethdebug/format/type/specifier to formalize
the "type or type reference" pattern. Update type wrapper,
variables, and doc pages to use the new schema instead of
inline oneOf/if-then discrimination. Remove the now-unnecessary
allow-list entry for the function context schema.

* Format

* format: add TypeScript types for function call contexts (#186)

Add Type.Reference, Type.Specifier, and type guards for the
new function call context schemas (invoke, return, revert).

- Type.Reference: { id: string | number } for type references
- Type.Specifier: Type | Reference union (matches type/specifier schema)
- Context.Function.Identity: shared function identity fields
- Context.Invoke: internal calls, external calls, contract creation
- Context.Return: function return with data and optional success
- Context.Revert: function revert with optional reason/panic
- Update Variable.type to accept Type.Specifier (was Type only)
- Update Type.Wrapper to use Type.Specifier

* bugc: emit invoke/return contexts for internal function calls (#185)

Add debug context annotations for function call boundaries
using the typed Format.Program.Context.Invoke and
Context.Return interfaces:

- Invoke context on caller's JUMP instruction with target
  pointer and argument group pointers (stack slots)
- Return context on continuation JUMPDEST with data pointer
  to return value at stack slot 0
- Invoke context on callee entry JUMPDEST with target and
  argument pointers

Includes tests verifying context emission for single-arg,
multi-arg, nested, and void function call scenarios.

* bugc: add source maps for call setup instructions (#188)

The call setup sequence (POP cleanup, PUSH return address,
MSTORE, push arguments, PUSH function address) was using
remark-only debug contexts, leaving these instructions
unmapped in tracing output. Now threads the call
terminator's operationDebug (which carries the source
code range for the call expression) through all setup
instructions, matching how other instruction generators
use operationDebug.

* Add function call tracing documentation (#187)

* Add function call tracing documentation

Add invoke/return/revert context documentation across concept,
reference, and spec pages:

- concepts/programs.mdx: new "Function call contexts" section
  explaining the three context types with a SchemaExample
- tracing.mdx: walkthrough of tracing through an internal function
  call (Adder contract), plus external call and revert examples
- Spec pages: added intro prose to function.mdx, return.mdx,
  revert.mdx

* Make function call tracing example interactive

Replace the static BUG code block with an interactive TraceExample
component that lets readers compile and step through the Adder
contract, seeing invoke/return contexts at function boundaries.

Static SchemaExample blocks are kept for the narrative walkthrough
and for external call/revert examples (which BUG can't demonstrate).

* Fix BUG source indentation in tracing examples

Add 2-space indentation inside block bodies (storage, create, code,
if) to match the canonical style used in .bug example files.

* Revert BUG indentation to match prettier formatting

Prettier strips indentation inside template literal strings in MDX
JSX props. Revert to the unindented style that prettier enforces.

* docs: move 4th BUG example into tracing-examples.ts

Move the "Function call and return" (Adder) example into
tracing-examples.ts with proper indentation, matching the
pattern established for the other three examples.

* docs: add invoke.mdx prose intro and cross-link spec pages (#192)

Add a descriptive introduction to the invocation contexts spec
page, matching the pattern of function.mdx, return.mdx, and
revert.mdx. Add cross-links between the function context spec
pages and the tracing documentation.

* bugc: use format type guards in call context tests (#194)

Validate that compiler output satisfies @ethdebug/format
type guards (Context.isInvoke, Context.isReturn,
Invocation.isInternalCall) instead of casting through
Record<string, unknown>. Adds a typed
findInstructionsWithContext helper that filters by mnemonic
and type guard, providing proper type narrowing.

* bugc: add declaration source ranges and param names to invoke/return contexts (#196)

The invoke and return contexts emitted by bugc now include
declaration source ranges (pointing to the function definition)
and named argument pointers (using parameter names from the
function signature). This enriches debug info so debuggers can
link call stack entries to source declarations and display
meaningful parameter names.

Changes:
- Add loc/sourceId fields to Ir.Function, populated during
  IR generation from AST function declarations
- Thread module.functions map through EVM codegen so call
  terminators can look up target function metadata
- Build declaration source ranges on invoke contexts (caller
  JUMP, callee entry JUMPDEST) and return contexts
  (continuation JUMPDEST)
- Add parameter names to argument group pointers
- Update call-contexts tests for new fields

* bugc: add debug contexts to all unmapped bytecodes (#190)

* bugc: add debug contexts to all remaining unmapped bytecodes

Thread remark/code contexts through all compiler-generated
instructions that previously lacked debug info:

- Free memory pointer initialization (remark)
- Return value spill after call continuation (call expr source range)
- STOP guard between main and user functions (remark)
- Function prologue MSTORE for param storage (thread existing remark)
- Function prologue return PC save sequence (thread existing remark)
- Deployment wrapper CODECOPY+RETURN (remark)

All 82 instructions across runtime and create programs now
carry debug contexts (previously 22 were unmapped).

* bugc: add code contexts with source ranges to compiler-generated instructions

Add source location info (loc, sourceId) to Ir.Function so EVM
codegen can build code contexts for compiler-generated instructions.

Instructions that map to a source location now use gather contexts
combining both a remark (for debugger tooling) and a code context
(for source highlighting):
- Free memory pointer init → code block / create block range
- Function prologue (param stores, return PC save) → function decl range
- STOP guard → code block range

Deployment wrapper remains remark-only (no corresponding source).
Return value spill already had correct source mapping (call expr).

* Add call stack breadcrumb and call info panel (#189)

* Add call stack breadcrumb and call info panel components

Surface invoke/return/revert context information in the
trace viewer: a breadcrumb showing the current call stack,
and a panel showing call details with async-resolved
pointer ref values.

New components: CallStackDisplay, CallInfoPanel
New utilities: extractCallInfoFromInstruction, buildCallStack,
  buildPcToInstructionMap
New types: CallInfo, CallFrame, ResolvedCallInfo,
  ResolvedPointerRef

* Integrate CallStackDisplay and CallInfoPanel into TraceViewer

The components were exported from programs-react but never
rendered in the web package's TraceViewer. Add them to the
layout: call stack breadcrumb in the header, call info
panel at the top of the right sidebar.

* Add call stack breadcrumb and call info banner to TraceDrawer

The deploy preview uses TraceDrawer (not TraceViewer) for the
interactive trace playground. Add call context display directly
to TraceDrawer: a breadcrumb bar showing nested call frames
with clickable navigation, and a colored banner showing
invoke/return/revert status at the current step.

* Show always-visible call stack and fix duplicate frame bug

- Call stack bar now always visible with "(top level)" empty
  state so users know the feature exists
- Fix duplicate call stack frames: compiler emits invoke
  context on both the caller JUMP and callee entry JUMPDEST,
  so skip push if top frame already matches the same call
- Applied fix to both TraceDrawer and programs-react utility

* docs: expand return and revert spec pages (#193)

Add subsections to return.mdx covering internal returns,
external call returns, the success field, and data pointer.
Add subsections to revert.mdx covering reason-based reverts,
panic codes, and field optionality. Both pages now match the
depth of invoke.mdx.

* bugc-react: surface invoke/return/revert contexts in BytecodeView (#195)

Add context-aware rendering for function call debug contexts
in the bytecode disassembly view. Instructions with invoke,
return, or revert contexts now show colored badges and inline
labels instead of the generic info icon, with colored left
borders to make call boundaries visually scannable.

- Add classifyContext/summarizeContext utils for extracting
  human-readable info from debug context objects
- Replace generic info icon with arrow/return/x badges for
  invoke/return/revert instructions
- Show inline context labels (e.g. "invoke add", "return add")
- Add structured tooltip headers before raw JSON
- CSS for context badges, labels, and row highlighting

* bugc: map return epilogue instructions to source location (#197)

The function-return PUSH/MLOAD/JUMP sequence previously had
remark-only debug context. Now uses the IR return terminator's
operationDebug, which carries the source location of the
return statement. This lets debuggers map the epilogue back
to the BUG source.

* ui: named arguments, click-to-source, and call stack params (#198)

Show named argument parameters throughout the UI:

- BytecodeView labels show "invoke add(a, b)" instead of
  "invoke add" when argument names are available in the
  invoke context's pointer group entries
- Call stack breadcrumbs show "add(a, b)" instead of "add()"
  in both programs-react CallStackDisplay and web TraceDrawer
- CallInfoPanel banner shows "Calling add(a, b)"

Add click-to-source for context badges:

- BytecodeView accepts onDeclarationClick callback
- Clicking an invoke/return/revert badge with a declaration
  field fires the callback with sourceId, offset, and length
- Falls back to pinning the tooltip if no declaration

Extract declaration and argument names from contexts:

- summarizeContext now returns argumentNames and declaration
- New formatCallSignature utility for consistent formatting
- New DeclarationRange type for click-to-source data
- CallFrame and CallInfo types carry argumentNames

* Fix typos in type concepts and add missing CSS classes (#199)

Fix "disinction" typo and "languages that whose" grammar
error in type concepts spec page. Add missing CSS class
definitions for .call-stack-empty-text and
.call-info-ref-value in both programs-react and web theme.

* docs: fix broken anchor in type concepts page

Update internal link to match renamed heading
"Type specifiers, wrappers, and references".

* bugc: fix multi-block function return values (#200)

Functions with if/else branches returning from both sides
produced wrong return values. The branch terminator's
loadValue(condition) DUPs the condition onto the stack;
JUMPI consumes the dup but the original remains. Successor
blocks inherit this stale value, so the return epilogue
incorrectly treated it as the return value.

Add generateReturnEpilogue that loads the return value,
cleans stale stack entries via SWAP+POP, then loads the
saved return PC and jumps back. Uses the same imperative
state pattern as generateCallTerminator.

Also changes generateTerminator return type from
Transition<S, S> to Transition<S, Stack> to accommodate
the stack-cleaning epilogue.

* bugc: add runtime call stack for recursion support (#201)

Implement FMP-based call frames so functions can call themselves
recursively. Each call allocates a frame (saved FP, saved return
PC, locals) from the free memory pointer; returns deallocate by
restoring FP and FMP.

Also fixes pre-existing SUB/DIV/MOD operand ordering bug where
non-commutative operations computed right op left instead of
left op right.

* docs: add recursive function call tracing example (#202)

Add a recursive count/succ example to the tracing docs,
showing how nested invoke/return contexts appear when
functions call themselves repeatedly.

* Fix call stack display for recursive function calls (#203)

The call stack dedup logic compared only function name and
call type, causing recursive calls (e.g. count -> count) to
be collapsed into a single frame. Now also checks whether the
previous frame was pushed on the immediately preceding step,
which distinguishes the compiler's duplicate invoke contexts
(caller JUMP + callee JUMPDEST on consecutive steps) from
genuine recursive calls (same name but steps far apart).

Fixed in both programs-react buildCallStack and web
TraceDrawer's duplicated call stack logic.

* Add source-level stepping to trace viewer controls (#204)

Use outline triangles (◁/▷) for single-step and filled
triangles (◀/▶) for source-level stepping, alongside
skip-to-start (⏮) and skip-to-end (⏭) controls.

* docs: replace recursive example with isEven/isOdd mutual recursion (#205)

Replace the succ/count recursive example with an isEven/isOdd
mutual recursion example, which better demonstrates alternating
invoke/return contexts between two functions.

* bugc: add skipped test for optimizer recursion bug

Nested call arguments (e.g. count(succ(n), target)) fail at
optimizer level 2+. Add a skipped test to track the issue.

* Resolve argument values in call stack display (#206)

Store argument pointers on call stack frames and resolve
them against historical machine state at each frame's
invoke step. Display as "add(a: 3, b: 4)" in both
CallStackDisplay and TraceDrawer breadcrumbs.

When the compiler emits duplicate invoke contexts (caller
JUMP + callee JUMPDEST), use the callee entry step for
resolution since argument pointers reference stack slots
valid at the JUMPDEST, not the JUMP.

Fix stack peek indexing in both traceStepToMachineState
and traceStepToState: depth 0 should read from the top
of the stack (last array element), not the bottom. The
evm package stores stacks bottom-first.

Values are cached by step index to avoid re-resolving
unchanged frames. Small values (<=9999) display as
decimal, larger values as hex.

* bugc: fix optimizer for nested call arguments in recursion (#208)

Move phi resolution from target blocks to predecessor blocks
(standard phi deconstruction). The previous approach resolved
phis at the target using the layout-order predecessor, which
broke for back-edges where the runtime predecessor differs.

Also fixes jump optimization eliminating phi-bearing blocks
and rewrites TCO to use a pre_entry trampoline with phis on
the original entry block.

* format: fix invoke schema pointer semantics and examples (#207)

* format: fix invoke schema pointer semantics and examples

Align invoke context examples with the "following execution"
semantics defined by instruction.schema.yaml. The internal call
example previously referenced pre-JUMP stack state (destination
at slot 0, arguments at slots 2-3), but JUMP consumes its
destination operand. Fix the example to mark the callee's entry
JUMPDEST with the correct post-JUMP stack layout: return label
at slot 0, arguments at slots 1-2, and target as a code pointer
to the function's entry offset.

Also fix the CREATE2 example: salt was incorrectly shown at
stack slot 1 instead of slot 3 (the EVM pops value, offset,
length, salt in that order).

Add clarifying text to the schema description and spec page
explaining when internal call contexts use callee JUMPDEST
placement vs. external call/create contexts on the instruction
itself, and how stack-based pointers reference pre-execution
state for instructions that consume their operands.

* format: clarify context semantics — facts vs pointer state

Resolve the tension between "following execution" semantics and
pointer evaluation. The instruction schema's "following execution"
describes when a context's semantic facts hold (e.g., "a function
was invoked"), not the machine state that pointers reference.
Pointers reference the state at the instruction's trace step —
the state a debugger observes when it encounters the instruction.

Update instruction.schema.yaml to make this two-level model
explicit. Simplify the invoke schema description and spec page
to present this cleanly rather than restating the contradiction.

* chore: fix formatting

* bugc: update invoke contexts per spec fix (#207) (#209)

* bugc: update invoke contexts per spec fix (#207)

- Callee JUMPDEST: full invoke with arg pointers and code
  target (resolved at module-level patching)
- Caller JUMP: identity + code target only, no arg pointers
- Target pointers changed from stack to code location

* fix: use Pointer.Region.isCode type guard in tests

* programs-react: propagate arg names from callee JUMPDEST

The invoke spec change moved argument pointers from the
caller JUMP to the callee entry JUMPDEST. The call stack
builder was updating argumentPointers from the duplicate
callee step but not argumentNames, causing names to show
as _0 instead of the actual parameter name.

* web: propagate arg names from callee JUMPDEST in TraceDrawer

Same fix as the programs-react mockTrace.ts change — the
duplicated call stack builder in TraceDrawer.tsx also needs
to update argumentNames from the callee entry step, not
just argumentPointers.
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