mirror of
https://github.com/status-im/EIPs.git
synced 2025-02-23 20:28:21 +00:00
Automatically merged updates to draft EIP(s) 2315 (#2655)
Hi, I'm a bot! This change was automatically merged because: - It only modifies existing Draft or Last Call EIP(s) - The PR was approved or written by at least one author of each modified EIP - The build is passing
This commit is contained in:
parent
425d7db297
commit
6611c596f5
@ -15,30 +15,33 @@ This proposal introduces three opcodes to support subroutines: `BEGINSUB`, `JUM
|
|||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
|
|
||||||
The EVM does not provide subroutines as a primitive. Instead, calls can be synthesized by fetching and pushing the current program counter on the data stack and jumping to the subroutine address; returns can be synthesized by contriving to get the return address back to the top of stack and jumping back to it. Complex calling conventions are then needed to use the same stack for computation and control flow. Code becomes harder to read and write, and tools may need to pattern-match the conventions to identify the use of subroutines. Complex calling conventions like these can be avoided using memory, but regardless, it costs a lot of gas.
|
The EVM does not provide subroutines as a primitive. Instead, calls can be synthesized by fetching and pushing the current program counter on the data stack and jumping to the subroutine address; returns can be synthesized by contriving to get the return address back to the top of stack and jumping back to it. Complex calling conventions are then needed to use the same stack for computation and control flow. Memory allows for simpler conventions but still costs gas. Eschewing subroutines in user code is the least costly -- but also the most failure-prone.
|
||||||
|
|
||||||
Having opcodes to directly support subroutines can eliminate this complexity and cost, just as for other physical and virtual machines going back at least 50 years.
|
Over the course of 30 years the computer industry struggled with this complexity and cost
|
||||||
|
and settled in on opcodes to directly support subroutines. These are provided in some form by most all physical and virtual machines going back at least 50 years.
|
||||||
|
|
||||||
In the Appendix we show example solc output for a simple program that uses over three times as much gas just calling and returning from subroutines as comparable code using these opcodes.
|
Our design is modeled on the original Forth two-stack machine of 1970. The data stack is supplemented with a control stack to provide simple support for subroutines, as specified below.
|
||||||
|
|
||||||
|
In the Appendix we show example solc output for a simple program that uses over three times as much gas just calling and returning from subroutines as comparable code using these opcodes. Actual differences in run-time efficiency will of course vary widely.
|
||||||
|
|
||||||
## Specification
|
## Specification
|
||||||
|
|
||||||
We introduce one more stack into the EVM, called `return_stack`. The `return_stack` is limited to `1023` items.
|
We introduce one more stack into the EVM, called the `return_stack`. The `return_stack` is limited to `1023` items.
|
||||||
|
|
||||||
##### `BEGINSUB`
|
##### `BEGINSUB`
|
||||||
|
|
||||||
Marks the entry point to a subroutine. Subroutines can only be entered via `JUMPSUB`. Execution of BEGINSUB causes an exception (OOG: all gas consumed) and terminates execution.
|
Marks the entry point to a subroutine. Subroutines can only be entered via `JUMPSUB`. Attempted execution of a `BEGINSUB` causes an abort: terminate execution with an `OOG` (Out Of Gas) exception.
|
||||||
|
|
||||||
pops: `0`
|
pops: `0`
|
||||||
pushes: `0`
|
pushes: `0`
|
||||||
|
|
||||||
##### `JUMPSUB`
|
##### `JUMPSUB`
|
||||||
|
|
||||||
1. Pops `1` value from the `stack`, hereafter referred to as `location`.
|
1. Pop the `location` from the `stack`.
|
||||||
- 1.1 If the opcode at `location` is not a `BEGINSUB`, abort with error.
|
2. If the opcode at `location` is not a `BEGINSUB` abort.
|
||||||
2. Pushes the current `pc+1` to the `return_stack`. (See Note 1 below)
|
3. If the `return_stack` already has `1023` items abort.
|
||||||
- 2.1 If the `return_stack` already has `1023` items, abort with error.
|
4. Push the current `pc+1` to the `return_stack`. (See Note 1 below)
|
||||||
3. Sets the `pc` to `location + 1`.
|
5. Set the `pc` to `location + 1`.
|
||||||
|
|
||||||
**Note 1:** If the resulting `pc` is beyond the last instruction then the opcode is implicitly a `STOP`, which is not an error.
|
**Note 1:** If the resulting `pc` is beyond the last instruction then the opcode is implicitly a `STOP`, which is not an error.
|
||||||
|
|
||||||
@ -47,15 +50,18 @@ pushes: `0` (`return_stack` pushes: `1`)
|
|||||||
|
|
||||||
##### `RETURNSUB`
|
##### `RETURNSUB`
|
||||||
|
|
||||||
1. Pops `1` value form the `return_stack`.
|
1. If the `return_stack` is empty abort.
|
||||||
1.1 If the `return_stack` is empty, abort with error
|
2. Pop `pc` from the `return_stack`. (See Note 1 above)
|
||||||
2. Sets `pc` to the popped value (See Note 1 above)
|
|
||||||
|
|
||||||
pops: `0` (`return_stack` pops: `1`)
|
pops: `0` (`return_stack` pops: `1`)
|
||||||
pushes: `0`
|
pushes: `0`
|
||||||
|
|
||||||
**Note 2:** Values popped from `return_stack` do not need to be validated, since they cannot be set arbitrarily from code, only implicitly by the evm.
|
**Note 2:** Values popped from `return_stack` do not need to be validated, since they are alterable only by `JUMPSUB` and `RETURNSUB`.
|
||||||
**Note 3:** A value popped from `return_stack` _may_ be outside of the code length, if the last `JUMPSUB` was the last byte of the `code`. In this case the next opcode is implicitly a `STOP`, which is not an error.
|
|
||||||
|
**Note 3:** A value popped from `return_stack` _may_ be outside of the code length if the last `JUMPSUB` was the last byte of the `code`. In this case the next opcode is implicitly a `STOP`, which is not an error.
|
||||||
|
|
||||||
|
**Note 4:** The description above lays out the _semantics_ of this feature in terms of a `return_stack`. But the actual state of the `return_stack` is not observable to EVM code or consensus-critical to the protocol.
|
||||||
|
> For example, a node implementor may code `JUMPSUB` to unobservably push `pc` on the `return_stack` rather than `pc+1`, which is allowed so long as `RETURNSUB` observably returns control to the `pc+1` location.
|
||||||
|
|
||||||
**Note 4:** The description above lays out the _semantics_ of this feature in terms of a `return_stack`. It's up to node implementations to decide the internal representation. For example, a node may decide to place `PC` on the `return_stack` at `JUMPSUB`, as long as the `RETURNSUB` correctly returns to the `PC+1` location. The internals of the `return_stack` is not one of the "observable"/consensus-critical parts of the EVM.
|
**Note 4:** The description above lays out the _semantics_ of this feature in terms of a `return_stack`. It's up to node implementations to decide the internal representation. For example, a node may decide to place `PC` on the `return_stack` at `JUMPSUB`, as long as the `RETURNSUB` correctly returns to the `PC+1` location. The internals of the `return_stack` is not one of the "observable"/consensus-critical parts of the EVM.
|
||||||
|
|
||||||
@ -67,19 +73,7 @@ This is the smallest possible change that provides native subroutines without br
|
|||||||
|
|
||||||
These changes do not affect the semantics of existing EVM code.
|
These changes do not affect the semantics of existing EVM code.
|
||||||
|
|
||||||
## Alternative variants
|
# Test Cases
|
||||||
|
|
||||||
One possible variant, would be to add an extra clause to the `BEGINSUB` opcode.
|
|
||||||
|
|
||||||
- A `BEGINSUB` opcode may only be reached via a `JUMPSUB`.
|
|
||||||
|
|
||||||
This would make `walking` into a subroutine an error. The rationale for this would be to possibly improve static analysis, being able
|
|
||||||
to make stronger assertions about the code flow.
|
|
||||||
|
|
||||||
This is not part of the current specification, since code-generators can trivially implement these guarantees by always prepending `STOP` opcode before
|
|
||||||
any `BEGINSUB` operation.
|
|
||||||
|
|
||||||
## Test Cases
|
|
||||||
|
|
||||||
### Simple routine
|
### Simple routine
|
||||||
|
|
||||||
@ -92,7 +86,6 @@ Bytecode: `0x6004b300b2b7`
|
|||||||
|-------|-------------|------|-----------|-----------|
|
|-------|-------------|------|-----------|-----------|
|
||||||
| 0 | PUSH1 | 3 | [] | [] |
|
| 0 | PUSH1 | 3 | [] | [] |
|
||||||
| 2 | JUMPSUB | 8 | [4] | [] |
|
| 2 | JUMPSUB | 8 | [4] | [] |
|
||||||
| 4 | BEGINSUB | 1 | [] | [ 2] |
|
|
||||||
| 5 | RETURNSUB | 2 | [] | [ 2] |
|
| 5 | RETURNSUB | 2 | [] | [ 2] |
|
||||||
| 3 | STOP | 0 | [] | [] |
|
| 3 | STOP | 0 | [] | [] |
|
||||||
|
|
||||||
@ -105,10 +98,8 @@ Bytecode: `0x6800000000000000000cb300b26011b3b7b2b7`
|
|||||||
|-------|-------------|------|-----------|-----------|
|
|-------|-------------|------|-----------|-----------|
|
||||||
| 0 | PUSH9 | 3 | [] | [] |
|
| 0 | PUSH9 | 3 | [] | [] |
|
||||||
| 10 | JUMPSUB | 8 | [12] | [] |
|
| 10 | JUMPSUB | 8 | [12] | [] |
|
||||||
| 12 | BEGINSUB | 1 | [] | [10] |
|
|
||||||
| 13 | PUSH1 | 3 | [] | [10] |
|
| 13 | PUSH1 | 3 | [] | [10] |
|
||||||
| 15 | JUMPSUB | 8 | [17] | [10] |
|
| 15 | JUMPSUB | 8 | [17] | [10] |
|
||||||
| 17 | BEGINSUB | 1 | [] | [10,15] |
|
|
||||||
| 18 | RETURNSUB | 2 | [] | [10,15] |
|
| 18 | RETURNSUB | 2 | [] | [10,15] |
|
||||||
| 16 | RETURNSUB | 2 | [] | [10] |
|
| 16 | RETURNSUB | 2 | [] | [10] |
|
||||||
| 11 | STOP | 0 | [] | [] |
|
| 11 | STOP | 0 | [] | [] |
|
||||||
@ -166,15 +157,17 @@ Consumed gas: `26`
|
|||||||
|
|
||||||
## Implementations
|
## Implementations
|
||||||
|
|
||||||
No clients have implemented this proposal as of yet, but there are Draft PRs for
|
Three clients have implemented the previous version of proposal:
|
||||||
|
|
||||||
- [evmone](https://github.com/ethereum/evmone/pull/229), and
|
|
||||||
- [geth](https://github.com/ethereum/go-ethereum/pull/20619) .
|
- [geth](https://github.com/ethereum/go-ethereum/pull/20619) .
|
||||||
|
- [besu](https://github.com/hyperledger/besu/pull/717), and
|
||||||
|
- [openethereum](https://github.com/openethereum/openethereum/pull/11629).
|
||||||
|
|
||||||
|
The changes for the current version are trivial.
|
||||||
|
|
||||||
### Costs and Codes
|
### Costs and Codes
|
||||||
|
|
||||||
We suggest that the cost of `BEGINSUB` be _base_, `JUMPSUB` be _low_, and `RETURNSUB` be _verylow_.
|
We suggest that the cost of `JUMPSUB` be _low_, and `RETURNSUB` be _verylow_.
|
||||||
Measurement will tell. We suggest the following opcodes:
|
Measurement will tell. We suggest the following opcodes:
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -239,7 +232,7 @@ MULTIPLY:
|
|||||||
```
|
```
|
||||||
solc does a good job with the multiply() function, which is a leaf. Non-leaf functions are more awkward to get out of. Calling `fun.test()` will cost _118 gas_, plus 5 for the `mul`.
|
solc does a good job with the multiply() function, which is a leaf. Non-leaf functions are more awkward to get out of. Calling `fun.test()` will cost _118 gas_, plus 5 for the `mul`.
|
||||||
|
|
||||||
This is the same code written using `jumpsub` and `returnsub`. Calling `fun.test()` will cost _34 gas_ (plus 5).
|
This is the same code written using `jumpsub` and `returnsub`. Calling `fun.test()` will cost _32 gas_ plus 5 for the `mul`.
|
||||||
```
|
```
|
||||||
TEST:
|
TEST:
|
||||||
beginsub
|
beginsub
|
||||||
|
Loading…
x
Reference in New Issue
Block a user