为 EVM 引入静态相对跳转与调用:EIP-8013(RJUMP / RJUMPI / RJUMPV / RJUMPSUB / RJUMPSUBV)完整解析
2026/9/16 12:25:42 网站建设 项目流程

为 EVM 引入静态相对跳转与调用:EIP-8013(RJUMP / RJUMPI / RJUMPV / RJUMPSUB / RJUMPSUBV)完整解析

【免费下载链接】EIPsThe Ethereum Improvement Proposal repository项目地址: https://gitcode.com/GitHub_Trending/ei/EIPs

EIP-8013 为以太坊虚拟机(EVM)新增五条以有符号立即数编码跳转目标地址的指令——RJUMPRJUMPIRJUMPVRJUMPSUBRJUMPSUBV,让编译器能够在绝大多数JUMP/JUMPI场景下使用静态跳转替代动态跳转,从而在部署与执行阶段降低 gas 消耗、提升执行性能并大幅改善静态分析的可处理性。读完本文,你将掌握这五条指令的完整语义、立即数编码规则、与 EIP-7979 子程序机制(return stack /ENTERSUB/RETURNSUB)的衔接方式、验证算法扩展、gas 成本建议,以及配套的验证与执行测试用例,并能在此基础上评估该提案对 EVM 控制流演进的影响。

背景与动机:为什么 EVM 需要静态跳转

EVM 目前只提供动态跳转机制:JUMPJUMPI的跳转目标从数据栈(data stack)取值,指令本身不携带任何目标信息。这种设计只用两条指令就支撑了极其灵活的控制流,但代价是代码分析变得困难甚至不可解,也直接导致了JUMPDEST标记的诞生。

在绝大多数实际场景中,控制流本质上是静态的——跳转目标在字节码编译期就已确定,根本不需要动态行为。EIP-8013 的提案者总结了几类可以减少动态跳转需求的手段:

  1. 对函数 / 子程序的原生支持
  2. 一种"返回到调用者"的指令——即子程序返回(subroutine return);
  3. 支持动态索引的"switch-case"跳转表。

本提案引入一个最小特性集(恰好涵盖以上三点),使编译器可以只使用RJUMP/RJUMPI/RJUMPSUB就完成全部内部控制流的表达。同时,该功能不排斥 EVM 未来引入其他形式的控制流,也不排斥存量(legacy)代码继续运行:RJUMP/RJUMPI可以与更高级的函数声明方式高效共存,用于函数内部的控制流。

新指令带来的核心收益有三点:部署与执行阶段的 gas 成本更低执行性能更好静态分析性质更优

关于静态控制流更完整的历史脉络、技术基础及其对以太坊扩容路线图(ZK-Rollup、Optimistic Rollup、RISC-V 迁移)的影响,可参阅同仓库的姊妹文档 EIPS/eip-8173.md(Static Control Flow for the EVM,Informational 类型)。该文档还附带了一个可复现的实验 assets/eip-8173/walk-count/README.md:对同一程序分别用传统jump约定与调用/返回指令编写,统计分析器必须执行的路径遍历次数——jump版本遍历次数按1 + K + K² + … + K^K增长(20 次共享调用约为 10²⁶ 次遍历),而调用/返回版本恒为 1 次。

规范:五条新指令

提案引入五条新指令,每条指令要么携带一个两字节相对偏移,要么携带一个偏移表作为立即数(immediate):

指令Opcode立即数栈操作语义
RJUMP0xe0relative_offsetPC设为PC_post_instruction + relative_offset
RJUMPI0xe1relative_offset弹出conditionPC设为PC_post_instruction + ((condition == 0) ? 0 : relative_offset)
RJUMPV0xe2max_index relative_offset+弹出casePC设为PC_post_instruction + ((case > max_index) ? 0 : relative_offset[case])
RJUMPSUB0xe3relative_offsetPC_post_instruction压入return stack,并将PC设为PC_post_instruction + relative_offset(一个ENTERSUB,等价于以CALLSUB调用)
RJUMPSUBV0xe4max_index relative_offset+弹出casePC_post_instruction压入 return stack,并将PC设为PC_post_instruction + ((case > max_index) ? 0 : relative_offset[case])(一个ENTERSUB,等价于以CALLSUB调用)

