news 2026/9/16 18:30:12

EIP-7979 的 Yul 子集编译器:以代码回答“编译器会用 CALLSUB/CALLDEST/RETURNSUB 吗“

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
EIP-7979 的 Yul 子集编译器:以代码回答“编译器会用 CALLSUB/CALLDEST/RETURNSUB 吗“

EIP-7979 的 Yul 子集编译器:以代码回答"编译器会用 CALLSUB/CALLDEST/RETURNSUB 吗"

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

EIP-7979(Call and Return Opcodes for the EVM)为 EVM 引入CALLSUBCALLDESTRETURNSUB三条控制流指令。本仓库中的 yul-compiler 是这一提案的代码级回答——"will compilers use it?"——一个把 Yul 子集编译为上述三条指令的编译器,并配有 legacy 后端做同口径对比。读完本文,你将掌握该编译器的子集语法、双后端编译原理、验证/执行/对比的测试闭环,以及实测的字节与 gas 节省数据。

一句话定位:把"函数"翻译成三条指令

Yul 语言本身就有函数概念,但今天的 EVM 没有调用/返回指令,编译器只能把函数调用翻译成"压返回地址 + 动态 JUMP",把函数返回翻译成"经数据栈跳回"。这正是 EIP-7979 想解决的问题——动态跳转让控制流对人和工具都不可见。

这个仓库的编译器证明了替换方案可行:函数定义近一对一地映射为CALLDEST … RETURNSUB,函数调用映射为PUSH CALLSUB,并且编译产物能通过配套验证 EIP(validator.py)的参考验证器。同时它保留了一个 legacy 后端,用今天编译器必须用的方式(动态跳转合成调用与返回)生成同一批程序,用于同口径对比代码大小和 gas。

支持的 Yul 子集:刻意"不聪明"

compile.py 的模块文档明确划定了子集边界:

  • 支持:零或一个返回值的函数定义、let、赋值、if、带break/continueforleave、函数调用,以及日常内建函数(add/mul/sub/div/sdiv/mod/smod/exp/lt/gt/slt/sgt/eq/iszero/and/or/xor/not/byte/shl/shr/sar/calldataload/calldatasize/pop/mload/mstore/mstore8/stop/return/revert);
  • 不支持switch、多返回值、优化器;
  • 约束:局部变量必须在DUP16可达深度内(即栈上槽位深度超过 15 时,compile.py 的dup_of会直接报outside the subset)。

设计意图在 README 里说得很直白:"The point is not coverage; it is that nothing about the translation is clever."——重点不是覆盖面,而是证明翻译过程毫无取巧。代码生成器是同一套朴素的栈调度器,两个后端共享,唯一区别只在于调用与返回的发射方式;而 7979 后端反而是更简单的那一半:一次调用只有两条指令(替代四条指令加一个标签),一次返回完全不需要地址传递。

调用约定的本质差异

两个后端的核心分歧在 compile.py 的user_call与 epilogue:

  • 7979 后端:调用方从左到右压参数,然后执行CALLSUB;被调方的RETURNSUB把返回值留在参数原来所在的位置。
  • legacy 后端:调用方先把返回标签压到参数之下,再跳转;被调方通过这个地址跳回。返回地址和数据混在同一根数据栈上,返回必须SWAP+JUMP

对照 EIP-7979 主文档 eip-7979.md 中"为什么允许 JUMP 落在 CALLDEST"一节:把调用换成跳转可以消除尾调用,gRETURNSUB直接返回给f的调用者,少一条指令、少一个返回地址,递归还能在恒定返回栈深度下运行——这正是编译器依赖的尾调用、互递归、状态机与共享尾声(shared epilogues)变换。这个编译器用实测数据印证了该设计。

双端对比的真实数据:约 13% 字节、5% gas

README 的 "Measured" 一节给出 8 个程序的双端对比表,我在当前仓库实际运行python3 test_translator.py得到的输出与此完全一致(详见下节测试闭环):

programbytes savedgas saved
square18%14%
sum of squares24%16%
abs12%10%
fib (loop-heavy)7%1%
factorial (recursive)17%11%
sum words8%4%
find (break)6%4%
guard (leave)13%11%
total13%5%

