Alert Source Discuss
⚠️ Draft Standards Track: Core

EIP-7937: EVM64 - 64 位模式 EVM 操作码

用于 EVM 中 64 位算术、比较、按位和流操作的多字节操作码。

Authors Wei Tang (@sorpaas)
Created 2025-04-23
Discussion Link https://ethereum-magicians.org/t/eip-9687-64-bit-mode-evm-operations/23794

摘要

此 EIP 引入了以 C0 为前缀的多字节操作码,用于 64 位算术 (C001-C00B)、比较 (C010-C015)、按位 (C016-C019) 和流 (C056C057) 操作。

动机

并非 EVM 中的所有计算都可以利用完整的 256 位整数宽度。因此,拥有一个“64 位模式”以避免不必要的周期可能是有益的。此 EIP 使用“前缀”操作码 C0,本质上是形成多字节操作码,以避免过多地污染 EVM 操作码空间。

规范

本文档中的关键词“必须(MUST)”、“禁止(MUST NOT)”、“需要(REQUIRED)”、“应为(SHALL)”、“不应为(SHALL NOT)”、“应当(SHOULD)”、“不应当(SHOULD NOT)”、“推荐(RECOMMENDED)”、“不推荐(NOT RECOMMENDED)”、“可以(MAY)”和“可选(OPTIONAL)”应按照 RFC 2119 和 RFC 8174 中的描述进行解释。

前缀操作码行为

此 EIP 使用前缀操作码 C0,它仅占用这单个 EVM 操作码空间。当解释器遇到操作码 C0 时,它必须继续寻找代码中的下一个字节。然后,它根据下面描述的第二个字节,以“64 位模式”执行操作。如果执行成功,则解释器必须将 PC 增加 2(而不是 1)。

如果第二个字节不是有效的 64 位模式操作,则解释器必须 OOG。

一般 64 位模式行为

在 64 位模式下,所有操作仅作用于每个堆栈值的最低有效 64 位。最高有效 192 位将被丢弃。当结果值被推回堆栈时,它必须确保可观察到的效果将看到最高有效 192 位为零。请注意,这里解释器不必每次都将最高有效 192 位重置为零——如果下一个操作码仍然是 64 位模式,那么最高有效 192 位仍然是不可观察的。我们将在“原理”部分讨论完整的细节。解释器只需要在进入非 64 位模式时再现完整的 256 位值。如果完整的计算密集型部分可以用纯 64 位模式编写,那么这可以带来显着的性能提升。

Gas 成本常量

我们定义以下 gas 成本常量:

  • G_BASE64: 1
  • G_VERYLOW64: 2
  • G_LOW64: 3
  • G_MID64: 5
  • G_HIGH64: 7
  • G_EXP64_STATIC: 5
  • G_EXP64_DYNAMIC: 25

算术操作码

64 位模式算术操作码的定义与非 64 位模式相同,只是它仅作用于最低有效 64 位。在下面的定义中,abNa mod 2^64b mod 2^64N mod 2^64

  • ADD (C001) 和 SUB (C003): a op b mod 2^64,gas 成本 G_VERYLOW64
  • MUL (C002)、DIV (C004)、SDIV (C005)、MOD (C006)、SMOD (C007)、SIGNEXTEND (C00B): a op b mod 2^64,gas 成本 G_LOW64
  • ADDMOD (C008)、MULMOD (C009): a op b % N mod 2^64,gas 成本 G_MID64
  • EXP (C00A): a EXP b mod 2^64,gas 成本 static_gas = G_EXP64_STATIC, dynamic_gas = G_EXP64_DYNAMIC * exponent_byte_size

比较和按位操作码

64 位模式比较和按位操作码的定义与非 64 位模式相同,只是它们仅作用于最低有效 64 位。

  • LT (C010)、GT (C011)、SLT (C012)、SGT (C013)、EQ (C014)、AND (C016)、OR (C017)、XOR (C018): a op b mod 2^64,gas 成本 G_VERYLOW64
  • ISZERO (C015)、NOT (C019): op a mod 2^64,gas 成本 G_VERYLOW64
  • SHL (C01B)、SHR (C01C)、SAR (C01D): a op N mod 2^64,gas 成本 G_VERYLOW64

请注意:

  • 64 位 EQ (C014) 可能会对两个不同的整数返回 true,因为它只会比较最低有效 64 位。
  • 64 位 ISZERO (C015) 可能会对非零 256 位整数返回 true,只要最后 64 位为零。
  • BYTE (1A) 没有 64 位模式,因为它会影响字节序。

JUMP, JUMPI

