Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eip-2315: updated spec and examples #2576

Merged
merged 5 commits into from
Apr 3, 2020
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 107 additions & 60 deletions EIPS/eip-2315.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ title: Simple Subroutines for the EVM
status: Draft
type: Standards Track
category: Core
author: Greg Colvin (greg@colvin.org)
author: Greg Colvin (greg@colvin.org), Martin Holst Swende (@holiman)
discussions-to: https://ethereum-magicians.org/t/eip-2315-simple-subroutines-for-the-evm/3941
created: 2019-10-17
---
Expand All @@ -23,16 +23,38 @@ In the Appendix we show example solc output for a simple program that uses over

## Specification

We introduce one more `stack` into the evm, called `return_stack`. The `return_stack` is limited to `1023` items.
holiman marked this conversation as resolved.
Show resolved Hide resolved

##### `BEGINSUB`

Marks the entry point to a subroutine.

pops: `0`
pushes: `0`

##### `JUMPSUB`
Jumps to the destination address on top of the stack, which must be the offset of a `BEGINSUB`.

1. Pops `1` value from the `stack`, hereafter referred to as `location`.
1.1 If the opcode at `location` is not a `BEGINSUB`, abort with error.
2. Pushes the current `pc+1` to the `returnstack`.
2.1 If the `return_stack` already has `1023` items, abort with error.
3. Sets the `pc` to `location`.
holiman marked this conversation as resolved.
Show resolved Hide resolved

pops: `1`
pushes: `0` (`return_stack` pushes: `1`)


##### `RETURNSUB`
Returns to the most recently executed `JUMPSUB` and advances to the following instruction.

A program may `JUMPSUB` at most 1023 times without an intervening `RETURNSUB`. A program which executes `RETURNSUB` without no prior `BEGINSUB` will `STOP`.
1. Pops `1` value form the `return_stack`.
1.1 If the `return_stack` is empty, abort with error
2. Sets `pc` to the popped value

pops: `0` (`return_stack` pops: `1`)
pushes: `0`

**Note:** Values popped from `return_stack` do not need to be validated, since they cannot be set arbitrarily from code, only implicitly by the evm.
**Note2:** 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.

## Rationale

Expand All @@ -42,69 +64,91 @@ This is the smallest possible change that provides native subroutines without br

These changes do not affect the semantics of existing EVM code.

## Altenative variants
holiman marked this conversation as resolved.
Show resolved Hide resolved

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

This should jump into a subroutine, back out and stop.

Bytecode: `0x6004b300b5b7`


| Pc | Op | Cost | Stack | RStack |
|-------|-------------|------|-----------|-----------|
| 0 | PUSH1 | 3 | [] | [] |
| 2 | JUMPSUB | 8 | [4] | [] |
| 4 | BEGINSUB | 1 | [] | [ 2] |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it not be [3] on RStack here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right. So the geth-implementation currently pushes the pc, and then jumps to +1 from there. Which is not what the spec says.

| 5 | RETURNSUB | 2 | [] | [ 2] |
| 3 | STOP | 0 | [] | [] |

### Two levels of subroutines

This should execute fine, going into one two depths of subroutines
Bytecode: `0x6800000000000000000cb300b56011b3b7b5b7`

| Pc | Op | Cost | Stack | RStack |
|-------|-------------|------|-----------|-----------|
| 0 | PUSH9 | 3 | [] | [] |
| 10 | JUMPSUB | 8 | [12] | [] |
| 12 | BEGINSUB | 1 | [] | [10] |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and [11] here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. These were all generated by the actual geth evm, so I'll regenerate them when I update the code

| 13 | PUSH1 | 3 | [] | [10] |
| 15 | JUMPSUB | 8 | [17] | [10] |
| 17 | BEGINSUB | 1 | [] | [10,15] |
| 18 | RETURNSUB | 2 | [] | [10,15] |
| 16 | RETURNSUB | 2 | [] | [10] |
| 11 | STOP | 0 | [] | [] |


### Failure 1: invalid jump

This should fail, since the given `location` is outside of the code-range. The code is the same as previous example,
except that the pushed `location` is `0x01000000000000000c` instead of `0x0c`.

Bytecode: `0x6801000000000000000cb300b56011b3b7b5b7`

