Introduces support for EVM Procedures.
|Authors||Greg Colvin (@gcolvin), Greg Colvin <firstname.lastname@example.org>|
|Requires||EIP-2315, EIP-3540, EIP-3670, EIP-3779, EIP-4200|
Table of Contents
Five EVM instructions are introduced to define, call, and return from named EVM procedures and access their call frames in memory -
Currently, Ethereum bytecode has no syntactic structure, and subroutines have no defined interfaces.
We propose to add procedures – delimited blocks of code that can be entered only by calling into them via defined interfaces.
Also, the EVM currently has no automatic management of memory for procedures. So we also propose to automatically reserve call frames on an in-memory stack.
Constraints on the use of procedures must be validated at contract initialization time to maintain the safety properties of EIP-3779: Valid programs will not halt with an exception unless they run out of gas or recursively overflow stack.
The terminology is not well-defined, but we will follow Intel in calling the low-level concept subroutines and the higher level concept procedures. The distinction is that subroutines are little more than a jump that knows where it came from, whereas procedures have a defined interface and manage memory as a stack. EIP-2315 introduces subroutines, and this EIP introduces procedures.
ENTERPROC (0x??) dest_section: uint8, dest_offset: uint8, n_inputs: uint16, n_outputs: uint16, n_locals: uint16
frame_stack.push(FP) FP -= n_locals * 32 PC +- <length of immediates>
Marks the entry point to a procedure
- at offset
dest_offsetfrom the beginning of the
n_inputsarguments from the data stack,
n_outputsvalues on the
data stack, and
n_localswords of data in memory on the
Procedures can only be entered via a
CALLPROC to their entry point.
FP = frame_stack.pop() asm RETURNSUB
frame stackand return to the calling procedure using
Marks the end of a procedure. Each
ENTERPROC requires a closing
Note: Attempts to jump into a procedure (including its
LEAVEPROC) from outside of the procedure or to jump or step to
ENTERPROC at all must be prevented at validation time.
CALLPROC is the only valid way to enter a procedure.
FP -= n_locals asm JUMPSUB <offset of section> + <offset of procedure>
Allocate a stack frame and transfer control and
JUMPSUBto the Nth (N=dest_proc) procedure in the Mth(M=dest_section) section of the code. Section 0 is the current code section, any other code sections are indexed starting at 1.
Note: That the procedure is defined and the required
n_inputs words are available on the
data stack must be shown at validation time.
FP += n_locals asm RETURNSUB
frame stackand return control to the calling procedure using
Note: That the promised
n_outputs words are available on the
data stack must be shown at validation time.
asm PUSH2 FP + offset
Push the address
FP + offsetonto the data stack.
Call frame data is addressed at an immediate
offset relative to
Typical usage includes storing data on a call frame
PUSH 0xdada FRAMEADDRESS 32 MSTORE
and loading data from a call frame
FRAMEADDRESS 32 MLOAD
MSTORE is defined as
memory[stack...stack+31] = stack memory_size = max(memory_size,floor((stack+32)÷32)
memory_sizeis the number of active words of memory above 0.
We propose to treat memory addresses as signed, so the formula needs to be
memory[stack...stack+31] = stack if (stack)+32)÷32) < 0 negative_memory_size = max(negative_memory_size,floor((stack+32)÷32)) else positive_memory_size = max(positive_memory_size,floor((stack+32)÷32)) memory_size = positive_memory_size + negative_memory_size
negative_memory_sizeis the number of active words of memory below 0 and
positive_memory_sizeis the number of active words of memory at or above 0.
These instructions make use of a
frame stack to allocate and free frames of local data for procedures in memory. Frame memory begins at address 0 in memory and grows downwards, towards more negative addresses. A frame is allocated for each procedure when it is called, and freed when it returns.
Memory can be addressed relative to the frame pointer
FP or by absolute address.
FP starts at 0, and moves downward towards more negative addresses to point to the frame for each
CALLPROC and moving upward towards less negative addresses to point to the previous frame for the corresponding
Equivalently, in the EVM’s twos-complement arithmetic,
FP moves from the highest address down, as is common in many calling conventions.
For example, after an initial
CALLPROC to a procedure needing two words of data the
frame stack might look like this
0-> ........ ........ FP->
Then, after a further
CALLPROC to a procedure needing three words of data the
frame stack would like this
0-> ........ ........ -64-> ........ ........ ........ FP->
RETURNPROC from that procedure the
frame stack would look like this
0-> ........ ........ FP-> ........ ........ ........
and after a final
RETURNPROC, like this
FP-> ........ ........ ........ ........ ........
There is actually not much new here. It amounts to EIP-615, refined and refactored into bite-sized pieces, along lines common to other machines.
This proposal uses the EIP-2315 return stack to manage calls and returns, and steals ideas from EIP-615, EIP-3336, and EIP-4200.
ENTERPROC corresponds to
BEGINSUB from EIP-615. Like EIP-615 it uses a frame stack to track call-frame addresses with
FP as procedures are entered and left, but like EIP-3336 and EIP-3337 it moves call frames from the data stack to memory.
Aliasing call frames with ordinary memory supports addressing call-frame data with ordinary stores and loads. This is generally useful, especially for languages like C that provide pointers to variables on the stack.
The design model here is the subroutines and procedures of the Intel x86 architecture.
RETURNSUB(from EIP-2315 – like
RET– jump to and return from subroutines.
ENTER– sets up the stack frame for a procedure.
CALLPROCamounts to a
RETURNPROCamounts to an early
LEAVE– takes down the stack frame for a procedure. It then executes a
This proposal adds new EVM opcodes. It doesn’t remove or change the semantics of any existing opcodes, so there should be no backwards compatibility issues.
Safe use of these constructs must be checked completely at validation time – per EIP-3779 – so there should be no security issues at runtime.
LEAVEPROC must follow the same safety rules as for
RETURNSUB in EIP-2315. In addition, the following constraints must be validated:
ENTERPROCmust be followed by a
LEAVEPROCto delimit the bodies of procedures.
- There can be no nested procedures.
- There can be no jump into the body of a procedure (including its
LEAVEPROC) from outside of that body.
- There can be no jump or step to
BEGINPROCat all – only
- The specified
n_outputsmust be on the stack.
Copyright and related rights waived via CC0.
Please cite this document as:
Greg Colvin (@gcolvin), Greg Colvin <email@example.com>, "EIP-4573: Procedures for the EVM [DRAFT]," Ethereum Improvement Proposals, no. 4573, December 2021. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-4573.