12 KiB
12 KiB
Phase 10: Cranelift JIT Backend(MIR→VM→Cranelift)
Status: Planned (Primary path for native speed) Last Updated: 2025-08-25
🎯 ゴール
- 実行系の主経路を「MIR→VM」を維持しつつ、ホットパスをCraneliftでJIT化して高速化する。
- LLVM AOTは後段(Phase 11以降)の研究対象へ繰り延べ。
🔗 位置づけ
- これまでの案(MIR→LLVM AOT)を改め、現実的な開発速度と安定性を優先してCranelift JITを先行。
- VMとのハイブリッド実行(OSR/ホットカウントに基づくJIT)を採用。
📐 アーキテクチャ
AST → MIR → Optimizer → VM Dispatcher
└─(Hot)→ Cranelift JIT (fn単位)
- VMが命令カウント・プロファイルを集計し、しきい値超過関数をJITコンパイル。
- JIT済み関数は関数テーブルから直接呼び出し、VMはフォールバック先として維持。
📋 スコープ
- 基盤
- JITマネージャ(関数プロファイル・コンパイルキャッシュ)
- Craneliftコード生成(MIR→CLIF Lower)
- 呼出しABI(Nyash VMスタック/レジスタとのブリッジ)
- 命令カバレッジ(段階導入)
- Phase A: Const/Copy/BinOp/Compare/Jump/Branch/Return(純関数相当)
- Phase B: Call/BoxCall/ArrayGet/ArraySet(ホットパス対応)
- Phase C: TypeOp/Ref*/Weak*/Barrier(必要最小)
- ランタイム連携
- Boxの所有・参照モデルを維持(共有/クローンの意味論を破らない)
- 例外・TypeErrorはVMの例外パスへエスケープ
✅ 受け入れ基準(Milestone)
- M1: 算術/比較/分岐/returnの関数がJIT化され、VMより高速に実行
- M2: Array/Mapの代表操作(get/set/push/size)がJITで安定動作
- M3: BoxCallホットパス(特にArray/Map)で有意な高速化(2×目標)
- M4: 回帰防止のベンチと
--vm-stats連携(JITカウント/時間)
🪜 実装ステップ
- JITマネージャ/関数プロファイルの導入(VM統計と統合)
- MIR→CLIF Lower骨子(基本型/算術/比較/制御)
- 呼出しABIブリッジ(引数/戻り値/BoxRefの表現)
- JIT関数テーブル + VMディスパッチ切替
- Array/Map/BoxCallのホットパス最適化
- TypeOp/Ref/Weak/Barrierの必要最小を実装
- ベンチ/スナップショット整備・回帰検出
⚠️ 依存・前提
- MIR26整合(TypeOp/WeakRef/Barrierの統合前提)
- P2PBox再設計(Phase 9.x)を先に安定化しておく(VM/プラグインE2E維持)
📚 参考
- Cranelift: Peepmatic/CLIF、simple_jitの最小例
- JIT/VMハイブリッド: LuaJIT/HotSpotのOSR設計
備考: LLVM AOTはPhase 11以降の研究路線に移行(設計ドキュメントは維持)。
🔬 Sub-Phases (10_a .. 10_h)
各サブフェーズは「小さく立ち上げ→検証→次へ」。既存のVM/Thunk/PICを活用し、JITは同じ経路に自然合流させる。
10_a: JITブートストラップ(基盤+プロファイラ)
- 目的: Cranelift JITの骨組みとホット関数検出の導線を用意。
- 具体:
JitManager(関数プロファイル、しきい値、キャッシュ)- CLIF環境初期化(
Module,Context,ISA) - VM統合: 関数入口でホット度チェック→JITコンパイル/呼び出し
- 診断:
NYASH_JIT_STATS=1(JIT件数/時間/キャッシュヒット)
- 受入: ダミー関数をJIT登録してVMから呼出可能、ログが出る
10_b: Lower(Core-1) – Const/Move/BinOp/Cmp/Branch/Ret
- 目的: ループや条件分岐を含む純粋関数をJIT実行可能に。
- 具体:
- MIR値/型→CLIF型(i64/f64/i1/void)
- Const/Copy/算術/比較/ジャンプ/分岐/return のLower
- フレームレイアウト(VMValue最小表現)
- 受入: 算術/比較/分岐のみの関数がJITでVMより速い(小ベンチ)
10_c: ABI/呼出し – 関数呼び出しとフォールバック
- 目的: JIT化関数から他関数を呼ぶ/VMへ戻る道を用意。
- 具体:
- 同一モジュール関数呼び出し(JIT→JIT/JIT→VM)
- 引数/戻り値の受け渡し(整数/浮動/void)
- 例外/TypeErrorはVMへバイアウト(trap→VM)
- 受入: 再帰/多段呼び出しが安定動作
10_d: コレクション基礎 – Array/Map ホット操作(外部呼び出し)
- 目的: 実用的ホットパス(length/get/set/push/pop)をJIT側から呼べるように。
- 具体:
- ホスト関数テーブル(外部シンボル)で既存Rust実装を呼ぶ
- 境界チェック/エラーはRust側に委譲、JITは薄い橋渡し
- 受入: Array操作がVM経由より高速(目安1.5–2.0×)
Status(2025-08-27)
- Param経路でのE2Eを実装(
NYASH_JIT_HOSTCALL=1ゲート) - 実装済みシンボル(PoC, C-ABI in Rust):
nyash.array.{len,push,get,set}/nyash.map.size
- 使い方(例):
cargo build --features cranelift-jit --release
NYASH_JIT_THRESHOLD=1 NYASH_JIT_HOSTCALL=1 NYASH_JIT_EXEC=1 \
./target/release/nyash --backend vm examples/jit_array_param_call.nyash
NYASH_JIT_THRESHOLD=1 NYASH_JIT_HOSTCALL=1 NYASH_JIT_EXEC=1 \
./target/release/nyash --backend vm examples/jit_map_param_call.nyash
Notes
- 関数パラメータに渡した配列/MapのみHostCall経由でアクセス(thread-local引数参照)
- ローカルnew値は10_eへ移管(ハンドル表PoC: u64→Arc)
10_e: BoxCall高速化 – Thunk/PICの直結
- 目的: Phase 9.79bの
TypeMeta{slot->thunk}と Poly-PIC をJITにインライン。 - 具体:
slot -> thunk -> target解決をJITで再現(ユニバーサル0..3含む)(type_id, version)チェック(Poly-PIC 2–4件)→ヒット直呼び、ミスVM- バージョンミスマッチで安全にフォールバック
- 受入: BoxCallホットサイトで2×以上の高速化+正しい無効化挙動
10_f: TypeOp/Ref/Weak/Barrier(最小)
- 目的: 実アプリで必要な最小限のタイプ/参照操作を埋める。
- 具体:
as_bool()等の基本型変換- 参照/弱参照/バリアの最小パス(重い経路はVMへ)
- 受入: 代表的コードパスでJIT有効のままE2E成功
10_g: 診断/ベンチ/回帰
- 目的: 可視化と安定化。
- 具体:
--vm-statsにJIT統計統合(compile/ms, sites, cache率)- ベンチ更新(JIT有/無比較)とスナップショット
- 受入: CIで回帰検知可能/ドキュメント更新
10_h: 硬化・パフォーマンス調整
- 目的: ホットスポットの最適化とノイズ除去。
- 具体:
- ガード配置最適化(分岐予測/ICヒット優先)
- 不要コピー削減、ホスト呼出回数の削減
- 受入: 代表ベンチで安定して目標達成(2×以上)
📦 成果物(各サブフェーズ)
- 10_a:
jit/manager.rsスケルトン、VM連携、統計ログ - 10_b:
jit/lower/core.rs(Const/BinOp/Cmp/Branch/Ret)+単体テスト - 10_c:
jit/abi.rs(call/ret/fallback)+再帰テスト - 10_d:
jit/extern/collections.rs(Array/Mapブリッジ)+マイクロベンチ - 10_e:
jit/inline_cache.rs(PIC/VT連携)+無効化テスト - 10_f:
jit/lower/typeop_ref.rs(最小) - 10_g: ベンチ/統計/README更新
- 10_h: 最適化コミットと測定レポート
🧩 既存資産との連携(重要)
- Thunk: Phase 9.79b.3の
TypeMeta{thunks}をJIT直呼びターゲットとして使用 - Poly-PIC: VMの構造をJITに投影(同じキー
(label, version)を使用) - Versioning:
cache_versionsのイベントに同期してIC無効化
🎯 マイルストーン再定義
- M1: 10_a + 10_b 合格(Core関数のJIT実行)
- M2: 10_c + 10_d 合格(関数呼出/Arrayホット操作)
- M3: 10_e 合格(BoxCallホットパス2×)
- M4: 10_g + 10_h 合格(ベンチ/統計/硬化)
⚠️ リスクと緩和
- ABI複雑化: まず整数/浮動/voidに限定し、BoxRefはホスト関数へブリッジ
- 最適化過剰: 常にVMフォールバックを保持、ガード失敗で安全に戻す
- デバッグ困難: CLIFダンプ/CFG表示/
NYASH_JIT_STATSで観測
🐛 発見した問題点(2025-08-27 ベンチマーク実行時)
1. インタープリター性能問題
- 問題: 10万回のループで2分以上かかりタイムアウト
- 原因:
unwrap_instanceのデバッグログが大量出力(毎回の演算でInstanceBoxチェック) - 目標: 10万回ループを数秒で完了
- 対策:
- デバッグログの条件付き出力化
- 基本演算の高速化(IntegerBoxの直接操作最適化)
2. VMの変数管理エラー
- 問題:
Invalid value: Value %47 not set- simple_add_loop内の変数zが正しく管理されていない - 原因: MIR生成時の変数スコープ管理の不具合
- 対策: MirBuilderの変数トラッキング改善
3. Box APIの成熟度
- 問題: TimeBoxにelapsed()/reset()メソッドがインタープリターから呼べない
- 原因: Boxメソッドの登録システム未完成
- 対策:
- Boxメソッドの統一的登録システム実装
- インタープリターとVMのメソッド解決統一
4. ベンチマーク基盤
- 問題: Nyashスクリプトでの正確な時間計測が困難
- 根本原因: TimeBoxのelapsed()/reset()メソッドがインタープリターから呼べない(Box API問題と同じ)
- 対策: Box APIの成熟度問題(問題3)が解決すれば自動的に解決
改善優先度
- 高: インタープリター性能問題(基本機能の使い物にならない)
- 中: VM変数管理(JIT最適化の前提)
- 中: Box APIの成熟度(ベンチマーク基盤も同時解決)
🚀 Phase 10.9: Cranelift AOT Backend(追加計画)
Status: Future (JIT実装の上乗せで実現可能)
概要
JIT実装(10_a~10_h)で構築したMIR→CLIF変換基盤をそのまま活用し、事前コンパイル(AOT)によるスタンドアロン実行ファイル生成を実現する。
利点
- コード再利用: JITと同じLowerCore実装を使用(追加実装最小)
- 非同期完全サポート: WASMの制限なし、ネイティブ非同期可能
- 最高性能: ネイティブコード直接実行(起動時コンパイル不要)
- デバッグ容易: gdb/lldb等のネイティブデバッガ使用可能
実装ステップ案
10.9a: ObjectModule統合
├── cranelift-objectモジュール導入
├── CLIF→オブジェクトファイル生成
└── 既存のLowerCore再利用
10.9b: ランタイムライブラリ
├── Nyash標準Box群の静的リンク版作成
├── プラグインの静的埋め込み対応
└── 最小ランタイム(GC hooks等)分離
10.9c: リンカー統合
├── cc/ldによる実行ファイル生成
├── プラットフォーム別設定
└── デバッグシンボル生成
10.9d: クロスコンパイル
├── 複数ターゲット(x86_64/aarch64/wasm32)
├── ターゲット別最適化
└── CI/CDでのマルチプラットフォームビルド
使用イメージ
# ネイティブ実行ファイル生成
./target/release/nyash --compile-native program.nyash -o program
./program # スタンドアロン実行!
# クロスコンパイル
./target/release/nyash --compile-native --target x86_64-pc-windows-msvc program.nyash -o program.exe
./target/release/nyash --compile-native --target aarch64-apple-darwin program.nyash -o program.mac
技術的詳細
- 共通基盤:
LowerCoreのemit処理はJIT/AOT両対応 - 差分実装: JITは
JITModule、AOTはObjectModuleを使用 - リンク方式: 静的リンク優先(配布容易性重視)
- サイズ最適化: LTO/strip対応で実行ファイルサイズ削減
WASM AOTとの比較
| 特性 | Cranelift AOT | WASM AOT |
|---|---|---|
| 非同期 | ✅ 完全対応 | ❌ 制限あり |
| 実行速度 | 最速 | 速い |
| ファイルサイズ | 大(MB級) | 小(KB級) |
| ポータビリティ | プラットフォーム別 | 高い |
| デバッグ | ネイティブツール | 限定的 |
想定スケジュール
- Phase 10(JIT)安定化後に着手
- 実装期間: 2-3週間(JIT基盤の再利用により短期実現可能)
- 初期ターゲット: Linux x86_64、その後Windows/macOS対応