---
eip: 7979
title: Call and Return Opcodes for the EVM
description: Three new instructions to support calls and returns.
author: Greg Colvin (@gcolvin) <greg@colvin.org>, Martin Holst Swende (@holiman), Brooklyn Zelenka (@expede), John Max Skaller
discussions-to: https://ethereum-magicians.org/t/eip-7979-call-and-return-opcodes-for-the-evm/24615
status: Draft
type: Standards Track
category: Core
created: 2025-12-17
---

## Abstract

This is a minimal change to the EVM to support calls and returns.

This proposal introduces three new control-flow instructions to the EVM:

* `CALLSUB` transfers control to the *destination* on the *data stack*.
* `CALLDEST` marks a subroutine entry: the destination of a `CALLSUB`, or of a `JUMP` that eliminates a call.
* `RETURNSUB` returns to the *PC* after the most recent `CALLSUB`.

These changes are backwards compatible: the instructions behave identically wherever they appear.

*Note: Significant assistance from Anthropic's Claude is acknowledged, primarily for the reference implementation and its tests.*

## Motivation

The EVM currently lacks explicit call and return instructions.  Instead, calls and returns must be synthesized using the dynamic `JUMP` instruction, which takes its destination from the stack.  This creates two fundamental problems:

* **Inefficiency**: Synthesizing calls and returns with jumps wastes bytecode space and gas.
* **Complexity**: More important, dynamic jumps obscure the flow of control.  Explicit calls and returns make the call structure of code visible — to people, to tools, to compilers — where synthesized jumps bury it.  A companion proposal, *Validated EVM Code*, builds on that visibility: code that forgoes dynamic jumps entirely can be proven safe at `CREATE` time.  Its constraints need consensus of their own, and are not specified here.

## Specification

> *The key words MUST and MUST NOT in this Specification are to be interpreted as described in RFC 2119 and RFC 8174.*

The EVM's machine state includes a *data stack* of 256-bit words, at most 1024 deep, and the program counter, *PC*, whose value is a position in the code — the index of the next byte to execute.  This EIP adds a *return stack* of return addresses, pushed only by `CALLSUB`, popped only by `RETURNSUB`, and not otherwise accessible to EVM code.  (For readers of the Yellow Paper, and only here: the machine state is μ, the *data stack* μ_s, its depth |μ_s|, and the program counter μ_pc; the *return stack* is new, μ_r.)

### `CALLSUB (0x..)`

Transfers control to a subroutine.

1. Pop the *destination* from the top of the *data stack*.
2. Push the position *PC* + 1 to the *return stack*.
3. Set the *PC* to the *destination*.

If the *destination* is not a `CALLDEST`, or the *return stack* already
holds 1024 items, execution is in an exceptional halting state.

The gas cost is *mid* (8).

### `CALLDEST (0x..)`

Marks a subroutine entry.  Like `JUMPDEST`, it is otherwise a no-op:
execution falls through.  The *destination* of every `CALLSUB` MUST be a
`CALLDEST`.

A `CALLDEST` is also a valid `JUMP` and `JUMPI` destination.  Jumping to
one enters the subroutine without pushing to the *return stack*, so its
`RETURNSUB` returns to the original caller.

The gas cost is *jumpdest* (1).

### `RETURNSUB (0x..)`

Returns control to the most recent caller.

1. Set the *PC* to the position popped from the *return stack*.

If the *return stack* is empty, execution is in an exceptional halting
state.

The gas cost is *low* (5).

*Notes:*

* *Values popped off the return stack do not need to be checked, since they are alterable only by `CALLSUB` and `RETURNSUB`.*
* *The return stack describes the semantics; its actual state is not observable by EVM code, nor consensus-critical.  An implementer may, for example, push the PC rather than PC + 1, so long as `RETURNSUB` observably returns control to PC + 1.*
* *Opcode values are still to be determined.*

### Costs