节省量与调用密度强相关,符合预期:以循环为主的fib几乎无收益,调用密集的sum of squares收益最大。README 特别强调这是下限数字(floor numbers)——如果后端做尾调用消除和共享尾声优化,收益还会更高。这也与主文档中 square 例子 手算的 29% 更少字节、32% 更少 gas 相互印证:程序越大、优化越充分,相对差距越小,但使用CALLSUB的代码在字节与 gas 上始终不劣于等价旧代码。

测试闭环:编译→验证→执行→对比

README 的 "The loop" 一节给出入口命令:

python3 test_translator.py

test_translator.py 对每个示例程序(squaresum of squaresabsfibfactorial (recursive)sum wordsfind (break)guard (leave))同时编译两种后端,并断言四件事:

  1. 7979 输出必须通过验证器validator.validate(new)为真);
  2. legacy 输出必须通不过验证器——因为它的动态返回跳转无法通过静态验证,这正是 EIP 要解决的痛点(test_translator.py);
  3. 两套字节码在每个测试输入上执行结果完全一致(用run7979.py这个内置三条指令的小型 EVM 执行,逐一比对(status, returndata));
  4. 逐行打印字节数与 gas 的并排对比表

每个程序的 calldata 用例也很讲究,例如abs同时测正数与负数(2**256 - 5的补码表示)、factorial覆盖1/5/12find (break)同时验证"找到即 break"与"找不到返回0xff…ff"。我实际运行该脚本,8 个程序全部PASS,汇总行显示总字节 359 vs 413(-13%)、总 gas 5507 vs 5847(-5%),与 README 表完全吻合。

解释器:带 EIP-7979 运行时语义的最小 EVM

run7979.py 是一个足够执行编译器产物的 EVM 解释器,其语义严格对齐 EIP 规范:

  • CALLSUB:弹出目的地址;目的必须是CALLDESTcode[dest] != CALLDEST即返回CALLSUB to non-CALLDEST停机);返回栈已达 1024 则return stack overflow;否则压入pc + 1并跳转(run7979.py);
  • RETURNSUB:返回栈为空则return stack underflow,否则弹栈设 PC(run7979.py);
  • gas 采用 Yellow Paper 分层(very low 3、low 5、mid 8、high 10、jumpdest 1)加 EIP 提案价(CALLSUB8、CALLDEST1、RETURNSUB5,见 GAS 表);
  • 内存扩展不收费——这是刻意的:对比发生在同一程序的两次编译之间,两者内存使用完全相同,去掉该收费才能让差距只反映调用/返回机制的差异(run7979.py)。

它还保留了对 legacy 代码的支持:jumpdest_analysis像今天的客户端一样做 JUMPDEST 分析,标记动态跳转的合法目的地——"The runtime scan validated code no longer needs"(验证过的代码不再需要的运行时扫描)。

验证器与操作码表:独立成文、逐字节一致

validator.py 是配套验证 EIP 的参考验证器。它验证五条约束:合法操作码、目的地可证明、返回被正确封装(framed returns)、无栈下溢、每个指令只到达一个静态栈偏移。其关键设计是:

  • 遍历被限制在 JUMPDEST 分析找到的指令集合内——PUSH 立即数无论内容如何都不可执行、不可跳转(validator.py);
  • 数据栈深度以CALLDEST为基准相对测量,返回点等待被调方的净栈效果,跳入/落入CALLDEST则把两个子程序的净效果链接起来;
  • 需求(demand)只增不减且以栈上限封顶,最坏情况是O(1024 * n)——对代码线性,因为 1024 是协议常量(validator.py);
  • 它不证明溢出(递归存在时不可判定),运行时边界检查保留——这与 EIP-7979 的运行时停机条件一致。

opcodes.py 提供共享的操作码表:opcode_info()给出每个操作码的 size/pops/pushes/是否终止基本块,push_value()读取 PUSH 立即数。表中明确标注CALLSUB: (1, 0, True), CALLDEST: (0, 0, False), RETURNSUB: (0, 0, True)——即CALLSUB弹 1 压 0 且终止基本块,CALLDEST是纯标签,RETURNSUB不碰数据栈但终止基本块。

