レッスン15 — インタープリターの先へ — revmc による JIT/AOT コンパイル
問い
ここまで revm を インタープリター として扱った = Opcode → Rust 関数 → ループ、通常 mainnet 負荷には十分速い。MegaETH 等 6 桁 TPS チェーンではインタープリターループ自体がボトルネック、抜け穴 = インタープリトをやめてコンパイルする。Paradigm の revmc は revm 上に乗る = どう動くか?
原理(最小モデル)
- revmc は revm の置き換えではない. revm の上の層、インタープリターは依然として コンパイラが一致しなければならない仕様、コンパイル失敗は revm にフォールバック。
- JIT vs AOT. 目的地同じ(ネイティブ機械語)、タイミング違う。JIT = 実行時最初の呼び出しでコンパイル + code hash でキャッシュ、AOT = 既知ホット bytecode を事前にコンパイルしてバイナリ同梱。
- 3 困難. ① ガス会計の決定論性(LLVM 最適化が壊しうる)、② EVM スタック/メモリモデル非ネイティブ(U256 はレジスタに収まらない)、③ 副作用(SLOAD / SSTORE / CALL)はホスト呼び出し。
- ガス計測明示 IR の重要性. revmc は各 Opcode のガス差し引きを明示的 IR(load → sub → compare → conditional branch to OutOfGas)で出力、オプティマイザに推論させない、観測可能な書きだから最適化で消せない。
- コンパイル済み EVM が C 並みに速くない理由. EVM はスタックマシン + 256-bit ワード、U256 はネイティブレジスタに収まらない、算術 Opcode が load-load-op-store のシーケンスに下がる、勝ちは「ディスパッチループがなくなる + オプティマイザが Opcode 越しに見える」。
- 副作用 = revmc-builtins ランタイム呼び出し. SLOAD / SSTORE / CALL 等は不透明な副作用ありの呼び出し、LLVM は並べ替えない、ホスト環境(Database 実装)にルーティング。
JitEvm統合点.crates/revmc-contextのJitEvmがEvmTrベース EVM をラップしframe_runを上書き、code hash がコンパイル済みマップにあれば直接ディスパッチ、なければ on_miss フック or revm インタープリターフォールバック。- AOT vs JIT 選択. AOT = ホットセット既知(システムコントラクト / WETH / DEX ルーター)+ 起動レイテンシ予測可能 / JIT = ホットセット負荷依存(汎用 RPC / インデクサ)+ コンパイルコスト稼働時間で償却。
具体例
JitEvm(概念):
pub struct JitEvm<EVM, F = ...> {
inner: EVM,
functions: B256Map<RawEvmCompilerFn>, // code hash -> compiled fn
on_miss: F, // optional JIT-on-the-fly hook
}
ガス計測 IR の出方:
; 概念図 — 各 Opcode が明示的 IR を出力
%gas_remaining = load i64, ptr %gas_remaining_addr
%new_gas = sub i64 %gas_remaining, 3 ; ADD のコスト
%underflow = icmp slt i64 %new_gas, 0
br i1 %underflow, label %OutOfGas, label %Continue
オプティマイザは:
- %gas_remaining への書きを 観測可能 とみなす → デッドストア除去不可
OutOfGas分岐は 実装の責任 → 投機実行で消せないgas_metering: bool設定フラグでオフ可能(速いがコンセンサス外、オフチェーン再実行向け)
3 困難の解決:
| 困難 | 解決 |
|---|---|
| ガス会計決定論性 | 明示的 IR(load → sub → compare → branch)、観測可能で最適化で消せない |
| EVM スタック非ネイティブ | コンパイル済み関数にヒープ確保スタック/メモリを渡す、U256 算術は load-load-op-store |
| 副作用 | revmc-builtins へのランタイム呼び出し、LLVM は不透明とみなし並べ替えない |
失敗例(誤解)
「revmc は revm の置き換え」— 間違い。revmc は revm の上の層、インタープリターは依然として仕様、コンパイル失敗 / 非対応 Opcode / デバッグビルドは revm インタープリターフォールバック = 「コンパイル経路が間違っている」失敗モードはない、最悪「コンパイル経路が取られない」だけ。
「ガス計測は LLVM に任せれば最適化される」— 致命的。LLVM の通常最適化(デッドストア除去 / 定数畳み込み / ループ不変コード移動)が コンセンサスを壊す。ガス計測を明示 IR にして観測可能化、オプティマイザに推論させない。
「コンパイル済み EVM = コンパイル済み C と同速」— 間違い。EVM はスタックマシン + 256-bit ワード、U256 はネイティブレジスタに収まらない、算術が load-load-op-store のシーケンスに下がる、勝ちは「ディスパッチループ消失 + Opcode 越し最適化」、ネイティブコード並みではない。
ステップで組み立てる
Step 1: revmc は revm の上の層
インタープリターは仕様、コンパイル失敗時フォールバック。
Step 2: JIT vs AOT のタイミング
JIT = 実行時 + code hash キャッシュ、AOT = 事前にバイナリ同梱。
Step 3: 3 困難を理解
ガス会計決定論 + EVM スタック非ネイティブ + 副作用ホスト呼び出し。
Step 4: ガス計測明示 IR の規律
load → sub → compare → branch、観測可能で最適化で消せない、gas_metering: bool でオフ可。
Step 5: 副作用 = revmc-builtins ランタイム呼び出し
不透明な副作用、LLVM 並べ替え不可、ホスト環境(Database 実装)にルーティング。
Step 6: JitEvm 統合点 + AOT vs JIT 選択
frame_run 上書き + code hash マップ + on_miss フック、AOT = 既知ホット / JIT = 負荷依存。
答え合わせ
- revmc がガス計測を意図的に明示 IR にする理由: LLVM 通常最適化がコンセンサスを壊しうる(デッドストア除去 → MSTORE のメモリ拡張ガスコスト消失、定数畳み込み → ガス課金回数不一致、ループ不変コード移動 → GAS Opcode の観測値変化)。明示 IR の
load → sub → compare → branchで 書きを観測可能化、オプティマイザは観測不能と証明できない限り消せない、ほぼ常に観測可能。 - revmc と revm の統合境界:
crates/revmc-contextのJitEvmがEvmTrベース EVM をラップしframe_runを上書き = フレームが始まると code hash 引く → 事前コンパイル済みマップにあればコンパイル済み関数に直接ディスパッチ、なければon_missフック(JIT-on-the-fly)or revm インタープリターフォールバック。インタープリターはセーフティネット、コンパイル失敗時に静かに流れる、「コンパイル経路間違い」失敗モードはない。 - コンパイル済み EVM が C 並みに速くない 2 理由: ① U256 はネイティブレジスタに収まらない = 算術 Opcode が load-load-op-store のシーケンスに下がる、ネイティブ
u64 a + bは 1 命令、コンパイル済み EVM U256 同等は数命令、② EVM スタック/メモリは別領域 = コンパイル済み関数にヒープ確保のスタック/メモリを渡す、EVM スタックをネイティブレジスタにマップしない。勝ちはインタープリターループ消失 + オプティマイザが Opcode 越しに見える、ネイティブコード並みではない。
合格基準
- revmc が revm の置き換えではない(上の層 + フォールバック)ことを即答できる。
- JIT vs AOT のタイミング差を即答できる。
- 3 困難(ガス会計 / EVM スタック / 副作用)と各解決を即答できる。
- ガス計測明示 IR の根拠(最適化で消せない観測可能性)を 1 文で説明できる。
JitEvm統合点(frame_run上書き + code hash マップ + フォールバック)を即答できる。- AOT vs JIT 選択基準(ホットセット既知 vs 負荷依存)を 1 文で説明できる。
まとめ(3行)
- revmc = revm の上の層(インタープリターは仕様 + フォールバック)、JIT(実行時 + キャッシュ)+ AOT(事前バイナリ同梱)、目的地はネイティブ機械語。
- 3 困難の解決 = ガス計測明示 IR(最適化で消せない観測可能)+ ヒープ確保 EVM スタック(U256 はレジスタに収まらない)+ revmc-builtins 不透明ランタイム呼び出し(LLVM 並べ替え不可)。
JitEvmがframe_run上書き + code hash マップ、AOT = 既知ホット(システム / WETH / DEX)+ JIT = 負荷依存(汎用 RPC)+ 両方使う本番高スループット L1(MegaETH 等)、次はファイナルクイズ。