A *mid* cost for `CALLSUB` is justified by it taking very little more work than the *mid* cost of `JUMP` — just pushing an integer to the *return stack*.

A *jumpdest* cost for `CALLDEST` is justified by it being, like `JUMPDEST`, a mere label.

A *low* cost for `RETURNSUB` is justified by needing only to pop the *return stack* into the *PC* — less work than a jump.

Benchmarking will be needed to tell if the costs are well-balanced.

### Validation

This proposal specifies runtime semantics only.  A companion proposal — *Validated EVM Code*, drafted alongside this one — defines code that is proven at `CREATE` time to have fully static control flow: no invalid instructions, no invalid destinations, no underflow, and within each subroutine one stack depth per instruction, the same on every execution.  Its constraints are not specified here: they need consensus of their own, and they should be written once, against the full set of control-flow instructions, including future static relative jumps and calls.  What this proposal guarantees is that validation remains possible: `CALLSUB` halts unless its destination is a `CALLDEST`, so every subroutine entry is labeled in the code, today, whether or not the code is ever validated.

## Rationale

### Why no immediate arguments or code sections?

Primarily **backwards compatibility**.  Other reasons include:

* *Immediate arguments* for jump destinations would improve performance but increase the complexity of instruction encoding.
* *Code sections* or other structural constraints would impose syntactic restrictions that inhibit optimization.

The EVM Object Format took the complementary path — function descriptors in code sections, immediate arguments for relative jumps within sections — and needed special-purpose opcodes to keep important uses of cross-subroutine jumps.  A follow-on to this proposal provides static relative jumps and calls, with immediate arguments.

### Why are `CALLSUB`, `CALLDEST`, and `RETURNSUB` allowed in ordinary code?

Primarily **backwards compatibility**.  The same code must run unaltered whether or not it is ever validated.  These instructions also have legitimate uses in code that keeps dynamic jumps: a compiler might make good use of calls and returns, but still need dynamic jumps to implement efficient virtual functions.  Static jump tables, in a follow-on proposal, will serve that need.

### Why may `JUMP` land on a `CALLDEST`?

So that compilers can eliminate calls.  Where a call would be the last action before a return, a jump does the same work with no return address pushed:

```
f: CALLDEST              f: CALLDEST
   ...                      ...
   PUSH g                   PUSH g
   CALLSUB                  JUMP
   RETURNSUB
g: CALLDEST              g: CALLDEST
   ...                      ...
   RETURNSUB                RETURNSUB
```

On the left, `g` returns to `f`, which returns to its caller.  On the right, `g`'s `RETURNSUB` returns directly to `f`'s caller: one instruction shorter, one return address fewer — and where `g` is `f` itself, or calls back into it, the recursion runs at constant return-stack depth instead of halting at 1024.  Compilers rely on this transformation, for tail calls, mutual recursion, state machines, and shared epilogues.  And the jump is no wilder than the call it replaces: it lands on the same label.

### Why these three instructions?

This proposal aims to be a minimal change to the EVM.  We introduce two abstract operations — call and return — implemented by three instructions: `CALLSUB`, `CALLDEST`, and `RETURNSUB`.  These suffice to eliminate the need for dynamic jumps.

### Why the return-stack mechanism for calls and returns?

Register machines like x86, ARM, and RISC-V typically use a single stack and dedicated registers for both data and return addresses.  Stack machines like Turing's ACE, Forth, the JVM, Wasm, and .NET use separate data and return stacks.  The EVM is a stack machine, and we adopt the same proven approach: a separate *return stack* isolated from the *data stack*.  Another reason to maintain a separate stack is that *data stack* items are 32 bytes, but jump destinations will not need more than one or two.

#### Safety advantages of the *return stack*

The return addresses, being on their own stack, are not accessible to EVM code.  They cannot be read, modified, or moved by ordinary stack operations.  This eliminates an entire class of vulnerabilities where code could corrupt its own control flow.