一个值得注意的工程决策:README 的 Files 一节说明,validator.pyopcodes.py是从验证 EIP 资产中逐字节复制的精确副本("Exact copies: these assets must stand alone"),必须与原件保持 byte-identical——这样每个资产目录都能独立工作。

与主文档的呼应:从规范到实现的完整链条

把这条资产链路放回 EIP-7979 的整体叙事中看:

  • 主文档 eip-7979.md 定义语义:CALLSUB(mid/8 gas)弹目的地、压PC+1进返回栈、跳转,目的地非CALLDEST或返回栈满 1024 则异常停机;CALLDEST(jumpdest/1 gas)纯标签、同时也是合法JUMP/JUMPI目的地;RETURNSUB(low/5 gas)返回栈空则停机(eip-7979.md)。占位操作码CALLSUB=0xB0CALLDEST=0xB1RETURNSUB=0xB2在编译器、解释器、验证器三处完全一致。
  • 主文档的 EELS 参考实现(callsub/calldest/returnsub)展示了机器状态新增return_stack字段、RETURN_STACK_LIMIT = 1024,以及单趟get_valid_destinations扫描——本仓库解释器与验证器用可运行代码复现了同一套语义。
  • 主文档的 实时性能与 ZK 部分 论述静态控制流让 AOT/JIT 在线性时间内完成分析,并用 RISC-V 内核测量给出 5x–51x 的证明成本下降——编译器产出的"验证通过"代码正是这种分析的前置条件。

一句话总结:这个编译器仓库不是 EIP-7979 的附庸,而是它的试金石——它用真实可运行的 Yul 程序证明,一旦有了三条指令,编译器可以近乎一对一地发射它们,产物可通过静态验证,且在字节与 gas 上稳定优于今天的动态跳转合成方案。

复现与深入阅读

# 在仓库根目录进入资产目录后运行测试闭环 cd assets/eip-7979/yul-compiler && python3 test_translator.py

关键文件索引:

  • yul-compiler/README.md — 编译器设计文档与测量表
  • compile.py — tokenizer、递归下降解析器、双后端代码生成器
  • test_translator.py — 8 个示例程序的编译/验证/执行/对比闭环
  • run7979.py — 带 EIP-7979 语义与 Yellow Paper gas 的小型 EVM
  • validator.py / opcodes.py — 验证 EIP 的参考验证器与其操作码表
  • EIPS/eip-7979.md — EIP-7979 完整规范、测试用例与 EELS 参考实现
  • riscv 资产目录 — RISC-V AOT/JIT 与 ZK 证明成本的测量复现

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 18:29:41

Unity工程框架设计:UI分层、xLua热更与资源生命周期管理

简介:这是一套面向Unity开发者的完整框架方案压缩包,聚焦UI系统、热更新、资源管理、多线程与数据处理等核心模块,旨在帮助中高级开发者快速搭建项目基础架构,减少重复造轮子与后期维护成本。包体共436个文件、约8.61MB&#xff0…

作者头像 李华
网站建设 2026/9/16 18:28:52

基于STM32的超声波测厚仪:从原理图到源程序的完整设计

简介:基于STM32单片机设计的超声波测厚仪解决方案,面向嵌入式系统课程设计、电子竞赛及工业测控项目参考,利用超声波反射原理实现1.2mm-225mm范围内物体厚度的非接触测量,误差控制在(1%H0.1)mm以内,兼顾体积小、操作方…

作者头像 李华
网站建设 2026/9/16 18:27:39

深视智能SR系列3D相机SDK开发实践:连接、调参与点云获取

简介:深视智能SR系列3D相机SDK程序文件,面向工业视觉领域需要对该系列相机进行二次开发的工程师与集成商,解决SDK调用中不同数据采集模式的选型与实现问题。包内程序文件围绕SDK提供了四种典型模式说明:一次回调模式适合设定采集行…

作者头像 李华
网站建设 2026/9/16 18:27:08

UE5游戏资源解包实战:从Pak文件到资产提取全流程

2025年的游戏圈,虚幻引擎几乎成了默认选项。Steam新品榜上十款里七八款挂着UE5的标,从独立作品到3A大作都用同一套资源管线。也正是因为这样,游戏目录里那堆.pak文件越来越常见,装满了我按捺不住的好奇心:这些动辄几十…

作者头像 李华