打开任何一份讲以太坊优化技巧的文章,多半会撞见一段长得像函数调用套娃的代码——那就是 Yul。Solidity 官方文档给它的定位是低级语言,同时是一种可以独立编译的中间语言,也是新版基于 IR 代码生成器的落地目标。对读者来说,理解 Yul 的价值不在学会写它,而在于看懂合约从高级语言到字节码之间到底经历了几层翻译,每层各丢了什么、留了什么。
三层翻译:Solidity、Yul、字节码
把 Solidity 直接编译成十六进制操作码序列当然可行,旧管线也确实这么走过。问题是所有优化都得在生成字节码之后做——操作码跳转图上的优化,等于在机器码上雕花。中间插一层 Yul 后分工变了:前端负责把高级语义降级成贴近栈机但保留结构化控制流的表示,优化器在这一层做常量折叠、死代码清除、内联展开这类高杠杆手术,最后才生成操作码。Solidity 文档明确说 Yul 适合作为对所有目标平台同等受益的高级优化阶段目标;同时它是静态类型的——类型检查发生在编译期,这和它语法看起来像汇编并不矛盾。方言概念也值得知道:Yul 语法本身与具体虚拟机解耦,带函数与用户函数的版本对应 EVM 方言,字节码生成目前主要针对 EVM 方言完整实现。
内联汇编:合法越狱的护栏
Solidity 允许在合约里写 assembly { ... } 块,里面就是 Yul。官方立场是谨慎支持:能用高级语法就用高级语法,内联汇编留给三件事——极致 Gas 优化、访问高级语法未暴露的操作码、实现新语言特性。常见的正当用例是批量读存储槽、手写循环里省掉边界检查开销、调用 mcopy 一类低级原语。护栏依然存在:内联汇编不会帮你做安全检查,越界读写内存与存储、绕过 solidity 的函数可见性直接改槽位,编译器只负责翻译成准确的操作码,不负责替你评估这样做对不对。审计视角下的经验是:一个合约里 assembly 块的数量和圈复杂度,是判断它是否值得额外审查的最便宜信号。
功能式语法背后的栈机
Yul 长得像函数式语言,根子在 EVM 的栈结构上。mload(add(x, 0x20)) 一行对应一套压栈弹栈:先把 x 压上、再压常数、跑一次加法、拿结果去 mload。用嵌套调用的写法表达压栈顺序,比手写 PUSH PUSH ADD MLOAD 更不易错,同时优化器能把表达式树重排——这正是中间层的红利:同一段语义可以选更短的指令序列。另一个对读者有用的事实是变量寻址:Yul 里局部变量直接映射栈槽,编译器负责保持栈深不越过协议上限。
一条算术直觉
估算内联汇编的收益要有锚点:一条操作码的 Gas 从几个到几千不等,一个 Solidity 语法糖展开成几条到几十条操作码。手写成 Yul 省下的通常是个位数到十几条指令的量级,除以当前基础费才是真实收益;一旦引入越界风险,最坏损失是整个合约余额,这个不对称决定了内联汇编永远是收益明确才动的工具,而非默认写法。
读编译产物的最小姿势
编译器把 Yul 摆在输出接口的哪个位置?用 Solc 的 IR 输出选项可以同时拿到降级后的 Yul 与最终操作码,两栏对照着读,能直观看到一次赋值被拆成几条存储操作。审计工具里那些行级覆盖率与跳转图,多数也直接构建在这层中间表示上。把它当汇编器与高级语言之间的合同页来读,比背语法更有长期价值。
快速问答
问:不会 Yul 能写合约吗?答:完全可以,日常合约不需要手写 Yul;读懂它主要是为了读编译产物与审计报告。
问:Yul 和 Fevy、Vyper 的中间层是一回事吗?答:都是中间表示思路,但方言不同;Yul 特指 Solidity 家族采用的那一层。
问:怎么验证某段 assembly 生成了什么?答:用编译器输出接口导出操作码与 Yul IR 对照,源码映射与反编译器的结论要交叉核验。
风险提示:本文为编译机制科普;生产合约慎用内联汇编,任何绕过语言安全特性的写法都应在部署前经过完整测试与审计。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。