EIP-4573: EVM程序的过程
引入对EVM过程的支持。
Authors | Greg Colvin (@gcolvin), Greg Colvin <greg@colvin.org> |
---|---|
Created | 2021-12-16 |
Discussion Link | https://ethereum-magicians.org/t/eip-4573-named-procedures-for-evm-code-sections/7776 |
Requires | EIP-2315, EIP-3540, EIP-3670, EIP-3779, EIP-4200 |
摘要
引入五个EVM指令来定义、调用和从命名的EVM procedures 返回,并在内存中访问它们的 call frames - ENTERPROC
,LEAVEPROC
,CALLPROC
,RETURNPROC
和FRAMEADDRESS
。
动机
目前,以太坊字节码没有句法结构,并且 subroutines 没有定义的接口。
我们建议添加 procedures – 限定的代码块,只能通过经由定义的接口调用才能进入。
此外,EVM当前没有自动管理_procedures_的内存。因此,我们还建议在内存堆栈上自动保留调用栈帧。
对 procedures 使用的约束必须在合约初始化时进行验证,以保持 EIP-3779 的安全属性:有效的程序不会因异常而停止,除非它们耗尽 gas 或递归地溢出堆栈。
先有技术
术语定义不明确,但我们将遵循 Intel 的做法,将底层概念称为 subroutines,将更高层概念称为 procedures。区别在于 subroutines 仅仅是一个知道自己来自何处的跳转,而 procedures 具有定义的接口并像堆栈一样管理内存。EIP-2315 引入了 subroutines,而此 EIP 引入了 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>
标记过程的入口点
- 在距
dest_section
开头dest_offset
的偏移量处。 - 从数据堆栈中获取
n_inputs
个参数, - 在
data stack
上返回n_outputs
个值,并且 - 在
frame stack
中内存中保留n_locals
个数据字。
过程只能通过对到其入口点的CALLPROC
来输入。
LEAVEPROC (0x??)
FP = frame_stack.pop()
asm RETURNSUB
弹出
frame stack
并使用RETURNSUB
返回到调用过程。
标记过程的结束。每个ENTERPROC
都需要一个结束的LEAVEPROC
。
注意:尝试从过程外部跳转到过程(包括其LEAVEPROC
)或根本跳转或步进到ENTERPROC
必须在验证时阻止。CALLPROC
是进入过程的唯一有效方法。
CALLPROC (0x??) dest_section: uint16, dest_proc: uint16
FP -= n_locals
asm JUMPSUB <offset of section> + <offset of procedure>
分配一个stack frame并通过
JUMPSUB
转移控制权和分配到第M个(dest_section)代码 section 中的第N个(N=dest_proc) procedure 中。Section 0 是当前代码段,任何其他代码段都从 1 开始索引。
注意:过程已定义,并且在验证时必须显示所需的n_inputs
字在data stack
上可用。
RETURNPROC (0x??)
FP += n_locals
asm RETURNSUB
弹出
frame stack
并使用RETURNSUB
将控制返回到调用过程。
注意:必须在验证时显示承诺的n_outputs
字在data stack
上可用。
FRAMEADDRESS (0x??) offset: int16
asm PUSH2 FP + offset
将地址
FP + offset
压入数据堆栈。
调用帧数据在相对于FP
的立即offset
处寻址。
典型用法包括在调用帧上存储数据
PUSH 0xdada
FRAMEADDRESS 32
MSTORE
和从调用帧加载数据
FRAMEADDRESS 32
MLOAD
内存成本
目前,MSTORE
定义为
memory[stack[0]...stack[0]+31] = stack[1]
memory_size = max(memory_size,floor((stack[0]+32)÷32)
- 其中
memory_size
是高于 0 的活动内存字数。
我们建议将内存地址视为有符号的,因此公式需要为
memory[stack[0]...stack[0]+31] = stack[1]
if (stack[0])+32)÷32) < 0
negative_memory_size = max(negative_memory_size,floor((stack[0]+32)÷32))
else
positive_memory_size = max(positive_memory_size,floor((stack[0]+32)÷32))
memory_size = positive_memory_size + negative_memory_size
- 其中
negative_memory_size
是低于 0 的活动内存字数,并且 - 其中
positive_memory_size
是在_0_或以上的活动内存字数。
调用帧堆栈
这些指令使用frame stack
来为内存中_procedures_分配和释放本地数据的帧。帧内存从内存中的地址0开始,并向下增长,朝向更多的负地址。在调用每个过程时都会为其分配一个帧,并在返回时释放该帧。
内存可以相对于帧指针FP
或通过绝对地址进行寻址。FP
从0开始,并向下移动到更大的负地址,以指向每个CALLPROC
的帧,并向上移动到较小的负地址,以指向相应RETURNPROC
的先前帧。
等效地,在EVM的二进制补码算术中,FP
从最高地址向下移动,这在许多调用约定中很常见。
例如,在初始CALLPROC
到需要两个数据字的过程之后,frame stack
可能看起来像这样
0-> ........
........
FP->
然后,在进一步的CALLPROC
到需要三个数据字的过程之后,frame stack
将如下所示
0-> ........
........
-64-> ........
........
........
FP->
从该过程进行RETURNPROC
之后,frame stack
将如下所示
0-> ........
........
FP-> ........
........
........
在最终的RETURNPROC
之后,如下所示
FP-> ........
........
........
........
........
理由
实际上,这里并没有太多的新东西。它相当于 EIP-615,经过提炼和重构为小块,其方式与其他机器通用。
该提案使用 EIP-2315 返回堆栈来管理调用和返回,并从 EIP-615,EIP-3336 和EIP-4200 窃取想法。ENTERPROC
对应于EIP-615中的BEGINSUB
。像EIP-615一样,它使用帧堆栈来跟踪具有FP
的调用帧地址,因为在输入和离开_procedures_时,像EIP-3336和EIP-3337一样,它将调用帧从数据栈移至内存。
用普通内存别名化调用栈帧支持使用普通存储和加载来寻址调用栈帧数据。这通常很有用,特别是对于像C这样的语言,这些语言提供指向堆栈上变量的指针。
此处的设计模型是Intel x86体系结构的 subroutines 和 procedures。
JUMPSUB
和RETURNSUB
(来自 EIP-2315)– 像CALL
和RET
一样–跳转到并从_subroutines_返回。ENTERPROC
– 像ENTER
一样 – 为 procedure 设置堆栈帧。CALLPROC
相当于到ENTERPROC
的JUMPSUB
。RETURNPROC
相当于一个早期的LEAVEPROC
。LEAVEPROC
– 像LEAVE
一样 – 将_procedure_的堆栈帧拿下。然后,它执行RETURNSUB
。
向后兼容性
此提案添加了新的EVM操作码。它不会删除或更改任何现有操作码的语义,因此不应存在向后兼容性问题。
安全
必须在验证时完全检查这些构造的安全使用 – 根据EIP-3779 – 因此运行时不应存在安全问题。
ENTERPROC
和LEAVEPROC
必须遵循与EIP-2315中JUMPSUB
和RETURNSUB
相同的安全规则。此外,必须验证以下约束:
- 每个
ENTERPROC
之后必须跟随一个LEAVEPROC
,以分隔_procedures_的主体。 - 不能有嵌套的_procedures_。
- 不能从该主体外部跳转到过程主体(包括其
LEAVEPROC
)中。 - 禁止跳转或者step到
BEGINPROC
– 只能CALLPROC
。 - 指定的
n_inputs
和n_outputs
必须在堆栈上。
版权
版权及相关权利通过 CC0 放弃。
Citation
Please cite this document as:
Greg Colvin (@gcolvin), Greg Colvin <greg@colvin.org>, "EIP-4573: EVM程序的过程 [DRAFT]," Ethereum Improvement Proposals, no. 4573, December 2021. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-4573.