EIP-6206: EOF - JUMPF 和非返回函数
引入用于链接函数调用的指令。
Authors | Andrei Maiboroda (@gumb0), Alex Beregszaszi (@axic), Paweł Bylica (@chfast), Matt Garnett (@lightclient) |
---|---|
Created | 2022-12-21 |
Discussion Link | https://ethereum-magicians.org/t/eip-4750-eof-functions/8195 |
Requires | EIP-4750, EIP-5450 |
摘要
本 EIP 通过引入一条新的指令 JUMPF
来允许 EOF 函数 (EIP-4750) 中的尾调用优化, 该指令跳转到一个代码段而无需添加新的返回栈帧。
此外,类型部分的格式被扩展为允许将部分声明为非返回,从而简化了 JUMPF
对此类部分进行的堆栈验证。
动机
函数通常会在例程结束时进行调用,然后才返回。JUMPF
通过更改代码段而无需更新返回栈来优化此行为。
在验证时知道一个函数永远不会返回控制权,允许将 JUMPF
视为类似于终止指令,在这种情况下,额外的项目可能会在执行终止时留在操作数栈上。这为编译器提供了生成更优代码的机会,无论是在代码大小还是在花费的 gas 方面。这对于小型错误处理助手尤其有利,它们以 REVERT
结束执行:它们通常在多个分支中重复使用,并且在无需在 JUMPF
到此类助手之前弹出额外的堆栈项目时,将它们提取到助手函数中是高效的。
规范
类型部分更改
我们将非返回部分定义为无法将控制权返回给其调用方的部分。
当相应的代码段是非返回时,类型部分的 outputs
字段包含一个特殊值 0x80
。有关验证详细信息,请参见下面的 非返回状态验证。
第一个代码段必须具有 0 个输入并且是非返回的。
执行语义
引入了一个新的指令 JUMPF (0xe5)
。
JUMPF
有一个立即参数target_section_index
,编码为 16 位的无符号大端值。- 如果操作数栈大小超过
1024 - type[target_section_index].max_stack_increase
(即,如果被调用的函数可能超过全局堆栈高度限制),则执行将异常停止。这保证了目标函数不会超过全局堆栈高度限制。 JUMPF
将current_section_index
设置为target_section_index
,并将PC
设置为0
,但不会更改返回栈。执行在目标部分继续。JUMPF
的 gas 消耗为 5。JUMPF
不会弹出或推送任何内容到操作数栈。
如果代码是传统的 bytecode,则 JUMPF
指令会导致异常停止。(注意:这意味着行为没有变化。)
代码验证
让 type[i]
的定义继承自 EIP-4750,并将 stack_height_min
和 stack_height_max
定义为指令流遍历期间某个指令处的堆栈高度边界。
JUMPF
的立即参数必须小于代码段的总数。- 对于每个
JUMPF
指令:type[current_section_index].outputs
必须大于或等于type[target_section_index].outputs
,- 或者
type[target_section_index].outputs
必须为0x80
JUMPF
处的堆栈高度验证取决于目标段是否为非返回段:JUMPF
进入返回节(type[target_section_index].outputs
不等于0x80
):stack_height_min
和stack_height_max
必须等于type[current_section_index].outputs - type[target_section_index].outputs + type[target_section_index].inputs
。这意味着,如果当前代码段在堆栈上留下增量type[current_section_index].outputs - type[target_section_index].outputs
元素,则目标段可以输出比返回堆栈顶部元素调用的原始代码段更少的堆栈元素。JUMPF
进入非返回段(type[target_section_index].outputs
等于0x80
):stack_height_min
必须大于或等于type[target_section_index].inputs
。
JUMPF
处的堆栈溢出检查:stack_height_max
必须小于或等于1024 - types[target_section_index].max_stack_increase
。JUMPF
被认为是终止指令,即在代码验证中没有后继指令,并且可能是该段中的最终指令。- 如果任何
RJUMP*
偏移指向紧随JUMPF
指令之后的两个字节之一,则 EIP-4200 中定义的代码验证也会失败。
CALLF
指令验证已扩展为包括以下规则:
- 如果任何
CALLF
的立即参数target_section_index
指向非返回段,即type[target_section_index].outputs
等于0x80
,则代码段无效。
非返回状态验证
当且仅当该段不包含 RETF
指令且没有以返回段为目标的 JUMPF
指令时,该段类型必须为非返回(通过其在 type 段中的输出值检查目标段的状态)。
注意:这意味着仅包含进入非返回段的 JUMPF
的段本身是非返回段。
理由
允许 JUMPF
跳转到输出较少的部分
JUMPF
堆栈验证的替代规则可能要求目标部分的输出与当前部分的输出完全相同。 在此规则下,特定的目标部分(一段共享的“辅助”代码)将仅“匹配”具有相同数量输出的部分(要求在返回之前执行一些共享的“辅助”代码)。
相反,我们允许从具有更多输出的部分调用给定的 JUMPF
目标部分,只要这些部分自己提供这些额外的堆栈元素(“增量”)。 这将减少重复的代码,因为它将允许编译器在代码生成过程中具有更大的灵活性,以便某些助手可以被函数通用地使用,而不管其输出值如何。
向后兼容性
此更改是向后兼容的,因为 EOF 不允许使用或部署未定义的指令,这意味着不会影响任何合约。
安全考虑
在实施 EOF 容器验证算法期间,需要仔细考虑此新指令。
版权
在 CC0 下放弃版权及相关权利。
Citation
Please cite this document as:
Andrei Maiboroda (@gumb0), Alex Beregszaszi (@axic), Paweł Bylica (@chfast), Matt Garnett (@lightclient), "EIP-6206: EOF - JUMPF 和非返回函数," Ethereum Improvement Proposals, no. 6206, December 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6206.