FABRKNT
Inside Revm — EVM エンジンを読む
Revmの心臓部
レッスン 2 / 17·CONTENT8 分20 XP
コース
Inside Revm — EVM エンジンを読む
レッスンの役割
CONTENT
順序
2 / 17

レッスン1 — add をステップで組み立てる:シグネチャと本体

問い

ADD は EVM Opcode で最も単純な非自明: 数値 2 つ pop、和を push。Revm は 4 行で済ませているが、その 4 行に型パラメータ 2 つのジェネリック + ?Sized opt-out + アンダーフローガード + 分岐予測ヒントへ展開されるマクロ + wrapping_add+ だと最初のオーバーフローでメインネットから分岐)が詰まっている — どうやって到達するか?

原理(最小モデル)

  • 本物の形. pub fn add<IT: ITy, H: ?Sized>(context: Ictx<'_, H, IT>) -> Result { popn_top!(...); *op2 = op1.wrapping_add(*op2); Ok(()) }
  • 5 ステップ積み上げ. 素朴版 → <H: Host>?SizedIT: ITy&mut その場 + wrapping_add
  • モノモーフ化. <H: Host> は具象 H ごとに特殊化バイナリ = 静的ディスパッチ、vtable なし。
  • ?Sized opt-out. Rust の暗黙 Sized を外すと &mut dyn Host が渡せる、vtable 間接化と引き換え。
  • IT: ITy. 実行モード(本番 / トレース / Inspector)をコンパイル時に選ぶ marker、特殊化バイナリで実行時切替コストゼロ。
  • &mut その場書き込み. 3 スタック操作(pop + pop + push)→ 1 操作(pop + 上書き)。
  • wrapping_add 必須. EVM コンセンサスは mod 2²⁵⁶ wrap を要求、+ は debug panic / release wrap で分岐、saturating_add はネットワークフォーク。

具体例

ステップ 0 — 素朴版:

pub fn add(stack: &mut Vec<U256>) -> Result<(), &'static str> {
    let a = stack.pop().ok_or("underflow")?;
    let b = stack.pop().ok_or("underflow")?;
    stack.push(a + b);
    Ok(())
}

2 つ落とし穴: ① &mut Vec<U256> は具象型、トレーサー / ファザー / Inspector のスタックに差し替え不能、② pop + pop + push = 3 回スタック操作。

ステップ 1 — ホストジェネリック:

pub fn add<H: Host>(host: &mut H) -> Result {
    // ... 本体は同じだが、具象 Vec ではなく H に対して呼ぶ
}

<H: Host> = 「Host トレイトを実装する任意の型」、コンパイラがモノモーフ化で具象 H ごとに特殊化バイナリ出力。落とし穴: trait object &mut dyn Host を渡せない(<H: Host> はコンパイル時サイズ既知のみ)。

ステップ 2 — ?Sized で trait object 許可:

pub fn add<H: Host + ?Sized>(host: &mut H) -> Result {
    // ...
}

?Sized で Rust の暗黙の Sized 制約を外す = &mut dyn Host が有効引数に = 1 バイナリで全 Host 実装、vtable 間接化と引き換え。

ステップ 3 — IT: ITy で実行モード抽象化:

pub fn add<IT: ITy, H: Host + ?Sized>(host: &mut H) -> Result {
    // ...
}

IT = 「interpreter-types」マーカー、ストラテジパラメータ。同じソースが本番 / トレース / Inspector サンドボックスごとに特殊化バイナリへコンパイル。本物の add シグネチャ:

pub fn add<IT: ITy, H: ?Sized>(context: Ictx<'_, H, IT>) -> Result {

Host 制約は Ictx 側に移っている — add に残るのは IT: ITyH: ?Sized。)

ステップ 4 — 本体を &mut その場書き込み:

let a = stack.pop().ok_or(StackUnderflow)?;       // op1 を pop
let b = stack.last_mut().ok_or(StackUnderflow)?;  // 新トップ &mut
*b = a + *b;                                       // その場上書き

pop 1 + その場書き込み 1 = push なし-> Result に連結値なし、データの流れは参照経由。

ステップ 5 — wrapping_add:

*b = a.wrapping_add(*b);

EVM コンセンサス = ADD mod 2²⁵⁶ wrap、+ は debug panic / release wrap で分岐、saturating_add は最初のオーバーフローでネットワークフォーク。U256::MAX.wrapping_add(U256::from(1)) = 0x0

到達した本物の形:

pub fn add<IT: ITy, H: ?Sized>(context: Ictx<'_, H, IT>) -> Result {
    let op1 = context.interpreter.stack.pop().ok_or(StackUnderflow)?;
    let op2 = context.interpreter.stack.last_mut().ok_or(StackUnderflow)?;
    *op2 = op1.wrapping_add(*op2);
    Ok(())
}

中央 2 行は次レッスンで popn_top! マクロに抽出。

失敗例(誤解)

<H: Host> だけで十分」— 間違い。trait object &mut dyn Host(設定フラグ / 動的テストハーネス)には ?Sized 必須。

IT: ITy は飾り」— 間違い。これがないと add を本番 / トレース / Inspector の 3 回書く羽目、または実行時分岐で速度ペナルティ。

+ でも release ビルドなら大丈夫」— 致命的。Rust の + は debug panic / release wrap = ビルド分岐 = コンセンサス契約に載せられない。wrapping_add で明示 = EVM コンセンサスが要求する正確な挙動。

ステップで組み立てる

Step 1: 素朴版を確認

pop + pop + push、3 操作、ホスト固定。

Step 2: <H: Host> でホストジェネリック

モノモーフ化で特殊化バイナリ。

Step 3: + ?Sized で trait object 許可

&mut dyn Host 有効、vtable 間接化と引き換え。

Step 4: <IT: ITy, H: ?Sized> で実行モード抽象化

IT ストラテジパラメータ、特殊化バイナリ。

Step 5: &mut その場書き込み + wrapping_add

1 操作、-> Result 連結値なし、mod 2²⁵⁶ wrap。

答え合わせ

  • IT: ITy がくれるもの: 実行モードの 静的選択、特殊化バイナリで実行時切替コストゼロ。なければ 3 回書くか実行時分岐。
  • ?Sized が許すもの: trait object &mut dyn Host。Rust の暗黙 Sized 制約を外す、vtable 間接化と引き換え。
  • その場書き込みが勝つ理由: 3 操作 → 1 操作、ホットパス(毎 tx 毎 Opcode 数万 + 毎ブロック数百万 + CI 秒間数億)で実測される差。
  • U256::MAX.wrapping_add(U256::from(1)): 0x0(mod 2²⁵⁶ wrap)= EVM コンセンサス要求。saturating_add だと U256::MAX のまま → メインネットフォーク。

合格基準

  • 本物の add 4 行を即書ける。
  • 5 ステップ積み上げを順に言える。
  • モノモーフ化と ?Sized の trade-off を 1 文で説明できる。
  • IT: ITy の役割を即答できる。
  • wrapping_add 必須の理由を言える。

まとめ(3行)

  • 本物の add は 4 行: ジェネリック <IT: ITy, H: ?Sized> + popn_top! + wrapping_add その場書き込み + Ok(())
  • 5 ステップ積み上げ(素朴 → <H: Host>?SizedIT: ITy&mut + wrapping_add)、各ステップに明確な動機。
  • EVM コンセンサスは mod 2²⁵⁶ wrap を要求、+saturating_add 置換は分岐 / フォーク、次レッスンで中央 2 行を popn_top! マクロに抽出。