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?
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.
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
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)
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.
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 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)