读一个字节也要三条指令:EIP-8120提出MLOAD8与CALLDATALOAD8 图 1
读一个字节也要三条指令:EIP-8120提出MLOAD8与CALLDATALOAD8 · 图 1

三条指令读一个字节

EVM 的内存与 calldata 原生死于三十二字节字:读内存用 MLOAD、读 calldata 用 CALLDATALOAD,一次都是整整 32 字节。想取单字节?只能先整字读出、再压一个位移常数、再做右移,例如读 calldata 偏移 x 处的一个字节要写 PUSH xCALLDATALOADPUSH1 248SHR 这一串。对偶尔抠一个标志位的合约无所谓,但对把 calldata 当指令流逐字节解析的合约(解释器型协议、序列化容器、按标签遍历的元数据解析器),每个字节都要付一整轮取字加移位的钱,还要为每条读取在部署码里多塞约三个字节的移位脚手架。

EIP-8120(2026年1月起草,草案状态)补上这块不对称:给已经存在多年的单字节写入 MSTORE8 配一个镜像的读指令 MLOAD8,再给 calldata 配一个 CALLDATALOAD8

语义与边界规则

两条指令的栈签名一致:弹出一个偏移量,压回一个 32 字,把读到的字节放在最低位。MLOAD8 遵循与 MSTORE8 相同的内存扩展规则——内存先扩展到至少偏移加一字节,再取那一格;访问超出已分配内存的字节按零初始化返回零。CALLDATALOAD8 更干脆:偏移大于等于 CALLDATASIZE 时直接返回零,和 CALLDATALOAD 越界补零的旧惯例对齐。异常处理走标准路径:gas 不足或栈下溢即停机回滚当前调用帧。

定价刻意保守:基础费 3 gas,与 MLOADMSTORE8CALLDATALOAD 完全相同,MLOAD8 另按现行内存规则付扩展费。提案的逻辑是别给新指令开特殊价,和既有取数指令一致最不容易出争议。

一笔逐字节账

旧三连的裸账:CALLDATALOAD 3 gas、PUSH1 3 gas、SHR 3 gas,共 9 gas 读一字节,还没算压偏移量本身的开销;部署码每处多约 3 字节。换 CALLDATALOAD8 后 3 gas、约省 6 gas 与 3 字节。单次省 6 gas 听着寒酸,乘上解析循环就换了量级:一个遍历一千字节标签流的解析器,光读字节就从九千 gas 降到三千 gas,部署码省三千字节——别忘了部署费每字节 200 gas 上下,三千字节又是六十万 gas 的一次性 savings,往往比运行时省得多。这正是提案强调收益在字节导向合约中复利叠加的原因:写死在循环里的成本乘数,决定了小指令的大价值。

为什么是现在

单字节对称性其实缺席了很久:MSTORE8 自创世就有,读侧一直只能用整字加掩码凑合。提案的动机段没讲新故事,讲的是解释器型 calldata、指令流解析这些既有模式长期付的冤枉钱,以及 MLOAD8MSTORE8 在概念上的补齐——一读一写、命名对称,读代码的心智负担也降。操作码值在草案里仍标待定,说明连编码位都还在协商,属于典型的早期小切口提案:改动面小、语义无争议空间,历史上一批类似的补洞指令都有过命,也都有过胎死腹中,落地与否看的是优先级不是难度。

循环里的复利从哪来

把一万字节标签解析摊开:旧写法每字节的指令流是取偏移、CALLDATALOAD、压常数 248、右移,四步十二 gas 外加掩码不进、还得小心 SHR 把字节留在高位;循环控制本身每圈再叠比较与跳转。换成 CALLDATALOAD8 后每字节一步三条降到两到三步、9 对 3 的差值原样保留,一万圈循环光读字节就省约六万 gas,若解析器部署在库里被所有用户共享,部署码里那三万来字节的脚手架折算的部署费也由发布方一次付清。规模再大一级——把 calldata 当字节码解释的链上虚拟机——每执行一条单字节操作码都收一次过路费, savings 与指令执行数成正比,这类合约是提案动机段点名的头号受益者,省的是模型税率的钱。

快速问答

问:和位运算读字节的传统技巧比省在哪?省的是取整字加移位的固定税与部署码膨胀,循环越多差距越大。问:返回零的越界规则和谁一致?与 CALLDATALOAD 越界补零同构,合约不必加尺寸检查分支。问:Solidity 会自动用吗?编译器要用起来得等工具链支持单字节读取模式,草案阶段不会。

风险提示:本文仅解读协议草案,不构成投资建议;操作码值与定价以官方 EIP 文本为准。