Alert Source Discuss
🚧 Stagnant Standards Track: Core

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 - ENTERPROCLEAVEPROCCALLPROCRETURNPROCFRAMEADDRESS

动机

目前,以太坊字节码没有句法结构,并且 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-615EIP-3336EIP-4200 窃取想法。ENTERPROC对应于EIP-615中的BEGINSUB。像EIP-615一样,它使用帧堆栈来跟踪具有FP的调用帧地址,因为在输入和离开_procedures_时,像EIP-3336和EIP-3337一样,它将调用帧从数据栈移至内存。

用普通内存别名化调用栈帧支持使用普通存储和加载来寻址调用栈帧数据。这通常很有用,特别是对于像C这样的语言,这些语言提供指向堆栈上变量的指针。

此处的设计模型是Intel x86体系结构的 subroutinesprocedures

  • JUMPSUBRETURNSUB(来自 EIP-2315)– 像CALLRET一样–跳转到并从_subroutines_返回。
  • ENTERPROC – 像ENTER一样 – 为 procedure 设置堆栈帧。
  • CALLPROC相当于到ENTERPROCJUMPSUB
  • RETURNPROC相当于一个早期的LEAVEPROC
  • LEAVEPROC – 像LEAVE一样 – 将_procedure_的堆栈帧拿下。然后,它执行RETURNSUB

向后兼容性

此提案添加了新的EVM操作码。它不会删除或更改任何现有操作码的语义,因此不应存在向后兼容性问题。

安全

必须在验证时完全检查这些构造的安全使用 – 根据EIP-3779 – 因此运行时不应存在安全问题。

ENTERPROCLEAVEPROC必须遵循与EIP-2315中JUMPSUBRETURNSUB相同的安全规则。此外,必须验证以下约束:

  • 每个ENTERPROC之后必须跟随一个LEAVEPROC,以分隔_procedures_的主体。
  • 不能有嵌套的_procedures_。
  • 不能从该主体外部跳转到过程主体(包括其LEAVEPROC)中。
  • 禁止跳转或者step到BEGINPROC – 只能 CALLPROC
  • 指定的n_inputsn_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.