EVM 的尾调用优化:EIP-6206 的 JUMPF 与不返回函数 图 1
EVM 的尾调用优化:EIP-6206 的 JUMPF 与不返回函数 · 图 1

程序员的账本里有一种隐形浪费:函数 A 在最后一行调用函数 B,B 干完活把控制权还给 A,A 随即也宣告结束——那这一还一交两次手,纯属仪式。编译器管这叫尾调用,成熟语言早就知道该把交接砍成直达。EVM 也想要这个直达:EIP-6206 在 2022 年 12 月 21 日提交,给以太坊对象格式(EOF)的代码段之间加一条 JUMPF 指令——跳过去,但不登记回程地址;顺带扩展类型段,允许一个代码段声明’我永不返回’。提案停在 Stagnant,依赖 EIP-4750 的函数化改造与 EIP-5450 的静态栈验证。

JUMPF 的机械细节

这条指令编码为 0xe5,带一个十六位大端参数:目标代码段编号。执行时它做三件事——检查操作数栈加上目标函数可能的最大增量后不超过一千零二十四层的全局栈限,超限则异常停机;把当前段指针改成目标段、程序计数器归零;然后继续执行,返回栈纹丝不动。它自己不收任何栈上元素也不压入任何元素,价格五 Gas。与常规函数调用的区别全在’不登记’三个字:目标段的终点指令执行完,控制流直接回到 JUMPF 的调用者——名义上根本没发生过第二层调用。对旧式无段字节码,JUMPF 是异常停机指令,行为边界画得干净。

不返回函数:让验证器提前剧透

类型段里每个代码段要声明输入输出个数,EIP-6206 给输出字段设了一个特殊值 0x80,含义是’本段永不把控制权交还’——它的结尾必定是 REVERT、STOP 或一条跳到不返回段的 JUMPF。这个声明把验证规则放宽了一档:JUMPF 通常要求调用方的输出声明不少于目标段(少了谁补),但目标声明为不返回时,这条匹配豁免——反正没有’回头交差’那一刻,栈上多几个元素随它去,执行终结时清算即可。首个代码段被强制规定零输入、不返回,它是整份容器的起点,天然没有回头路。

省钱的账算在哪里

提案的动机页把账写在两处:代码尺寸与 Gas。最受益的画面是错误处理小助手——检查失败就 REVERT 的公共段,在每条分支里都要被想起。旧式写法要么内联复制这段收尾逻辑,要么正经调用它、并在跳入前把多余栈项弹干净;有了不返回声明加 JUMPF,收尾段摆在一处,每条分支跳过去时多带的栈垃圾由机器负责处理。省下的既是每条分支几个字节的代码重复,也是每次弹栈压栈的燃料。对动辄部署几十 KB 的合约,这是编译器乐见的那种’每处省一点’。

它和整条 EOF 线的关系

EOF 是一串环环相扣的地基提案:容器格式、代码校验、函数段、静态栈验证、创建改造,JUMPF 是链条末端的花饰——没有段化的容器、没有类型段,跳段就无从谈起。EOF 多年未能走进主网升级排期,这条提案便与整队同行者一起停在草稿。读它的价值不在进度,在切口:尾调用优化是操作系统和语言运行时做了几十年的常规手术,这份 EIP 展示了同一手术在’每个字节都要计费、每层栈都要验证’的沙盒虚拟机里落地时的样子——安全边界先收紧(全局栈限预检、段间输出匹配),效率在边界内找缝隙。

一次调用的两种走法

把两种世界摆在一起看更直观。旧式调用像快递转送:A 把包裹连同自己的地址条交给 B,B 处理完按地址条退回 A,A 再交差给自己的上游——每一层转送都要占一格登记簿。JUMPF 式走法是直达窗口:A 直接把包裹递进 B 的柜台,登记簿上从头到尾只有 A 这一笔,B 办完事整单宣告结束。登记簿就是返回栈,它每省一格,就省一份内存与一份校验开销;而’不返回’声明相当于在窗口上贴告示:此窗口只进不出,队伍不必为回程预留位置。编译器的任务,就是在静态分析阶段证明这则告示属实。

快速问答

问:JUMPF 会加深栈吗? 答:不会。它刻意不加返回栈帧,这正是省内存开销的机制本身;全局栈限预检防的是目标函数自身的增长。

问:谁用得上’不返回’声明? 答:编译器后端,不是手写者。作者们给的画像很具体:被十几个分支共用的 REVERT 收尾助手,声明不返回后就能被通用复用。

风险提示:本文为协议机制科普,不构成任何投资建议;指令编码与验证规则以 EIP 仓库当期文本为准。