EIP-2315 Simple Subroutines for the EVM

I have asked this on the gitter channel a while ago, but it seems it was never recorded anywhere.

EIP-615 had a static version of JUMPSUB, e.g. the destination is an immediate. This posed a problem, to which versioning was proposed, and ultimately led to the then-rejection of EIP-615.

EIP-2315 has a dynamic version of JUMPSUB, e.g. takes the destination from the stack.

I would like to see a static version of this in the future and hence asked to consider that here and design the proposal with that in mind (to be consistent), that is to have both the static and the dynamic version of JUMPSUB, but not implement the static version of the opcode now.

If subroutines are dynamic jump themselves how is disabling other kinds of dynamic jumps helping?

What are the proposed gas costs of the new opcodes? The current version doesn’t seem to contain them.

I wasn’t clear hear, @axic, sorry. It would be up to external tools to enforce stronger guarantees, such as only constants being pushed before all kinds of jumps.

This proposal is not directly compatible with EIP-615 because without a version change arguments have to go on the stack. But given that EIP-615 will require a version change that shouldn’t be difficult to deal with in a few different ways, including changing how arguments are passed to the JUMPSUB and BEGINSUB opcodes, or adding new opcodes, or having the validation phase enforce guarantees rather than restrict the opcodes themselves.

They are there, under the Implementations section.

We suggest that the cost of BEGINSUB be base , JUMPSUB be low , and RETURNSUB be verylow . Measurement will tell. …

I slightly disagree. Introducing a new multibyte opcode is IMO not a huge problem. However, EIP-615 included a lot of assumptions about code, such that a runtime jump analysis should not be needed (because of deploy time validation), or a double-pass of a much more complicated analysis would have to be made during runtime.

If we were to introduce e.g. JUMPSUB 0x00ff (defining it as in total three bytes), I don’t see any huge problems with that … ?

I thought one of the main reasons (apart from the complexity of the EIP – to which one solution offered was to split it up) was exactly the multi-byte opcode problem, description starts here (also check the example from @gumb0): EIP-663: Unlimited SWAP and DUP instructions - #10 by chriseth

Thanks! I think that definitely doesn’t belong under the Implementation section, but rather Specification.

Right – so it’s definitely the case, that old jumpdest analysis will no longer be valid, and the execution flow of a contract may change. I expect that to not actually be an issue for any real-world usecase, maybe with the exception of people use an on-chain “purity” verifier to attest that “this code will never do a DELEGATECALL”. It might be argued that such a verifier should reasonably exit on an opcode which it does not recognize.

EIP-615 contained statements about what was allowed and what was not allowed: the validity of code, and jumps. Whereas 2315 does not make any assumptions/certifications on the validity of code, so from that perspective it does not require versioning.

The only reason some might think versioning is a good thing, would be if we believe that it would cause problems for existing contracts. And I have yet to be convinced that a new multibyte instruction would cause be a problem in practice.

I’m not particularly advocating for it, though, just speculating.

Hi
Looking at the description of the (eip-2315) it seem that there is a contradiction between the test cases and the algorithm.
It is said that during the JUMPSUB we store PC + 1 in the RStack. And this is confirmed in Note 2 (A value popped from return_stack may be outside of the code length, if the last JUMPSUB was the last byte of the code).

But in the test cases (for example 0x6004b300b2b7) we can see that it is PC which is stored in the stack (and not PC + 1)

2 - JUMPSUB - 8 - [4] -[]
4 - BEGINSUB - 1 - [] - [2]

For me with the algorithm we should have 3 in RStack and not 2.

2 - JUMPSUB - 8 - [4] -[]
4 - BEGINSUB - 1 - [] - [3]

Can you tell me if it is an issue or if I missed something ?

Thanks

You are technically correct,but it’s not an issue. In geth, I implemented it differently than the spec states: instead of adding PC+1, I put PC there. And later on, the actual jump goes to PC+1. Unfortunately, this little implementation detail leaks out in the example traces. However, the important thing is that the behaviour of the code is consistent with the semantics of the EIP.

I realize that the example traces are “wrong”, and I will update either the EIP or the go-ethereum repo.

In essence, the EIP should specify exactly the observable behaviour of the EVM, and leave the internal represenation up to node implementors. So in that sense, the return_stack is not necessarily represented by an actual stack in reality.

Please see https://github.com/ethereum/EIPs/pull/2599 my proposed update

1 Like

Can any JUMP* go into any subroutine? Also can JUMP from within a subroutine go into another subroutine?

What happens in the case of:

JUMPSUB
JUMP (and jumping into another subroutine)
RETURNSUB

One would assume RETURNSUB picks up the last item from the return_stack. However from an analysis point of view in this case a RETURNSUB within a subroutine block may not only mean returning from that block, but returning from within some other block.

Yes, RETURNSUB must always return to the address last pushed on the stack and pop it @axic, regardless of the block it’s in. So in your example the RETURNSUB is unreachable, and the code will return to the JUMPSUB when and if it hits some other RETURNSUB. I think that will amount to a loop until the code jumped to stops somehow.

This proposal doesn’t constrain the structure of the code much at all, it just provides an efficient mechanism.

If subroutines are dynamic jump themselves how is disabling other kinds of dynamic jumps helping?

Other kinds of dynamic jumps aren’t disabled by this proposal. It only provides a more efficient alternative way to implement subroutines.

Sorry I had a typo in my example. I meant:

BEGINSUB
JUMP <next>
RETURNSUB (1)

next:
JUMPDEST
RETURNSUB (2)

And then (2) returns to where BEGINSUB was first invoked.

Yes. So in that way, it’s possible to implement the type of tail recursion I talked about here: EIP-2315 Simple Subroutines for the EVM

The analysis of the EIP with two proposed changes: EIP-2315 "Simple Subroutines for the EVM" analysis. By @chfast, @gumb0 and @axic.

We decided to publish in separately because of the length of it and wish to receive direct responses to the proposed changes.

1 Like

Here is a preliminary implementation to use subroutines for yul functions:


It needs much more work to get it properly working, but maybe you can already use the generated bytecode for testing.

Here is the asm.js binary: https://313936-40892817-gh.circle-artifacts.com/0/soljson.js

1 Like

Thanks, Christian.
(20must20)

If I see correctly this kind of relies/follows the recommendations from EIP-2315 "Simple Subroutines for the EVM" - Analysis, because it relies on not-flowing into beginsub (at least in one place in the assembler)