EIP-7979: Call and Return Opcodes for the EVM

EIP-7979: Call and Return Opcodes for the EVM

This is the smallest possible 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 top of the stack..
  • ENTERSUB marks a CALLSUB destination.
  • RETURNSUB returns to the PC after the most recent CALLSUB.

These changes are backwards-compatible:

Update Log

External Reviews

Outstanding Issues

yyyy-mm-dd: Issue description, link to issue

7 Likes

This EIP is great! Puts an important part which EVM is missing that can have immediate benefits, reducing gas costs with backwards compatibility. This is awesome.

The main question right now (and the reason it’s still a draft PR) is whether we need an ENTERSUB code. You can find them all in one pass by marking the destinations of JUMPSUB, but that’s the hard way.

ENTERSUB means a little bit more development time, but it’s a worthwhile addition. It won’t add any new runtime behaviour, it just makes the structure explicit.

Bottom line, it’s a minimal addition with a lot of benefit.

Yes – it amounts to a form of JUMPDEST. It’s also not clear that a validation algorithm can be made to work efficiently without it. Unfortunately Max – the compiler expert who was working with me on that algorithm – died a few weeks ago.

Damn that’s terrible news, I’m sorry to hear about Max.

ENTERSUB makes even more sense now, if the validation algorithm becomes harder with it, then its an almsot obvious decision.

I’m not a compiler expert but I’d like to help however I can. Let me know if there’s a way I can move this forward.

Thanks.

What some people seem not to understand (and I don’t blame them) is the basics of control flow graphs, why they can be an essential tool for static analysis, how to extract them from machine code, why dynamic jumps can make extracting them go quadratic, and why – unlike most applications – we need to extract them in linear time. And of course – given that static analysis is anything you can do to code without actually executing it – how many important things are forms of static analysis, including symbolic execution, translating EVM stack code to register code for faster interpretation and compiling to machine code, proofs of EVM code correctness and more. There is an entire academic literature devoted to working around the problems caused by dynamic jumps.

All this amounts to two or three university courses, some of which I never took – I finished school in 1982 and learned most of what I do know on the job. So I’m not the best person to explain all of this, let alone briefly enough to fit in an EIP.

Honsetly this gave me a much clearer view of what it can do. I didn’t study control flow graphs formally either, but the way you framed it makes a good case for structured control flow and ENTERSUB.

I’d love to help maybe I can write a short draft that explains the CFG/static analysis angle in layman’s terms as supporting context for people(like me haha) so it’s easier to understand. Let me know if that’s useful happy to contribute however I can

I support this EIP, as Ruby puts it, minimal addition with a lof of benefits!=.

1 Like

It feels like ENTERSUB behaving as a JUMPDEST just complicates static analysis, and I’m not sure the Rationale’s note does a good enough job explaining why that is desired behaviour:

[…] an ENTERSUB marks an entry, where the baseline resets and a jump eliminates a call. Without the distinction, the validator could not tell a merge from an entrance.

If someone wants to allow jumps and subroutines to the same “place”, then they could do a JUMPDEST followed by an ENTERSUB, making the added analysis complexity more visible.


The mnemonic ENTERSUB is a verb, when the actual behaviour is more a place/label. JUMPDEST is a noun.

I’d recommend naming it SUBROUTINE (or some contraction) instead because that doesn’t imply taking an action.

Thanks for the feedback @SamWilsn. Things have changed a bit since this merge and it will be a few days before the new version is ready. Your points are well taken and I’ll try to incorporate them.

It feels like ENTERSUB behaving as a JUMPDEST just complicates static analysis, and I’m not sure the Rationale’s note does a good enough job explaining why that is desired behaviour:

At this point the core validation function is less than 100 lines of Python, so complexity isn’t an issue, but conceptual clarity is.

The ability to jump into subroutines when it is safe to do so is essential to important optimizations, and the newest Rationale is specific about that:

A JUMP to an ENTERSUB enters the subroutine without pushing a return address: where a CALLSUB would be the last action before a RETURNSUB, the jump does the same work and the call is eliminated. Call elimination supports tail recursion, mutual recursion and state machines, shared epilogues, and the outlining of repeated code — the transformations optimizing compilers rely on.

[…] an ENTERSUB marks an entry, where the baseline resets and a jump eliminates a call. Without the distinction, the validator could not tell a merge from an entrance.

If someone wants to allow jumps and subroutines to the same “place”, then they could do a JUMPDEST followed by an ENTERSUB, making the added analysis complexity more visible.

They could, but this amounts to de-optimizing an optimization.

The mnemonic ENTERSUB is a verb, when the actual behaviour is more a place/label. JUMPDESTis a noun.

I’d recommend naming it SUBROUTINE (or some contraction) instead because that doesn’t imply taking an action.

Good point. SUBROUTINE doesn’t seem right, as it obscures the fact that subroutines need not be contiguous stretches of code and can have multiple entry points, and doesn’t make clear that it marks a destination.

CALLDEST might be good. Makes clear that it’s the same sort of label as a JUMPDEST, so it’s less surprising that you can only jump to a JUMPDEST, but you can call or jump to a CALLDEST.

Having a third CALLORJUMPDEST or such would be a mistake – nothing different happens at the CALLDEST site when you jump to it rather than call it, so it shouldn’t need to be changed just because some caller decided to optimize a call to a jump. It’s the validators job to tell whether the optimization is safe.

The validation angle makes sense, but there’s another perspective worth considering - explicit entry points are what debuggers and formal verification tools need to work cleanly, otherwise every tool ends up reconstructing the structure itself. Just having it at the instruction level is cleaner.

1 Like