立即数编码规则

  • relative_offset是一个16 位有符号(二进制补码)大端序值;
  • 所谓PC_post_instruction,指的是整个立即数之后PC位置(即该指令占用完毕后 PC 所指向的下一条指令位置);
  • RJUMP的跳转距离为有符号 16 位所能表示的范围,最大正向跳转距离为32767。若PC=0处的字节码以RJUMP开头,则最远可跳到PC=32770

RJUMPVRJUMPSUBV的特殊编码

RJUMPVRJUMPSUBV的立即数编码更为特殊:

  • 紧跟 opcode 的是一个无符号 8 位max_index,决定跳转表的最大索引
  • 随后跟有max_index + 1relative_offset值,即跳转表最多可容纳 256 个表项
  • RJUMPV的编码必须至少包含一个relative_offset,因此其最小长度为4 字节(1 字节 opcode + 1 字节max_index+ 至少 2 字节偏移);
  • case > max_index(索引越界)时,控制流直接顺序落入下一条指令(fall-through)。这意味着在大多数使用场景下,程序员会把default路径紧跟在RJUMPV指令之后放置;
  • 一个值得注意的用法是:RJUMPV 0 relative_offset等价于一个取反的RJUMPI,可在许多场合替代ISZERO RJUMPI relative_offset的组合。

与子程序机制的衔接(依赖 EIP-7979)

RJUMPSUBRJUMPSUBV的目的地址MUST 是ENTERSUB(子程序入口标记),并且控制权通过RETURNSUB返回到发起调用的RJUMPSUB/RJUMPSUBV。这两条指令相当于直接访问了 EIP-7979 中CALLSUB指令底层的"返回调用者"机制。

EIP-7979 为 EVM 引入的机器状态包括:

  • return stack(返回栈):仅由CALLSUB压入、仅由RETURNSUB弹出,最深 1024 项,EVM 代码无法直接访问(不能读、改、移动),从而消除了代码自身篡改控制流的整类漏洞;
  • CALLSUB:从数据栈弹出目标地址压入返回栈并跳转,目标必须是CALLDEST,gas 为mid(8);
  • CALLDEST:子程序入口标记,功能类似JUMPDEST的空操作,gas 为jumpdest(1);
  • RETURNSUB:从返回栈弹出地址写入PC,返回栈为空则异常停机,gas 为low(5)。

需要说明的是,EIP-8013 规范正文中统一使用ENTERSUB指代子程序入口目标("anENTERSUB, as if withCALLSUB"),其底层即 EIP-7979 所描述的调用目标标记机制。EIP-7979 的参考实现(以 EELS Python 执行规范形式给出,见 EIPS/eip-7979.md 第 333-401 行)展示了Evm状态如何新增return_stack字段、jumpdest 分析如何扩展出valid_call_destinations集合,以及三条指令的逐条实现,可作为理解本提案底层机制的直接参考。

验证算法扩展

