世界を動かす技術を、日本語で。

WATaBoy: JITコンパイルされたゲームボーイの指示をWASMに変換し、ネイティブインタープリタを凌駕する

2026年6月30日原文(humphri.es)

概要

  • iOSではJITコンパイルが制限 されており、DolphinエミュレータはApp Storeで利用不可
  • WebAssembly(Wasm)とJavaScriptCore のJIT例外を活用した新しいアプローチ
  • RustによるWasmバイトコード生成・リンク手法 の実装解説
  • WATaBoy(Game Boyエミュレータ)でのJIT-to-Wasmの実装事例
  • JIT-to-Wasmとインタプリタのパフォーマンス比較 を目的としたプロジェクト

iOSでJITが許可されない理由とWebAssemblyの活用

  • iOSではJITコンパイルが原則禁止 されているため、DolphinのようなCPU依存エミュレータはApp Storeでリリース不可
  • AppleはWebブラウザ(JavaScriptCore, WebKit)にのみJIT例外 を設けている現状
  • WebAssemblyやJavaScriptはJITで高速化 されるため、Wasmバイトコードを生成しブラウザ側でJITさせる手法を検討
  • 既存の類似プロジェクト(The Jiterpreter, v86) は存在するが、ゲームコンソールエミュレータでの応用例やパフォーマンス比較は未開拓
  • Game Boyエミュレータ(WATaBoy)を題材にJIT-to-Wasmを実装 し、インタプリタとの速度比較を最終目標に設定

Game BoyエミュレータでのJIT-to-Wasmの意義

  • Game BoyレベルのエミュレータでJIT導入の恩恵は限定的 だが、実装が容易で卒業研究の範囲に収まる利点
  • JIT導入時の課題 として、割り込み予測やMMIOアクセス時のインタプリタへのフォールバックが必要
  • GameRoyのJIT実装を参考 に最適化技術を応用(x86向けだが手法はWasmにも有効)

RustによるWasmバイトコード生成・リンク手法

  • 低レイヤWasm操作にはwasm-encoderクレートを使用
    • wasm-bindgenやwasm-packは低レベル用途に不向き なため、C ABI経由でRustとJS間のデータ受け渡しを実施
    • Nightly Rustの利用が必須 (inline Wasmのため)
  • add関数のWasmバイトコード生成例
    • wasm_encoderで型セクション・関数セクション・エクスポートセクション・コードセクションを順に構築
    • make_add_module関数でバイトコードを出力

Wasmバイトコードの実行方法

  • WasmはHarvardアーキテクチャを採用 し、生成バイトコードを直接実行不可
  • JavaScript側でWasmバイトコードをコンパイル・インスタンス化・リンク する必要
    • linkNewModule関数をJavaScriptで実装 し、Rustから呼び出す
  • Rust側でcall_indirect命令をinline Wasmで発行
    • 関数テーブルの指定インデックスを間接呼び出しし、JIT生成関数を実行

LLDへのビルドフラグ指定

  • --export-tableで関数テーブルをエクスポート
  • --growable-tableで関数テーブルの動的拡張を許可
  • build.rsでフラグを指定し、cargo build --release --target wasm32-unknown-unknownでビルド

JavaScript側の実装例

  • Wasmインスタンスの生成と関数テーブル操作
    • linkNewModuleで新規Wasmバイトコードをコンパイル→インスタンス化→関数テーブルに追加
    • 追加した関数のインデックスをRust側に返却
  • make_and_execute_add関数を呼び出し、動的生成したadd関数を実行
    • 実行結果として正しい加算結果(例:2+3=5)が得られる

WATaBoyでのJIT-to-Wasm応用

  • Game Boy命令セットをWasmにJIT再コンパイル
    • 分岐のない命令列(ベーシックブロック)ごとにWasmモジュールを生成・キャッシュ
  • 関数シグネチャや命令列を動的に拡張可能

パフォーマンス比較と今後

  • JIT-to-Wasm、Wasmインタプリタ、ネイティブインタプリタの速度比較が主目的
  • WATaBoyのソースコードや詳細な命令再コンパイル例も公開
  • 今後の応用範囲 として、より高性能なゲーム機エミュレータやiOS向けJIT活用の可能性

この内容は iOS/JIT制限の回避策Rust/Wasmの低レベル連携 の実装知見、 エミュレータ開発の新しい選択肢 として有用です。

Hackerたちの意見

とても興味深い記事だね。iOSでネイティブインタープリターとWASM上のJITの比較も見てみたかったな。

これは学部生にとってすごいプロジェクトだね。印象的だよ。FirefoxがChromeやSafariより25%遅いってのも面白い。なんでだろう?

もちろん、ネイティブインタープリターには勝ってるよ。WASMのオーバーヘッドは約20%、インタープリターのオーバーヘッドは約1000%だし。ここで面白いのは、GameBoyのJITランタイムが存在するってことだね。

合計で2つのJITがあるよ。

じゃあ、JIT-in-JITってこと?JiJIT?

アンドリュー・ケリーの2013年のNESコードを静的に再コンパイルしようとする記事がずっと好きだったんだ。[1] 彼はすごい進展を遂げるけど、当時の手書きアセンブラが高レベルのLLVM IRにマッピングするのがあまり得意じゃない現実に引っかかってしまうんだよね。結論では、実行時データを使ってホットパスをライブ再コンパイルするJIT型の手法が多分良い方法だって言ってる。理解できない部分は気にしなくていいって。こういうのが実際に動いてるのを見るのはすごくクールだよ。[1]: https://andrewkelley.me/post/jamulator.html

DolphinはiOSにはないんだ。iOSではJITコンパイルができないからね....でも、AppleにはJIT制限の例外が一つあるんだ。それはウェブブラウザ。JavaScriptCore、WebKitのJSエンジンは、より高性能な層のためにJITコンパイルを使ってる。だから、JS関数が十分に呼ばれると、最終的には最適化されてネイティブマシンコードにコンパイルされるんだ。WebAssemblyも同じことが言えるね。見出しの理由について考えてたけど、これは本当に面白い答えだね。制限を回避する美しい方法だと思う。他のプロジェクトにもどれくらい適用できるのか気になるな。

「制限は置いといて、何年も前にFirefoxのJSエンジンで自分でホストしたArray.sortの実装を作ったんだけど、ネイティブのC++実装よりもパフォーマンスが良かったんだよね :] こういうのはよくあるテーマだよ。」 https://bugzilla.mozilla.org/show_bug.cgi?id=715181

作者の基本的な前提と、このプロジェクトをやる動機は間違ってるよ。https://github.com/StephenDev0/StikDebug 学部生のプロジェクトとしては、良い成績を取るためにこの解決策の存在を便利に忘れるのも仕方ないのかな。

「get-task-allowの権限を持つサイドロードアプリには間違ってるけど、普通のアプリには正しいよ。」

「普通の大学生がiOSの動的コードサイニングの仕組みを理解するとは思わないな。Appleで働いてる高学歴の人たちでも、実際のところどうなってるか分からない人が結構いるし。」

「これを使うには、指定されたプロセスでデバッグセッションを開始するために別のデバイスが必要だよ。開発者資格を使ってDolphinをサイドロードするのはいいけど、他のアプリには受け入れられない解決策だね。アプリをランダムな人にデバッグされるのを望まない人もいるだろうし、アプリを起動するのに別のiPhoneが必要なのは面倒でユーザーに優しくないよ。対照的に、WebViewを立ち上げるのはどこでもできるし、App Reviewも多分気にしないだろうし。」

Hacker Newsで議論の続きを見る