动态跳转的代价
EVM 多年来只有两种跳转方式:JUMP 无条件跳、JUMPI 按栈顶条件跳,目的地都从栈上取一个 256 位数字。两条指令就搭起了整个控制流,灵活性的代价却不小。第一,目的地在运行时才知道,任何工具都无法仅凭字节码确定程序能走到哪里,静态分析常常直接不可行。第二,为了区分合法指令位置和任意数据字节,EVM 被迫引入 JUMPDEST 标记,每个能被跳到的位置都必须摆一个 0x5b 字节,执行前的跳转目标分析还要为整段代码逐字节付一次扫描成本。第三,编译器每次跳转都要先发一条 PUSH 把绝对地址压栈,再跟一条 JUMP,部署时多占字节、执行时多耗 gas。
现实中绝大多数跳转其实根本不需要动态性:函数入口、循环边界、switch 分支表,目的地上编译期就完全确定。EIP-8013(2025年8月起草,草案状态,依赖 EIP-7979 引入的子程序指令族)于是提出五条相对跳转指令,把编译期已知的目的地写成指令自带的立即数。
五条指令各自做什么
RJUMP(操作码 0xe0)带一个 16 位带符号大端偏移量,执行后程序计数器 PC 等于指令结束位置加上偏移,正数往后跳、负数往回跳,循环因此天然成立。RJUMPI(0xe1)从栈上弹出一个条件值,条件非零才跳转,否则继续顺序执行。RJUMPV(0xe2)是跳转表:先弹出一个 case 值,再读一个 8 位无符号 max_index,后面跟 max_index+1 个偏移量,最多支撑 256 项的分支表;当 case 超出 max_index 时不跳转、顺序落到下一条指令,所以把 default 分支直接写在表后面是最省事的写法。附带一个实用技巧:RJUMPV 0 加一个偏移恰好等价于反条件跳转,可以省掉 ISZERO 加 RJUMPI 的两条指令组合。
另外两条 RJUMPSUB(0xe3)与 RJUMPSUBV(0xe4)在进入目标前把返回地址压入独立的返回栈,配合 EIP-7979 定义的 ENTERSUB 与 RETURNSUB 完成子程序调用。它们的目的地必须落在 ENTERSUB 上,而前三条可以指向合法指令位置。
验证、边界与定价
验证算法在部署时一次性检查每条相对跳转:偏移目标必须落在指令边界上,不能扎进 PUSH 或跳转指令自己的立即数数据里,也不能越出代码边界。这一步做完之后,执行期不再需要任何跳转合法性检查,提案建议 RJUMP 定价 2 gas、RJUMPI 与 RJUMPV 定价 4、两条 SUB 版本定价 5,都低于对应的动态跳转。16 位带符号偏移意味着最远能跳 32767 字节,而 EIP-170 早已把合约代码上限定为 24576 字节,够用。
一条对照账
读一个目标地址是 300 的分支:动态写法要先 PUSH1 再 JUMP,光压地址就占两个字节,加上 JUMP 本身,每次执行都要为取地址和合法性检查重复付费,目标位置还得摆一个 JUMPDEST;静态写法 RJUMP 加两字节偏移共三个字节,编译期即定死目的地,执行时只剩一次加法。字节数打平,运行时开销却几乎归零。一个函数里嵌八个 if-else,八处跳转每处都省下一轮目标解析,这就是提案把收益押在真实合约而非极端用例上的原因。
为什么目标写进指令而不是给序列打折
一种替代路线是不加新指令,只对 PUSH 紧跟 JUMP 的模式退差价。提案明确反对:gas 规则会更复杂,而且要么给字节码规定共识级内部表示,要么放任各实现自行优化再统一收费,两条路都风险不小。相对寻址还让代码片段天然可搬移,一段字节码复制到哪里都能跑,过去靠注入 PUSHn PC ADD JUMP 的技巧从此有了协议级替代,提案甚至认为 PC 指令未来可以退役。
快速问答
问:静态跳转会淘汰 JUMP 吗?不会,动态跳转在函数指针一类的场景仍不可替代,提案只是给编译期已知流向提供更便宜的选择。问:和 EIP-4200 什么关系?同一问题空间的先期尝试,聚焦在带符号偏移的 RJUMP 家族上,本提案把表格跳转与子程序调用一并补齐。问:现在能用吗?不能,它还是草案,且要求先进入 EIP-7979 引入的子程序机制,编译器落地以官方规范合入为准。
风险提示:本文仅介绍协议草案机制,不构成任何投资建议;EIP 内容以官方仓库当期文本为准。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。