本提案同时扩展了 EIP-7979 的验证算法,要求对每一条RJUMP/RJUMPI/RJUMPV/RJUMPSUB/RJUMPSUBV校验其relative_offset指向一条指令

  • 不能指向PUSHn/RJUMP/RJUMPI/RJUMPV立即数数据
  • 不能指向代码边界之外
  • 进一步地,所有且仅有RJUMPSUBRJUMPSUBVrelative_offset必须指向ENTERSUB;而其余指令(RJUMP/RJUMPI/RJUMPV允许指向JUMPDEST,但并不要求必须如此。

由于跳转目标在 jumpdest 分析阶段即被校验完毕,运行时无需再次检查,这正是静态跳转相比动态跳转在 gas 和执行开销上的优势来源。

Gas 成本

由于跳转目标在 jumpdest 分析阶段已一次性校验,运行时无需重复检查,因此这些指令的成本可以低于其动态对应物。提案建议:

指令建议 gas
RJUMP2
RJUMPI/RJUMPV4
RJUMPSUB/RJUMPSUBV5

作为对比,当前动态跳转JUMP的成本为mid(8)gas,JUMPDEST为 1 gas。

设计权衡(Rationale)

相对寻址(Relative addressing)

选择相对寻址是为了支持可重定位代码(relocatable code),这意味着代码片段可以被注入。在本提案之前,业界已有通过注入PUSHn PC ADD JUMPI来实现同样目标的技巧。相对寻址没有明显缺点,并且它使得PC指令有被废弃的可能。

立即数大小(Immediate size)

有符号 16 位立即数意味着最大跳转距离为 32767。结合 EIP-170 的MAX_CODE_SIZE = 24576与 EIP-3860 的MAX_INITCODE_SIZE = 49152,16 位立即数被认为足够使用。

若改用 8 位立即数,PC 最多只能向后移动 125 字节、向前移动 127 字节——对许多 for 循环足够,但不足以支撑跨函数跳转。而且 16 位立即数与动态跳转在此类场景下的占用完全一致(JUMP PUSH1 n恰好 3 字节),因此"指令更少"是更优选择。若未来确有其他尺寸(8 位、24 位、32 位)立即数的需求,可以像多条PUSH指令那样引入新的 opcode。

PUSHn JUMP序列的处理

如果选择绝对寻址,RJUMP可被视为PUSHn JUMP序列的等价物(RJUMPI类似PUSHn JUMPI)。那样的话,一种观点认为不必引入新指令,只需为这类序列提供 gas 折扣、由 EVM 实现自行优化即可。

提案认为这不是好方向,并将现有PUSHn JUMP序列的语义定义留给 EIP-7979,不在本提案中优化其 gas,理由如下:

  1. 会进一步复杂化本就繁琐的 gas 计算规则;
  2. 要么需要在共识层定义 EVM 代码的内部表示,要么强迫 EVM 实现各自做优化——两者都有风险;
  3. EVM 实现应当自由选择应用何种优化,优化收益并不必须全部让渡给用户;
  4. 还会要求当前依赖"流式逐字节执行、无前瞻"的实现做出重大改动。

无需JUMPDEST(Lack ofJUMPDEST

JUMPDEST有两个用途:

  1. 高效划分代码块——可用于预先计算某个block(即两个JUMPDEST之间的指令)的总 gas,以及 JIT/AOT 翻译;
  2. 显式标记合法跳转位置(否则任何非数据位置都可能是跳转目标)。

对静态跳转而言,这两点都不需要:分析器在 jumpdest 分析阶段可以直接从静态立即数识别跳转目标。由此带来两个直接收益:

  1. 每个跳转目标省掉 1 字节的JUMPDEST,部署时每个目标节省 200 gas;
  2. 执行时每次跳转额外节省 1 gasJUMPDEST自身消耗 1 gas 且跳转时会"执行"到它)。

RJUMPV/RJUMPSUBV的默认分支(fallback case)

RJUMPVRJUMPSUBVcase在表中找不到匹配(即default情形)时,执行继续顺序进行、不分支。这种设计允许用0填充跳转表中的空隙,并让程序员自由选择实现方式。另一种备选方案是在无匹配时触发异常中止,但被本提案放弃。

向后兼容

本变更对向后兼容性不构成任何风险。新指令是全新引入的 opcode,对现有 EVM 代码的语义没有任何改动。

测试用例

提案给出了完整的验证与执行测试用例清单(详见 EIPS/eip-8013.md 的Test Cases一节),可作为实现与测试驱动开发(TDD)的直接输入。

验证(Validation)

有效用例(Valid cases):

  • RJUMP/RJUMPI/RJUMPVJUMPDEST为目标,且relative_offset分别为正、负、0
  • RJUMP/RJUMPI/RJUMPV以非JUMPDEST指令为目标,且relative_offset分别为正、负、0
  • RJUMPV/RJUMPSUBV的各种合法表大小(1 到 256);
  • RJUMP作为代码段中的最后一条指令

无效用例(Invalid cases):

  • RJUMP/RJUMPI/RJUMPV立即数被截断(truncated);
  • RJUMPI/RJUMPV作为代码段中的最后一条指令;
  • RJUMPSUB/RJUMPSUBV的目标不是ENTERSUB
  • 所有五条指令的目标超出代码段边界;
  • 所有五条指令的目标指向 push 数据;
  • 所有五条指令的目标指向另一条RJUMP/RJUMPI/RJUMPV的立即数参数。

执行(Execution)

  • RJUMPrelative_offset分别为正、负、0
  • RJUMPIrelative_offset分别为正、负、0,且分别覆盖condition == 0condition != 0
  • RJUMPV 0 relative_offsetcase0与非0
  • RJUMPV表内含正、负、0偏移:case0、非0、越界(case > max_index落入 default)、case > 255
  • RJUMPSUBrelative_offset分别为正、负、0
  • RJUMPSUBV 0 relative_offsetcase0与非0
  • RJUMPSUBV表内含正、负、0偏移:case0、非0、越界、case > 255

安全考虑

  • 验证算法是安全核心:新增带立即数的指令,实现验证算法时必须谨慎。静态相对跳转的执行不需要在运行时检查跳转目标,这大幅降低了执行成本,也因此允许显著降低新指令的 gas;
  • DoS 面可控RJUMPVRJUMPSUBV的相对偏移表最多包含 256 个表项,读取一个偏移量不可能成为潜在的 DoS 攻击面。

从更广的视角看,静态控制流把"跳转目标"从运行时数据(数据栈)搬移到了指令编码本身,使代码的调用结构对分析工具、编译器与审计工具显式可见。这一点与 EIP-7979 的 return stack 设计一脉相承:返回地址存放在 EVM 代码不可触及的独立返回栈中,从机制上杜绝了数据覆盖返回地址这类控制流破坏攻击。

在仓库中的延伸阅读

  • 提案正文:EIPS/eip-8013.md;
  • 底层调用/返回机制(return stack、CALLSUB/CALLDEST/RETURNSUB及 EELS 参考实现):EIPS/eip-7979.md;
  • 控制流基础与扩容路线图(Informational 背景文档):EIPS/eip-8173.md,其 CFG 复杂度图示见 assets/eip-8173/control-flow.svg,路径遍历计数实验见 assets/eip-8173/walk-count/README.md(可用python3 walker.py复现);
  • EIP-7979 配套的 RISC-V 基准测量(解释执行、寄存器 IR、AOT 编译三档对比):assets/eip-7979/riscv/README.md,源码位于 assets/eip-7979/riscv/(aot.pyinterp.cir.py等)与 Yul 编译器/验证器 assets/eip-7979/yul-compiler/;
  • 历史前身:EOF 容器中的静态相对跳转提案 EIPS/eip-4200.md(RJUMP/RJUMPI/RJUMPV三指令的最早形态,仅限 EOF1 代码);EIP-8013 将其推广为不依赖 EOF 的通用指令,并新增子程序跳转形态RJUMPSUB/RJUMPSUBV

小结

EIP-8013 以最小化的五条指令,把 EVM 的控制流从"只有动态跳转"推进到"静态为主、动态为辅"的形态:RJUMP/RJUMPI覆盖绝大部分条件与无条件分支,RJUMPV以跳转表实现 switch-case 与默认分支,RJUMPSUB/RJUMPSUBV借助 EIP-7979 的 return stack 机制实现可重入的子程序调用与返回。所有跳转目标在验证阶段一次性确认,运行时零检查,换来部署与执行阶段的双重 gas 节省、更优的静态分析性质,以及向 JIT/AOT 编译、RISC-V 迁移与 ZK 证明效率提升等方向展开的演进空间。对实现者而言,EIPS/eip-8013.md 中详尽的验证/执行测试用例与 EIPS/eip-7979.md 的 Python 参考实现,构成了从语义到落地的完整闭环。

【免费下载链接】EIPsThe Ethereum Improvement Proposal repository项目地址: https://gitcode.com/GitHub_Trending/ei/EIPs

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询