Because return addresses are controlled exclusively by `CALLSUB` and `RETURNSUB`, they are intrinsically safe to validate.  Unlike data-stack values (which may depend on arbitrary computation), return-stack values are guaranteed to be valid *PC* values — we can validate all return addresses at compile time.

### Are there code size and gas savings?

The difference these instructions make can be seen in this very simple code for calling a routine that squares a number.  The distinct opcodes make it easier for both people and tools to understand the code, and there are modest savings in code size and gas costs as well.

```

SQUARE:                           |       SQUARE:
    jumpdest       ; 1 gas        |           calldest       ; 1 gas
    dup            ; 3 gas        |           dup            ; 3 gas
    mul            ; 5 gas        |           mul            ; 5 gas
    swap1          ; 3 gas        |           returnsub      ; 5 gas
    jump           ; 8 gas        |
                                  |
CALL_SQUARE:                      |       CALL_SQUARE:
    jumpdest       ; 1 gas        |           calldest       ; 1 gas
    push RTN_CALL  ; 3 gas        |           push 2         ; 3 gas
    push 2         ; 3 gas        |           push SQUARE    ; 3 gas
    push SQUARE    ; 3 gas        |           callsub        ; 8 gas
    jump           ; 8 gas        |           returnsub      ; 5 gas
RTN_CALL:                         |           stop           ; 0 gas
    jumpdest       ; 1 gas        |
    swap1          ; 3 gas        |
    jump           ; 8 gas        |
    stop           ; 0 gas        |
                                  |
Size in bytes: 17                 |      Size in bytes: 12
Consumed gas:  50                 |      Consumed gas:  34

```

That's 29% fewer bytes and 32% less gas using `CALLSUB` versus using `JUMP`.  So we can see that these instructions provide a simpler, more efficient mechanism.  As code becomes larger and better optimized the gains become smaller, but code using `CALLSUB` always takes less space and gas than equivalent code without it.

### Are there real-time performance gains?

Some real-time interpreter performance gains are reflected in the lower gas costs.  But larger gains come from AOT and JIT compilers.  In validated code (see the companion proposal), within each subroutine the depth of the stack at each instruction is the same on every execution, so a JIT can traverse the control flow in one pass, generating machine code on the fly, and an AOT can emit better code in linear time.  (The Wasm, JVM, and .NET VMs share this property.)

The EVM is a stack machine, but most real machines are register machines.  Both routes are measured in this proposal's assets (`rv64/`): retired RISC-V instructions, counted exactly, on two kernels bracketing the workload space — an arithmetic-heavy loop and a call-heavy tree.  Three tiers of execution, at 256-bit and 64-bit word widths:

| kernel    | interpreted, 256 | interpreted, 64 | register IR, 256 | register IR, 64 | AOT, 256 | AOT, 64 |
|-----------|-----------------:|----------------:|-----------------:|----------------:|---------:|--------:|
| mul chain | 6122597 | 4492253 (1.4x) | 4081563 (1.5x) | 1856126 (3.3x) | 1315924 (4.7x) | 120641 (50.8x) |
| call tree | 7833101 | 7360257 (1.1x) | 3669063 (2.1x) | 2450626 (3.2x) | 1119924 (7.0x) | 405641 (19.3x) |

The first column is the status quo: legacy bytecode, interpreted.  The second adds 64-bit arithmetic instructions alone — dispatch dominates an interpreter, so they barely show.  The middle columns interpret a register intermediate code, translated once at deploy from code that validates under the companion proposal: slots become numbered registers, pushes fuse into branches, no destination checks or stack bookkeeping survive — the path for clients that will never JIT, and gains that any EVM-compatible chain collects without RISC-V.  The last columns compile that same validated code ahead of time to RISC-V, removing the dispatch as well; composed with 64-bit instructions this beats the product of the two proposals alone, because a stack slot becomes a machine register only when its offset is proven static *and* its value fits the register.  Every cell meters gas — per operation when interpreting bytecode, per basic block after translation — and keeps the runtime overflow and depth checks.  These are floors, from deliberately naive translators.