对于流操作 JUMP 和 JUMPI,行为如下:

  • JUMP 将仅从堆栈值中读取最后 64 位。其余 192 位将被丢弃而不读取。Gas 成本为 G_MID64
  • JUMPI 将仅从堆栈中读取最后 64 位作为目标,并且条件检查将仅读取最后 64 位。Gas 成本为 G_HIGH64
  • JUMPDEST 验证短语中,C0 被认为是独立的“模式”操作码,如果跟随的下一个字节是 JUMPDEST,它将继续标记为有效的 JUMPDEST 目标。请注意,由于没有 64 位 JUMPDEST,因此在执行期间,C0 JUMPDEST 将导致 OOG。

原理

当智能合约使用 64 位模式时,预计一旦进入,它将希望保持在 64 位模式,并且仅在计算密集型函数完成后才退出到非 64 位模式。此 EIP 的设计特别考虑了这一点。

所有 64 位操作码仅作用于 64 位值。它完全丢弃了其余 192 位。解释器只需要确保当它退出到非 64 位模式并且下次读取值结果时,该值的前 192 位重置为零。因此,EVM 解释器可以使用类型化的堆栈进行优化:

type StackItem = Value U256 | Value64 U64

类型化的堆栈也可以实现为用于内存对齐的位图。

对于 64 位操作码的所有输入,它将读取 Value(此时它只会获取最后 64 位)或 Value64(这是所需的)。然后它总是输出 Value64。在退出到非 64 位模式并且读取 Value64 后,解释器然后通过将前 192 位扩展为零将该值转换回 256 位 Value

64 位模式不包含任何依赖于值的字节序的操作码,因此 Value64 也可以存储在架构的最佳字节序中。

64 位模式不会节省任何内存使用量。

讨论

前缀(模式)操作码

此 EIP 还建议我们保留 C0-CF 用于前缀(模式)操作码。例如,可以设想另外一种模式 OVERFLOW,它可以将算术操作码的行为从包装更改为溢出 OOG,这可以帮助减少例如 SafeMath 所需的额外周期。

优化假设

此 EIP 假设大多数 EVM 实现的目标是原生小端 64 位架构(如 x86_64arm64riscv64)。一个 256 位堆栈项在内部有效地存储为 4 个 64 位无符号整数项 [u64; 4]。就优化而言,此 EIP 对于这些实现效果最佳。这些操作码只是作用于最后一个 u64,而不是整个 [u64; 4]。基本上没有其他变化。

字节序

在 64 位模式下,规范的原则是字节序应严格保留为实现细节。不应该有任何依赖于字节序的操作码。这很重要,因为一方面,我们必须为 64 位和非 64 位操作码启用简单快速的互操作。另一方面,解释器应该能够内部进行优化,无论它使用小端还是大端。

由于字节序问题,此 EIP 目前不包含 64 位模式 BYTE64、内存操作码 MLOAD64MSTORE64 以及堆栈推送操作码 PUSH*64 (PUSH1PUSH8)。

可以扩展 EVM64,其中“64 位模式”被定义为“小端 64 位模式”,这可以单独讨论和指定:

  • BYTE64 将是小端,因此不是 (x >> (248 - i * 8)) & 0xFF,而是 (x >> i * 8) & 0xFF
  • MLOAD64MSTORE64 在内存中加载和存储小端 64 位值。
  • PUSH*64 (PUSH1PUSH8) 接受字面小端字节。

在规范中,该设计没有问题,但这可能会给开发人员带来很大的困惑。因此,作者建议谨慎行事。

POP, DUP*, SWAP*

堆栈操作没有显式的 64 位模式:

  • POP 是“自动 64 位”,因为它仅从堆栈中删除值。
  • DUP*SWAP* 将按原样复制或交换针对 64 位优化的堆栈项。它们保持优化状态,因此无需为这些操作码单独设置 64 位模式。

缺点

64 位模式的已知缺点和权衡包括:

  • 二进制文件大小会变大。这是显而易见的,因为之前的操作码是单字节,但现在是双字节。每个字节都需要额外的 200 gas 才能存入。但是,64 位模式操作码(通常)更便宜,这为开发人员提供了充分的激励来利用它(最多 200 个调用,并且累积的 gas 成本将更便宜)。

向后兼容性

此 EIP 引入了一个新的(前缀)操作码 C0C0 以前是一个无效的操作码,使用率很低,因此向后兼容性问题微乎其微。

测试用例

测试用例组织为 [stack_item_1, stack_item_2] C0 操作码 => [result_stack_item_1, result_stack_item_2]

  • [ff00000000000000 000000000000000 0000000000000ff 000000000000001, ff0000000000000 000000000000000 0000000000000ff f0000000000000f] C0 SHR => [<0> 780000000000007]

参考实现

待添加。

安全考虑事项

待添加。

需要讨论。

版权

CC0 下放弃版权及相关权利。

Citation

Please cite this document as:

Wei Tang (@sorpaas), "EIP-7937: EVM64 - 64 位模式 EVM 操作码 [DRAFT]," Ethereum Improvement Proposals, no. 7937, April 2025. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7937.