Skip to content

Implement EIP-7979 CALLSUB, CALLDEST, RETURNSUB (EVMC_EXPERIMENTAL) - #1706

Draft
gcolvin wants to merge 1 commit into
ipsilon:masterfrom
gcolvin:eip-7979
Draft

gcolvin wants to merge 1 commit into
ipsilon:masterfrom
gcolvin:eip-7979

Conversation

@gcolvin

@gcolvin gcolvin commented Sep 12, 2026

Copy link
Copy Markdown

Draft. Implements EIP-7979 (proposed for Hegotá) in the Baseline interpreter, gated on EVMC_EXPERIMENTAL, so that compilers targeting the EIP have an EVM to test against. Solidity's IR pipeline can already emit these instructions under --evm-version @future (argotorg/solidity#17005), and its CI runs on evmone: with this change those tests can execute.

The instructions

opcode gas semantics
CALLSUB 0xB0 8 pop destination (must be a CALLDEST), push next position to the return stack, jump
CALLDEST 0xB1 1 subroutine entry; no-op like JUMPDEST; also a valid JUMP/JUMPI destination
RETURNSUB 0xB2 5 pop the return stack into the program counter

Return stack limit 1024 per frame. Failures: invalid destination → EVMC_BAD_JUMP_DESTINATION; return-stack overflow / underflow → EVMC_STACK_OVERFLOW / EVMC_STACK_UNDERFLOW.

Design notes

  • The code analysis computes a second bitset of CALLDEST positions in the same pass as the JUMPDEST one, sharing the same allocation.
  • Because CodeAnalysis is cached per code independently of revision, CALLDESTs are kept out of the jumpdest bitset. jump_impl consults the CALLDEST bitset only when the JUMPDEST test fails and the revision has EIP-7979, so ordinary jumps are unchanged and behaviour before the EIP is byte-for-byte what it was (a jump to a 0xB1 byte is still a bad jump destination).
  • The return stack is a std::vector<uint32_t> on ExecutionState, cleared by reset(), so it is per frame by construction.
  • The Advanced interpreter maps the three opcodes to op_undefined, as EIP-8024's instructions are.

Tests

evm_eip7979_callsub_test.cpp: the EIP's five test cases with their gas totals (17, 34, 29), invalid destinations (JUMPDEST, PUSH data, out of range, uint64 overflow, stack underflow), JUMP/JUMPI landing on a CALLDEST, RETURNSUB after an unframed jump, tail call, CALLDEST by fall-through, a value returned through a subroutine, the 1024 limit from both sides (1023-deep recursion succeeds, 1024 halts), return-stack reset between executions, and pre-EIP behaviour (undefined instruction, 0xB1 not a jump destination). All 1285 unit tests pass.

End to end

The demo contract from the Solidity PR, compiled with subroutines and run on this build with the mocked host:

compute(a, b) legacy build, Osaka subroutine build, Amsterdam subroutine build, EVMC_EXPERIMENTAL
(3, 4) 49, 1008 gas undefined instruction 49, 889 gas
(0, 0) 1, 504 gas undefined instruction 1, 454 gas
(10, 5) 245, 1176 gas undefined instruction 245, 1034 gas

EIP-7979 adds a per-frame return stack and three instructions to the EVM.
This implements them in the Baseline interpreter, available from the
EVMC_EXPERIMENTAL revision.

- CALLSUB (0xB0, 8 gas) pops a destination, which must be a CALLDEST,
  pushes the position of the next instruction onto the return stack and
  transfers control. The return stack holds at most 1024 entries.
- CALLDEST (0xB1, 1 gas) marks a subroutine entry; it is a no-op like
  JUMPDEST and, from EVMC_EXPERIMENTAL on, also a valid JUMP/JUMPI
  destination.
- RETURNSUB (0xB2, 5 gas) pops the return stack into the program counter.

The code analysis computes a second bitset of CALLDEST positions next to
the JUMPDEST one in a single pass. Because the analysis is cached per
code independently of the revision, CALLDESTs are kept out of the
jumpdest bitset; jump_impl consults the CALLDEST bitset only when the
JUMPDEST test fails and the revision has EIP-7979, so ordinary jumps are
unaffected and pre-EIP behaviour is unchanged.

Return-stack overflow and underflow report EVMC_STACK_OVERFLOW and
EVMC_STACK_UNDERFLOW; an invalid CALLSUB destination reports
EVMC_BAD_JUMP_DESTINATION.

The Advanced interpreter does not implement the instructions, as for
EIP-8024.

Tests: the EIP's five test cases with their gas totals, invalid
destinations, JUMP/JUMPI to a CALLDEST, tail call, the 1024 limit from
both sides, and pre-EIP behaviour.
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