However, for most transactions, storage dominates execution time — it is outside these kernels — and gas counting and other overhead always take their toll.  So such gains would be most visible in contexts where overhead is minimal, such as L1 precompiles, some L2s, and some EVM-compatible chains.

### Does ZK-rollup efficiency improve?

The zkVMs that dominate current practice prove RISC-V execution, so the answer is measurable in one of them.  The same twelve binaries as above, executed in Zisk, Polygon's 64-bit RISC-V zkVM, count *steps* — the unit its prover pays for — equal in every cell to the instruction count in the table above plus a constant 444 steps of startup.  The ratios reproduce to three digits: for an RV64 zkVM the table above gives proving-cost figures, not a proxy for them.  Details and reproduction under `rv64/` in the assets.

## Backwards Compatibility

These changes are backwards compatible.  The new opcodes behave identically wherever they appear, and there are *no changes* to the semantics of existing EVM code.  (With the caveat that code with unspecified behavior might behave in different, unspecified ways.  Such code was always broken.)  Implementation can come down to a push and a jump to call, and a pop and another jump to return.

These changes do not preclude running the EVM in zero knowledge; neither do they foreclose EOF, RISC-V, or other changes.

## Test Cases

*Note: the bytecode strings in these tests use placeholder opcode values
`0xB0`=`CALLSUB`, `0xB1`=`CALLDEST`, `0xB2`=`RETURNSUB`, which are to be
confirmed when final opcode assignments are made.  The traces, gas totals,
and pass/fail outcomes are correct for the semantics defined in this EIP.*

*The Stack column shows the data stack before the instruction executes.
The RStack column shows the return stack before the instruction executes.*

### Simple routine

This should call a subroutine, return from it, and stop.

Bytecode: `0x6004B000B1B2` (`PUSH1 0x04, CALLSUB, STOP, CALLDEST, RETURNSUB`)

```
PC=0: PUSH1  imm=0x04   size=2
PC=2: CALLSUB            size=1
PC=3: STOP               size=1
PC=4: CALLDEST           size=1
PC=5: RETURNSUB          size=1
```

|  PC   |      Op     | Cost |   Stack   |   RStack  |
|-------|-------------|------|-----------|-----------|
|    0  |      PUSH1  |    3 |        [] |        [] |
|    2  |    CALLSUB  |    8 |       [4] |        [] |
|    4  |    CALLDEST |    1 |        [] |       [3] |
|    5  |  RETURNSUB  |    5 |        [] |       [3] |
|    3  |       STOP  |    0 |        [] |        [] |

Output: 0x
Consumed gas: `17`

### Two levels of subroutines

This should execute fine, going into two depths of subroutines.

Bytecode: `0x6004B000B16009B0B2B1B2` (`PUSH1 0x04, CALLSUB, STOP, CALLDEST, PUSH1 0x09, CALLSUB, RETURNSUB, CALLDEST, RETURNSUB`)

```
PC=0:  PUSH1  imm=0x04   size=2
PC=2:  CALLSUB            size=1
PC=3:  STOP               size=1
PC=4:  CALLDEST           size=1
PC=5:  PUSH1  imm=0x09   size=2
PC=7:  CALLSUB            size=1
PC=8:  RETURNSUB          size=1
PC=9:  CALLDEST           size=1
PC=10: RETURNSUB          size=1
```