| Pc | Op | Cost | Stack | RStack |
|-------|-------------|------|-----------|-----------|
| 0 | PUSH9 | 3 | [] | [] |
| 10 | JUMPSUB | 8 |[18446744073709551628] | [] |

```
data return
offset step opcode stack stack
0 0 PUSH1 3 [] []
1 1 JUMPSUB [3] [1]
2 4 STOP [] [1]
3 2 BEGINSUB [] [1]
4 3 RETURNSUB [] []
```
The above code should terminate after 4 steps with an empty stack.
```
data return
offset step opcode stack stack
0 0 PUSH1 2 [] []
1 1 JUMPSUB [2] [1]
2 2 BEGINSUB [] [1]
3 3 RETURNSUB [] []
Error: at pc=10, op=JUMPSUB: evm: invalid jump destination
```
The above code should terminate after 4 steps with an empty stack.

### Failure 2: shallow `return_stack`

This should fail at first opcode, due to shallow `return_stack`

Bytecode: `0xb75858` (`RETURNSUB`, `PC`, `PC`)


| Pc | Op | Cost | Stack | RStack |
|-------|-------------|------|-----------|-----------|
| 0 | RETURNSUB | 2 | [] | [] |

```
data return
offset step opcode stack stack
0 0 PUSH1 3 [] []
1 1 JUMPSUB [3] [1]
2 8 STOP [] []
3 2 BEGINSUB [] [1]
4 3 PUSH1 7 [] [1]
5 4 JUMPSUB [7] [1,5]
6 7 RETURNSUB [] [1]
7 5 BEGINSUB [] [1]
8 6 RETURNSUB [] [1]
Error: at pc=0, op=RETURNSUB: evm: invalid retsub
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know there is no firm convention around EVM errors yet (even though that would be nice!), but I would probably call this a stack underflow instead of invalid retsub.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have a stack underflow, so I wanted a different message. However, it's an implementation detail in geth

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okok, if the intention is to make a new error msg, then that seems appropriate.

```
The above code should terminate after 8 steps with an empty stack. The above code should terminate after 8 steps with an empty stack.

## Implementations

No clients have implemented this proposal as of yet, but there are Draft PRs on the [evmone](https://github.com/ethereum/evmone/pull/229) and [geth](https://github.com/ethereum/go-ethereum/pull/20619) interpreters.
No clients have implemented this proposal as of yet, but there are Draft PRs for

- [evmone](https://github.com/ethereum/evmone/pull/229), and
- [geth](https://github.com/ethereum/go-ethereum/pull/20619) .

The new operators proposed here are demonstrated by the following pseudocode, which adds a return stack and cases for `BEGINSUB`, `JUMPSUB` and `RETURNSUB` to a simple loop-and-switch interpreter.
```
bytecode[code_size]
data_stack[1024] = { }
return_stack[1024] = { code_size }
PC = 0
while PC < code_size {
opcode = bytecode[PC]
switch opcode {
...
case BEGINSUB:

case JUMPSUB:
push(return_stack, PC)
PC = pop(data_stack)
continue

case RETURNSUB:
PC = pop(return_stack)
}
++PC
}
```
Execution of EVM bytecode begins with one value on the return stack—the size of the bytecode. The implicit 0 bytes at and after this offset are EVM `STOP` opcods. So executing a `RETURNSUB` with no prior `JUMPSUB` jumps to the _code_size_ offset on the stack, then executes a `STOP` on the next cycle. A `STOP` or `RETURN` ends the execution of the program.

### Costs and Codes

Expand All @@ -118,7 +162,10 @@ We suggest that the cost of `BEGINSUB` be _base_, `JUMPSUB` be _low_, and `RETUR
```
## Security Considerations

Program flow analysis frameworks will need to be updated to allow for a new type of branch - `JUMPSUB` - which will cause a jump to a destination which is a `BEGINSUB`, not a `JUMPDEST`.
These changes do introduce new flow control instructions, so any software which does static/dynamic analysis of evm-code
gcolvin marked this conversation as resolved.
Show resolved Hide resolved
needs to be modified accordingly. The `JUMPSUB` semantics are similar to `JUMP` (but jumping to a `BEGINSUB`), whereas the `RETURNSUB` instruction
is different, since it can 'land' on any opcode (but the possible destinations can be statically inferred).


## Appendix: Comparative costs.

Expand Down Expand Up @@ -190,4 +237,4 @@ MULTIPLY:
returnsub
```

**Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).**
**Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).**