|  PC   |      Op     | Cost |   Stack   |   RStack  |
|-------|-------------|------|-----------|-----------|
|    0  |      PUSH1  |    3 |        [] |        [] |
|    2  |    CALLSUB  |    8 |       [4] |        [] |
|    4  |    CALLDEST |    1 |        [] |       [3] |
|    5  |      PUSH1  |    3 |        [] |       [3] |
|    7  |    CALLSUB  |    8 |       [9] |       [3] |
|    9  |    CALLDEST |    1 |        [] |      [3,8] |
|   10  |  RETURNSUB  |    5 |        [] |      [3,8] |
|    8  |  RETURNSUB  |    5 |        [] |       [3] |
|    3  |       STOP  |    0 |        [] |        [] |

Consumed gas: `34`

### Failure 1: invalid destination

This should fail because the destination is outside the code range.

Bytecode: `0x60FFB000B1B2` (`PUSH1 0xFF, CALLSUB, STOP, CALLDEST, RETURNSUB`)

```
PC=0: PUSH1  imm=0xFF   size=2   ← destination 255, code is only 6 bytes
PC=2: CALLSUB            size=1
PC=3: STOP               size=1
PC=4: CALLDEST           size=1
PC=5: RETURNSUB          size=1
```

|  PC   |      Op     | Cost |   Stack   |   RStack  |
|-------|-------------|------|-----------|-----------|
|    0  |      PUSH1  |    3 |        [] |        [] |
|    2  |    CALLSUB  |    8 |    [0xFF] |        [] |

Error: at pc=2, op=CALLSUB: invalid destination

### Failure 2: empty return stack

This should fail at the first opcode because the *return stack* is empty.

Bytecode: `0xB2` (`RETURNSUB`)

|  PC   |      Op     | Cost |   Stack   |   RStack  |
|-------|-------------|------|-----------|-----------|
|    0  |  RETURNSUB  |    5 |        [] |        [] |

Error: at pc=0, op=RETURNSUB: invalid return stack

### Subroutine at end of code

In this example, `CALLSUB` is the last byte of code.  When the subroutine
returns, it should hit the implicit STOP after the bytecode and not exit
with error.

Bytecode: `0x600556B1B25B6003B0` (`PUSH1 0x05, JUMP, CALLDEST, RETURNSUB, JUMPDEST, PUSH1 0x03, CALLSUB`)

```
PC=0: PUSH1    imm=0x05   size=2
PC=2: JUMP                size=1
PC=3: CALLDEST            size=1
PC=4: RETURNSUB           size=1
PC=5: JUMPDEST            size=1
PC=6: PUSH1    imm=0x03   size=2
PC=8: CALLSUB             size=1  ← last byte; returns to PC=9 (past end → implicit STOP)
```

|  PC   |      Op         | Cost |   Stack   |   RStack  |
|-------|-----------------|------|-----------|-----------|
|    0  |          PUSH1  |    3 |        [] |        [] |
|    2  |           JUMP  |    8 |       [5] |        [] |
|    5  |       JUMPDEST  |    1 |        [] |        [] |
|    6  |          PUSH1  |    3 |        [] |        [] |
|    8  |        CALLSUB  |    8 |       [3] |        [] |
|    3  |       CALLDEST  |    1 |        [] |       [9] |
|    4  |     RETURNSUB   |    5 |        [] |       [9] |
|    9  | (implicit STOP) |    0 |        [] |        [] |

Consumed gas: `29`

## Reference Implementation

The runtime semantics above are minimal by design; the substantial implementation work belongs to the companion validation proposal, which provides a Python reference validator and a linear-time control-flow-graph extractor.

## Security Considerations

Return addresses live on their own stack, inaccessible to EVM code: they cannot be read, modified, or moved, which eliminates an entire class of vulnerabilities where code corrupts its own control flow.  The remaining hazards are checked at run time, as the instruction definitions specify: `CALLSUB` halts unless its destination is a `CALLDEST`, `RETURNSUB` halts on an empty *return stack*, and a `CALLSUB` that would exceed 1024 return addresses halts.  The deeper guarantee — code that provably cannot reach these halts — is the subject of the companion validation proposal.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
