Phase 10.7 - JIT統計とイベント機能の完成
主要な実装: - PHI(b1)統計追跡: phi_total_slots/phi_b1_slotsをJSON出力 - 関数単位統計API: JitStatsBox.perFunction()で詳細統計取得 - JITイベントシステム: compile/execute/fallback/trapをJSONL形式で記録 - Store/Load命令対応: ローカル変数を含む関数のJIT実行が可能に 新しいBox: - JitStatsBox: JIT統計の取得 - JitConfigBox: JIT設定の管理(将来用) - JitEventsBox: イベントのJSONL出力(将来用) - JitPolicyBox: 実行ポリシー管理(将来用) CLI拡張: - --jit-exec, --jit-stats, --jit-dump等のフラグ追加 - --jit-directモードでの独立JIT実行 - NYASH_JIT_*環境変数によるきめ細かい制御 ドキュメント: - Phase 10.7実装計画の詳細化 - Phase 10.9 (ビルトインBox JIT) の計画追加 - JIT統計JSONスキーマ v1の仕様化 ChatGPT5との共同開発により、JIT基盤が大幅に強化されました。 次はPhase 10.9でビルトインBoxのJIT対応を進め、 Python統合(Phase 10.1)への道を開きます。 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
11
.gitignore
vendored
11
.gitignore
vendored
@ -124,3 +124,14 @@ local_tests/
|
|||||||
|
|
||||||
# Backup files
|
# Backup files
|
||||||
/backups/
|
/backups/
|
||||||
|
|
||||||
|
# 📚 論文関連フォルダ(作業中と公開用を分離)
|
||||||
|
# 作業中(WIP: Work In Progress)- Git追跡除外
|
||||||
|
docs/research/papers-wip/
|
||||||
|
docs/research/drafts/
|
||||||
|
docs/research/notes/
|
||||||
|
|
||||||
|
# 査読中・未公開論文 - Git追跡除外
|
||||||
|
docs/research/papers-under-review/
|
||||||
|
|
||||||
|
# 完成・公開済み論文は docs/research/papers-published/ に配置(Git追跡対象)
|
||||||
55
CLAUDE.md
55
CLAUDE.md
@ -6,6 +6,36 @@
|
|||||||
- 現在のタスク: docs/development/current/CURRENT_TASK.md
|
- 現在のタスク: docs/development/current/CURRENT_TASK.md
|
||||||
- ドキュメントハブ: README.md
|
- ドキュメントハブ: README.md
|
||||||
- 🚀 **開発マスタープラン**: docs/development/roadmap/phases/00_MASTER_ROADMAP.md
|
- 🚀 **開発マスタープラン**: docs/development/roadmap/phases/00_MASTER_ROADMAP.md
|
||||||
|
- 📊 **JIT統計JSONスキーマ(v1)**: docs/reference/jit/jit_stats_json_v1.md
|
||||||
|
|
||||||
|
## 🧱 先頭原則: 「箱理論(Box-First)」で足場を積む
|
||||||
|
Nyashは「Everything is Box」。実装・最適化・検証のすべてを「箱」で分離・固定し、いつでも戻せる足場を積み木のように重ねる。
|
||||||
|
|
||||||
|
- 基本姿勢: 「まず箱に切り出す」→「境界をはっきりさせる」→「差し替え可能にする」
|
||||||
|
- 環境依存や一時的なフラグは、可能な限り「箱経由」に集約(例: JitConfigBox)
|
||||||
|
- VM/JIT/GC/スケジューラは箱化されたAPI越しに連携(直参照・直結合を避ける)
|
||||||
|
- いつでも戻せる: 機能フラグ・スコープ限定・デフォルトオフを活用し、破壊的変更を避ける
|
||||||
|
- 「限定スコープの足場」を先に立ててから最適化(戻りやすい積み木)
|
||||||
|
- AI補助時の注意: 「力づく最適化」を抑え、まず箱で境界を確立→小さく通す→可視化→次の一手
|
||||||
|
|
||||||
|
進め方(積み木が大きくなりすぎないために)
|
||||||
|
- 1) 足場は最小限(設定/境界/可視化/フォールバック)→ 2) すぐ実効カバレッジ拡大へ
|
||||||
|
- 返り値ヒントなどは「フックだけ」用意し、切替ポイントを1箇所に固定(将来1行切替)
|
||||||
|
- 同じ分岐を複数箇所に作らない(ParamKind→型決定は1関数に集約)
|
||||||
|
- 観測(統計/CFG)で回れるようにして「止まらない実装サイクル」を維持
|
||||||
|
|
||||||
|
|
||||||
|
実践テンプレート(開発時の合言葉)
|
||||||
|
- 「箱にする」: 設定・状態・橋渡しはBox化(例: JitConfigBox, HandleRegistry)
|
||||||
|
- 「境界を作る」: 変換は境界1箇所で(VMValue↔JitValue, Handle↔Arc)
|
||||||
|
- 「戻せる」: フラグ・feature・env/Boxで切替。panic→フォールバック経路を常設
|
||||||
|
- 「見える化」: ダンプ/JSON/DOTで可視化、回帰テストを最小構成で先に入れる
|
||||||
|
|
||||||
|
標準化(例)
|
||||||
|
- 設定の箱: JIT/VMフラグは `JitConfigBox` に集約し、`apply()`でenvにも反映
|
||||||
|
- 実行時読み: ホットパスでは `jit::config::current()` を参照(env直読みを排除)
|
||||||
|
- ハンドルの箱: JIT↔Hostは `HandleRegistry`(u64↔Arc)で疎結合化
|
||||||
|
- 可視化: `NYASH_JIT_DUMP/…_JSON/NYASH_JIT_DOT` を活用し、変化を常に観測
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- ここから先の導線は README.md に集約。Claude Codeくんがこのファイルを上書きしても最低限のリンクは保たれるよ。
|
- ここから先の導線は README.md に集約。Claude Codeくんがこのファイルを上書きしても最低限のリンクは保たれるよ。
|
||||||
@ -91,6 +121,15 @@ python3 -m http.server 8010
|
|||||||
|
|
||||||
**注意**: WASMビルドでは一部のBox(TimerBox、AudioBox等)は除外されます。
|
**注意**: WASMビルドでは一部のBox(TimerBox、AudioBox等)は除外されます。
|
||||||
|
|
||||||
|
### 🔧 JIT-direct(独立JIT)運用メモ(最小)
|
||||||
|
- 方針: 当面は read-only(書き込み命令はjit-directで拒否)
|
||||||
|
- 失敗の見える化: `NYASH_JIT_STATS_JSON=1` または `NYASH_JIT_ERROR_JSON=1` でエラーを1行JSON出力
|
||||||
|
- すぐ試す例(Cranelift有効時)
|
||||||
|
- `examples/jit_direct_local_store_load.nyash`(最小Store/Load → 3)
|
||||||
|
- `examples/jit_direct_bool_ret.nyash`(Bool戻り → true)
|
||||||
|
- `examples/jit_direct_f64_ret.nyash`(F64戻り → 3.75, `NYASH_JIT_NATIVE_F64=1`)
|
||||||
|
- Box-First運用キット: `docs/engineering/box_first_enforcement.md`(PRテンプレ/CIガード/運用指標)
|
||||||
|
|
||||||
## 📚 ドキュメント構造
|
## 📚 ドキュメント構造
|
||||||
|
|
||||||
### 🎯 **最重要ドキュメント(開発者向け)**
|
### 🎯 **最重要ドキュメント(開発者向け)**
|
||||||
@ -578,6 +617,22 @@ echo 'print("Hello Nyash!")' > local_tests/test_hello.nyash
|
|||||||
|
|
||||||
# 実用アプリテスト
|
# 実用アプリテスト
|
||||||
./target/debug/nyash app_dice_rpg.nyash
|
./target/debug/nyash app_dice_rpg.nyash
|
||||||
|
|
||||||
|
# JIT 実行フラグ(CLI)
|
||||||
|
./target/release/nyash --backend vm \
|
||||||
|
--jit-exec --jit-stats --jit-dump --jit-threshold 1 \
|
||||||
|
--jit-phi-min --jit-hostcall --jit-handle-debug \
|
||||||
|
examples/jit_branch_demo.nyash
|
||||||
|
# 既存の環境変数でも可:
|
||||||
|
# NYASH_JIT_EXEC/NYASH_JIT_STATS(/_JSON)/NYASH_JIT_DUMP/NYASH_JIT_THRESHOLD
|
||||||
|
# NYASH_JIT_PHI_MIN/NYASH_JIT_HOSTCALL/NYASH_JIT_HANDLE_DEBUG
|
||||||
|
|
||||||
|
# HostCallハンドルPoCの例
|
||||||
|
./target/release/nyash --backend vm --jit-exec --jit-hostcall examples/jit_array_param_call.nyash
|
||||||
|
./target/release/nyash --backend vm --jit-exec --jit-hostcall examples/jit_map_param_call.nyash
|
||||||
|
./target/release/nyash --backend vm --jit-exec --jit-hostcall examples/jit_map_int_keys_param_call.nyash
|
||||||
|
./target/release/nyash --backend vm --jit-exec --jit-hostcall examples/jit_string_param_length.nyash
|
||||||
|
./target/release/nyash --backend vm --jit-exec --jit-hostcall examples/jit_string_is_empty.nyash
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 🔌 **プラグインテスター(BID-FFI診断ツール)**
|
#### 🔌 **プラグインテスター(BID-FFI診断ツール)**
|
||||||
|
|||||||
@ -2,7 +2,14 @@
|
|||||||
|
|
||||||
フェーズ10はJIT実用化へ!Core-1 Lowerの雛形を固めつつ、呼出/フォールバック導線を整えるよ。
|
フェーズ10はJIT実用化へ!Core-1 Lowerの雛形を固めつつ、呼出/フォールバック導線を整えるよ。
|
||||||
|
|
||||||
## ⏱️ 今日のサマリ(10_c実行経路の堅牢化+10_7分岐配線+GC/スケジューラ導線)
|
## 🆕 Quick Hot Update(2025-08-27-2)
|
||||||
|
- JitStatsBox: perFunction() 追加(関数単位の統計をJSON配列で取得)
|
||||||
|
- b1 PHI格子 強化(Compare/Const/Cast(TypeOp)/Branch/Copy/PHI/Store/Loadの固定点伝播)
|
||||||
|
- 独立JIT: `--jit-direct` に引数対応(`NYASH_JIT_ARGS` をMIR型にcoerce)、結果の型名も表示
|
||||||
|
- b1 ABI切替の下地: `NYASH_JIT_ABI_B1_SUPPORT=1` でcapを強制ONできるように(将来のtoolchain更新に備えたスイッチ)
|
||||||
|
- リンク: JSONスキーマ(v1) → `docs/reference/jit/jit_stats_json_v1.md`
|
||||||
|
|
||||||
|
## ⏱️ 今日のサマリ(10_c実行経路の堅牢化+10_7分岐/PHI/独立ABI+GC/スケジューラ導線)
|
||||||
- 目的: JIT実行を安全に通す足場を仕上げつつ、GC/スケジューラ導線を整備し回帰検出力を上げる。
|
- 目的: JIT実行を安全に通す足場を仕上げつつ、GC/スケジューラ導線を整備し回帰検出力を上げる。
|
||||||
- 10_c: panic→VMフォールバック(`catch_unwind`)/ JIT経路のroot区域化 / Core-1 i64 param minimal pass(`emit_param_i64` + LowerCoreで供給)✅ 完了
|
- 10_c: panic→VMフォールバック(`catch_unwind`)/ JIT経路のroot区域化 / Core-1 i64 param minimal pass(`emit_param_i64` + LowerCoreで供給)✅ 完了
|
||||||
- 10_4a/10_4b: GC導線 + Write-Barrier挿入(ArraySet/RefSet/BoxCall)、root API(enter/pin/leave)
|
- 10_4a/10_4b: GC導線 + Write-Barrier挿入(ArraySet/RefSet/BoxCall)、root API(enter/pin/leave)
|
||||||
@ -10,8 +17,81 @@
|
|||||||
- 10_4d: STRICTバリア検証(CountingGc前後比較で漏れ即検出)✅ 完了(`NYASH_GC_BARRIER_STRICT=1`)
|
- 10_4d: STRICTバリア検証(CountingGc前後比較で漏れ即検出)✅ 完了(`NYASH_GC_BARRIER_STRICT=1`)
|
||||||
- 10_6b: シングルスレ・スケジューラ(spawn/spawn_after/poll)、Safepointで`poll()`連携、`NYASH_SCHED_POLL_BUDGET`対応 ✅ 完了
|
- 10_6b: シングルスレ・スケジューラ(spawn/spawn_after/poll)、Safepointで`poll()`連携、`NYASH_SCHED_POLL_BUDGET`対応 ✅ 完了
|
||||||
- 10_7: JIT分岐配線(Cranelift)— MIR Branch/Jump→CLIFブロック配線、条件b1保持、分岐b1/`i64!=0`両対応(feature: `cranelift-jit`)
|
- 10_7: JIT分岐配線(Cranelift)— MIR Branch/Jump→CLIFブロック配線、条件b1保持、分岐b1/`i64!=0`両対応(feature: `cranelift-jit`)
|
||||||
|
- 10_7: 最小PHI(単純ダイアモンド)— `NYASH_JIT_PHI_MIN=1` で有効、ブロック引数で合流値を受け渡し ✅ 初期対応
|
||||||
|
- 10_7: JIT独立ABI導入 — `JitValue`(i64/f64/bool/handle) + VMValue↔JitValueアダプタ、TLS分離(legacy VMArgs / JIT Args)✅ 完了
|
||||||
- ベンチ: CLIベンチへJIT比較追加(ウォームアップあり)、`branch_return`ケース追加、スクリプト版`examples/ny_bench.nyash`追加(TimerBoxでops/sec)
|
- ベンチ: CLIベンチへJIT比較追加(ウォームアップあり)、`branch_return`ケース追加、スクリプト版`examples/ny_bench.nyash`追加(TimerBoxでops/sec)
|
||||||
|
|
||||||
|
### 🆕 追加アップデート(10_7進捗)
|
||||||
|
- ハンドルレジストリ + スコープ管理(JIT呼び出し単位で自動クリーンアップ)✅
|
||||||
|
- HostCallブリッジ(ハンドル経由・read-only中心)✅
|
||||||
|
- Array/Map/String: `length`/`isEmpty`、Map: `has`、String: `charCodeAt`
|
||||||
|
- Array/Map get/set/push/size: 既存PoCをハンドル経由に段階移行
|
||||||
|
- 多値PHIの配線(then/else/jumpで順序つき引数渡し)✅
|
||||||
|
- DOT可視化(`NYASH_JIT_DOT=path.dot` でCFG出力)✅ 初期対応
|
||||||
|
- 引数ネイティブ型(10_7hの前段): MIRシグネチャから `Float→F64`, `Bool→I64(0/1)` を反映(`NYASH_JIT_NATIVE_F64/BOOL`)✅ 初期対応
|
||||||
|
- ダンプの簡潔化(CFG/PHI要点表示、f64/boolネイティブ有効フラグ表示)✅
|
||||||
|
- CFG/PHIダンプの詳細化(`NYASH_JIT_DUMP=1`)✅ 初期強化
|
||||||
|
- 例: `phi: bb=3 slots=2 preds=1|2` に続けて `dst vX <- pred:val, ...` を列挙(合流値の源が一目で分かる)
|
||||||
|
- JIT設定の集約(Rust側)✅ 初期導入
|
||||||
|
- `src/jit/config.rs` に `JitConfig` を追加し、RunnerからCLIと環境変数を一元反映
|
||||||
|
- JitConfigBox 追加(10_7f)✅ 初期対応
|
||||||
|
- `new JitConfigBox()` で `exec/stats/dump/phi_min/hostcall/.../threshold` を操作し `apply()` でenv反映
|
||||||
|
- `toJson()/fromJson()` で再現性確保、`summary()` で状態確認
|
||||||
|
- f64ネイティブ最小経路(`NYASH_JIT_NATIVE_F64=1`)✅
|
||||||
|
- f64定数、F64同士の四則、F64戻りシグネチャ・トランポリン
|
||||||
|
- トランポリンは`desired_ret_is_f64`を捕捉(env依存のズレを排除)✅
|
||||||
|
- Boolネイティブ最小経路(`NYASH_JIT_NATIVE_BOOL=1`)↗ 準備済み
|
||||||
|
- 比較はb1で保持し分岐に直接供給(既定)/戻りは当面i64(0/1)で正規化(将来b1戻りに拡張)
|
||||||
|
- 細かな修正: P2PBoxでtransportコールバック登録時の借用期間エラーを修正(lock解放順を明示)
|
||||||
|
- Bugfix: Cranelift `push_block_param_i64_at` で `switch_to_block` 未呼出によるpanicを修正(CFG/PHI経路の安定化)✅
|
||||||
|
- LowerCore小型足場追加 ✅
|
||||||
|
- ret_boolヒント: 返り値がBoolの関数で `builder.hint_ret_bool(true)` を呼ぶ切替点を用意(現状no-op、将来1行で切替)
|
||||||
|
- Castの最小下ろし: `I::Cast` を値供給+既知整数の継承で安全通過(副作用なし領域)
|
||||||
|
- 観測強化 ✅
|
||||||
|
- b1正規化カウンタ: `b1_norm_count`(分岐条件・b1ブロック引数の正規化で加算)
|
||||||
|
- JitStatsBox: `{abi_mode, abi_b1_enabled, abi_b1_supported, b1_norm_count}` を `toJson()` で取得可能(BoxFactory登録済み)
|
||||||
|
- 統合JIT統計(テキスト/JSON)に `abi_mode/…/b1_norm_count` を追加
|
||||||
|
- 便利CLI ✅
|
||||||
|
- `--emit-cfg <DOT_FILE>` 追加(`NYASH_JIT_DOT` のCLI版。`NYASH_JIT_DUMP`も自動オン)
|
||||||
|
- 重要: Cranelift未有効時の挙動を是正 ✅
|
||||||
|
- 以前: `NYASH_JIT_EXEC=1` でJITスタブが0を返すことがあり、結果が変わり得た
|
||||||
|
- 変更: Cranelift未有効では「未コンパイル扱い」にしてVMへ完全フォールバック(JIT有効でも結果は不変)
|
||||||
|
|
||||||
|
- 独立JITモード ✅ 追加
|
||||||
|
- `--jit-direct`: VM実行ループを介さず、JITエンジンで `main` を直接コンパイル実行(Cranelift有効時)
|
||||||
|
- `--jit-only`: フォールバック禁止(JIT未コンパイル/失敗は即エラー)— 独立性検証に最適
|
||||||
|
|
||||||
|
- b1 PHIの型/格子解析 強化 ✅
|
||||||
|
- Compare/Const Bool起点+Copy/PHIに加え、Load/Storeヒューリスティックを導入(固定点伝播)
|
||||||
|
- PHI統計は常時計測(`phi_total_slots`/`phi_b1_slots`)→ JSON/ダンプ一致
|
||||||
|
|
||||||
|
- 関数単位JIT統計 ✅
|
||||||
|
- LowerCore→JitEngine→JitManagerで `phi_total/phi_b1/ret_bool_hint/hits/compiled/handle` を集約
|
||||||
|
- `JitStatsBox.summary()` に `perFunction` 配列を追加(関数ごとの明細)
|
||||||
|
|
||||||
|
- f64 E2Eと簡易ベンチ(Cranelift向け)✅ 追加
|
||||||
|
- 例: `examples/jit_f64_e2e_add_compare.nyash` / ベンチ: `examples/ny_bench_f64.nyash`
|
||||||
|
- 注意: VM単体ではf64演算/比較は未対応。`--features cranelift-jit` + `NYASH_JIT_EXEC=1 NYASH_JIT_NATIVE_F64=1` で実行
|
||||||
|
|
||||||
|
- JSONスキーマの文書化 ✅
|
||||||
|
- 参照: `docs/reference/jit/jit_stats_json_v1.md`(version=1)
|
||||||
|
- 統一JSON/Box JSONともに `version` フィールドを付与
|
||||||
|
|
||||||
|
- f64 E2Eと簡易ベンチ(Cranelift向け)✅ 追加
|
||||||
|
- 例: `examples/jit_f64_e2e_add_compare.nyash`
|
||||||
|
- ベンチ: `examples/ny_bench_f64.nyash`
|
||||||
|
- 注意: VM単体ではf64演算/比較は未対応。`--features cranelift-jit` + `NYASH_JIT_EXEC=1 NYASH_JIT_NATIVE_F64=1` で実行
|
||||||
|
|
||||||
|
- JSONスキーマの文書化 ✅ 追加
|
||||||
|
- 参照: `docs/reference/jit/jit_stats_json_v1.md`(version=1)
|
||||||
|
- 統一JSON/Box JSONともに `version` フィールドを付与
|
||||||
|
|
||||||
|
- PHI統計(常時計測)✅
|
||||||
|
- `phi_total_slots`/`phi_b1_slots` をLowerCoreで常時計測し、JSONに出力
|
||||||
|
|
||||||
|
- Cranelift互換性修正 ✅
|
||||||
|
- b1シグネチャを使用せず、戻りはi64に正規化(`select(b1,1,0)`)。`types::B1`/`bint` 非依存でビルド安定化
|
||||||
|
|
||||||
### 直近タスク(小さく早く)
|
### 直近タスク(小さく早く)
|
||||||
1) 10_b: Lower/Core-1 最小化(進行中 → ほぼ完了)
|
1) 10_b: Lower/Core-1 最小化(進行中 → ほぼ完了)
|
||||||
- IRBuilder抽象 + `NoopBuilder`(emit数カウント)✅ 完了
|
- IRBuilder抽象 + `NoopBuilder`(emit数カウント)✅ 完了
|
||||||
@ -20,25 +100,96 @@
|
|||||||
- Engine.compile: builder選択(feature連動)+Lower実行+JIT handle発行✅ 完了
|
- Engine.compile: builder選択(feature連動)+Lower実行+JIT handle発行✅ 完了
|
||||||
- JIT関数テーブル(stub: handle→ダミー関数)✅ 完了
|
- JIT関数テーブル(stub: handle→ダミー関数)✅ 完了
|
||||||
- 残: 最小emit(const/binop/ret)をCLIFで生成し、関数ポインタをテーブル登録(feature有効時)
|
- 残: 最小emit(const/binop/ret)をCLIFで生成し、関数ポインタをテーブル登録(feature有効時)
|
||||||
→ 実装: CraneliftBuilderでi64用の`const/binop/ret`を生成し、JIT関数テーブルへクロージャとして登録完了(args未対応・i64専用)
|
→ 実装: CraneliftBuilderでi64用の`const/binop/ret`を生成し、JIT関数テーブルへクロージャとして登録完了(i64引数0〜6個対応/bool,f64はi64正規化)✅ 更新
|
||||||
2) 10_c: 呼出/フォールバック(最小経路)✅ 完了
|
2) 10_c: 呼出/フォールバック(最小経路)✅ 完了
|
||||||
- VM側の疑似ディスパッチログ(compiled時/実行時ログ)✅
|
- VM側の疑似ディスパッチログ(compiled時/実行時ログ)✅
|
||||||
- JIT実行→`VMValue`返却、panic時VMフォールバック ✅(`engine.execute_handle`で`catch_unwind`)
|
- JIT実行→`VMValue`返却、panic時VMフォールバック ✅(`engine.execute_handle`で`catch_unwind`)
|
||||||
- Core-1最小: i64 param/return、Const(i64/bool→0/1)、BinOp/Compare/Return ✅
|
- Core-1最小: i64 param/return、Const(i64/bool→0/1)、BinOp/Compare/Return ✅
|
||||||
- HostCall最小(Array/Map: len/get/set/push/size)ゲート`NYASH_JIT_HOSTCALL=1` ✅
|
- HostCall最小(Array/Map: len/get/set/push/size)ゲート`NYASH_JIT_HOSTCALL=1` ✅
|
||||||
- Branch/JumpはCranelift配線導入済み(feature `cranelift-jit`)。副作用命令は未lowerのためVMへフォールバック
|
- Branch/JumpはCranelift配線導入済み(feature `cranelift-jit`)。副作用命令は未lowerのためVMへフォールバック
|
||||||
3) 10_7: 分岐配線(Cranelift)— 進捗中
|
3) 10_7: 分岐/PHI/独立ABI(Cranelift)— 進捗中
|
||||||
- LowerCore: BB整列・マッピング→builderの`prepare_blocks/switch/seal/br_if/jump`呼出 ✅
|
- LowerCore: BB整列・マッピング→builderの`prepare_blocks/switch/seal/br_if/jump`呼出 ✅
|
||||||
- CraneliftBuilder: ブロック配列管理、`brif/jump`実装、条件b1/`i64!=0`両対応 ✅
|
- CraneliftBuilder: ブロック配列管理、`brif/jump`実装、条件b1/`i64!=0`両対応 ✅
|
||||||
- 最小PHI(単純ダイアモンド)導入(`NYASH_JIT_PHI_MIN=1`ガード)✅ 初期対応
|
- 最小PHI(単純ダイアモンド)導入(`NYASH_JIT_PHI_MIN=1`ガード)✅ 初期対応
|
||||||
- 残: 副作用命令の扱い方針(当面VMへ)、CFG可視化の拡張(`NYASH_JIT_DUMP=1`)
|
- JIT独立化: `JitValue` ABI + 変換アダプタ + TLS分離でVM内部へ非依存化 ✅ 完了
|
||||||
|
- 残: 副作用命令の扱い方針(当面VMへ)、CFG可視化の追加改善(ブロック引数の整形など)
|
||||||
|
- 検証: `--features cranelift-jit` 有効ビルドで `examples/jit_branch_demo.nyash` / `jit_phi_demo.nyash` がJIT経路で正常動作を確認(`NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1`)✅
|
||||||
|
|
||||||
|
### 🆕 追加(2025-08-28)
|
||||||
|
- JitStatsBox: `perFunction()` をVMディスパッチへ実装(JSON配列で name/hits/phi_total/phi_b1/ret_bool_hint/compiled/handle を返却)✅
|
||||||
|
- PHI(b1)読出しの安定化(最小)✅
|
||||||
|
- `NYASH_JIT_PHI_MIN=1` 有効時、ブールと推定されたPHIは b1 としてブロック引数を読み出し(分岐に直結)
|
||||||
|
- 分岐条件をブール格子のシードに追加(pre-scanで `condition` をbooleanとして扱う)
|
||||||
|
- デモ更新: `examples/jit_stats_summary_demo.nyash` に `perFunction()` 出力を追加 ✅
|
||||||
|
- jit-direct(独立JIT)足場の強化 ✅
|
||||||
|
- read-onlyガード(WriteHeap検出で明示エラー)/ 統一エラーJSON(`NYASH_JIT_ERROR_JSON=1`)
|
||||||
|
- 最小E2E: `examples/jit_direct_local_store_load.nyash` / `jit_direct_bool_ret.nyash` / `jit_direct_f64_ret.nyash`
|
||||||
|
- 参考: Box-First運用キット `docs/engineering/box_first_enforcement.md`(PR/CIはアドバイザリ導入)
|
||||||
|
|
||||||
|
## 🔜 次:Phase 10.9 Builtin-Box JIT(Box-Firstで最小から)
|
||||||
|
|
||||||
|
目的: VMとJITでビルトインBoxの読み取り系を同等動作にし、後続(生成/書き込み)へ進む足場を固める。
|
||||||
|
|
||||||
|
### 必要な箱(最小セット)
|
||||||
|
- JitPolicyBox(ポリシー箱)
|
||||||
|
- 役割: read-onlyゲート/書き込み検出/HostCall許可リストの一本化(1箇所で切替)
|
||||||
|
- 経路: runner/jit-direct/engine/lower が参照(env直読みはしない)
|
||||||
|
- JitEventsBox(観測箱)
|
||||||
|
- 役割: compile/execute/fallback/trapを JSONL へAppend({fn, abi_mode, reason, ms})
|
||||||
|
- 経路: 既存statsと共存。回帰の目視を容易に。
|
||||||
|
- HostcallRegistryBox(read-only)
|
||||||
|
- 役割: 許可HostCallと引数/戻りの種別を宣言。型検査の単一点にする。
|
||||||
|
- 初期: String.length/isEmpty/charCodeAt, Array.length/isEmpty/get, Map.size/has
|
||||||
|
- FrameSlotsBox(スロット箱)
|
||||||
|
- 役割: ptr→slot の管理と型注釈(今はi64のみ)。LowerCoreから委譲。
|
||||||
|
- CallBoundaryBox(呼出し境界箱/薄い)
|
||||||
|
- 役割: JIT↔JIT/JIT↔VM呼出しの単一点(将来の多関数対応の足場)。今は型変換の箱だけ用意。
|
||||||
|
|
||||||
|
最小原則: まず箱を置く(no-op/ログだけでもOK)→ 切替点が1箇所になったら機能を足す。
|
||||||
|
|
||||||
|
### 実装計画(小さい積み木で順に)
|
||||||
|
1) Week 10.9-α(足場)
|
||||||
|
- JitPolicyBox v0: read-only/HostCall whitelist を箱へ移動(runnerの散在チェックを統合)
|
||||||
|
- JitEventsBox v0: compile/executeのJSONLイベント(オプトイン)
|
||||||
|
- CURRENT_TASK/CLAUDEへポリシーと使い方を簡潔追記
|
||||||
|
2) Week 10.9-β(読み取り系カバレッジ)
|
||||||
|
- HostcallRegistryBox v0: String/Array/Mapの読み取りAPIを登録・型検査
|
||||||
|
- LowerCore: BoxCallのread-only経路をRegistry参照に切替(散在ロジックの削減)
|
||||||
|
- E2E: length/isEmpty/charCodeAt/get/size/has(jit-direct + VMで一致)
|
||||||
|
3) Week 10.9-γ(生成の足場)
|
||||||
|
- CallBoundaryBox v0: JIT→VM(new演算子など)を一旦VMへ委譲する薄い箱
|
||||||
|
- new StringBox/IntegerBox/ArrayBox の最小経路(jit-directは方針次第で拒否でも可)
|
||||||
|
4) Week 10.9-δ(書き込みの導線のみ)
|
||||||
|
- JitPolicyBox: write許可のスイッチを箱に実装(既定OFF)
|
||||||
|
- LowerCore: 書き込み命令はPolicy参照で拒否/委譲/許可(1箇所で判断)
|
||||||
|
|
||||||
|
### 成功判定(DoD)
|
||||||
|
- 機能: 読み取り系ビルトインBoxがJIT経路でVMと一致(上記API)
|
||||||
|
- 箱: Policy/Events/Registryが1箇所で参照される(散在ロジック解消)
|
||||||
|
- 観測: JSONLイベントが最低1件以上出力(明示opt-in)
|
||||||
|
- 回帰: `jit-direct` read-only方針が維持され、拒否理由がイベント/JSONで判読可
|
||||||
|
|
||||||
|
### リスクと箱での緩和
|
||||||
|
- HostCallの型崩れ: HostcallRegistryBoxで型検査→不一致はPolicy経由で明示拒否
|
||||||
|
- 方針変更の揺れ: JitPolicyBoxのフラグだけで切替可能(コード散在を回避)
|
||||||
|
- 観測不足: JitEventsBoxのイベント粒度を先に用意(必要最小限でOK)
|
||||||
|
|
||||||
|
|
||||||
備考(制限と次の着手点)
|
備考(制限と次の着手点)
|
||||||
- 返り値はi64(VMValue::Integer)に限定。f64はconst最小emit、boolはi64 0/1へ正規化(分岐条件入力に対応)
|
- 返り値はi64(VMValue::Integer)に限定。f64/boolは設計着手(ダンプで有効フラグ観測のみ)
|
||||||
- 引数はi64のみ最小パス。複数引数はparamマッピングで通過、非i64は未対応 → 次対応
|
- 引数はi64最小パス。複数引数はparamマッピングで通過、非i64はハンドル/正規化で暫定対応
|
||||||
- Branch/JumpのCLIF配線は導入済み(feature `cranelift-jit`)。条件はb1で保持し、必要に応じて`i64!=0`で正規化
|
- Branch/JumpのCLIF配線は導入済み(feature `cranelift-jit`)。条件はb1で保持し、必要に応じて`i64!=0`で正規化
|
||||||
- 副作用命令(print等)はJIT未対応のためVMへ委譲(安全性優先)
|
- 副作用命令(print等)はJIT未対応のためVMへ委譲(安全性優先)
|
||||||
- JIT/VM統合統計(フォールバック率/時間の一括出力)未統合 → 次対応
|
- JIT/VM統合統計(フォールバック率/時間の一括出力)✅ 実装済み(最小)
|
||||||
|
- `maybe_print_jit_unified_stats()` により、テキスト/JSON(`NYASH_JIT_STATS_JSON=1`)でsites/compiled/hits/exec_ok/trap/fallback_rate/handlesを出力
|
||||||
|
|
||||||
|
## 📦 箱作戦(JIT独立化の方針)
|
||||||
|
- ABIの箱: `JitValue`(I64/F64/Bool/Handle)でJIT入出力を統一、VM型に非依存
|
||||||
|
- 変換アダプタ: VMValue↔JitValueはアダプタに集約、境界は必ずアダプタ経由
|
||||||
|
- TLS分離: legacy VMArgs(既存HostCall用)とJIT Args(新ABI)を分離し段階移行
|
||||||
|
- HostCall独立化(計画): Handle(u64) + ハンドルレジストリ導入でJIT→ホストを完全独立(VM型非参照)
|
||||||
|
- 設定の箱(進捗): Rust側で`JitConfig`を導入しenv/CLI設定を集約(Nyash側Box化は後続)
|
||||||
|
- 可視化: `NYASH_JIT_DUMP=1`でCFG/ブロック引数を可視化し回帰検出力を強化
|
||||||
|
|
||||||
### すぐ試せるコマンド
|
### すぐ試せるコマンド
|
||||||
```bash
|
```bash
|
||||||
@ -67,6 +218,17 @@ NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 ./target/release/nyash --backend vm examp
|
|||||||
./target/release/nyash --benchmark --iterations 200
|
./target/release/nyash --benchmark --iterations 200
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### HostCall(ハンドルPoC)
|
||||||
|
```bash
|
||||||
|
./target/release/nyash --backend vm --jit-exec --jit-hostcall examples/jit_array_param_call.nyash
|
||||||
|
./target/release/nyash --backend vm --jit-exec --jit-hostcall examples/jit_map_param_call.nyash
|
||||||
|
./target/release/nyash --backend vm --jit-exec --jit-hostcall examples/jit_map_int_keys_param_call.nyash
|
||||||
|
./target/release/nyash --backend vm --jit-exec --jit-hostcall examples/jit_map_has_int_keys.nyash
|
||||||
|
./target/release/nyash --backend vm --jit-exec --jit-hostcall examples/jit_string_param_length.nyash
|
||||||
|
./target/release/nyash --backend vm --jit-exec --jit-hostcall examples/jit_string_is_empty.nyash
|
||||||
|
./target/release/nyash --backend vm --jit-exec --jit-hostcall examples/jit_string_charcode_at.nyash
|
||||||
|
```
|
||||||
|
|
||||||
## 現在の地図(Done / Next)
|
## 現在の地図(Done / Next)
|
||||||
|
|
||||||
### ✅ 完了(Phase 9.79b)
|
### ✅ 完了(Phase 9.79b)
|
||||||
@ -92,12 +254,13 @@ NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 ./target/release/nyash --backend vm examp
|
|||||||
- Lower emitのテスト雛形
|
- Lower emitのテスト雛形
|
||||||
- CLIFダンプ/CFG表示(`NYASH_JIT_DUMP=1`)
|
- CLIFダンプ/CFG表示(`NYASH_JIT_DUMP=1`)
|
||||||
- VM `--vm-stats` とJIT統計の統合
|
- VM `--vm-stats` とJIT統計の統合
|
||||||
|
→ 実装済み(VM終了時にJIT統合サマリ出力。JSON出力も可)
|
||||||
|
|
||||||
### 残タスク(箇条書き)
|
### 残タスク(箇条書き)
|
||||||
- 10_c:
|
- 10_c:
|
||||||
- CLIF: Branch/Jumpの実ブロック配線、Compare結果の適用確認
|
- CLIF: Branch/Jumpの実ブロック配線、Compare結果の適用確認
|
||||||
- 返り値/引数の型拡張(bool/f64)、複数引数の網羅
|
- 返り値/引数の型拡張(bool/f64)、複数引数の網羅
|
||||||
- JIT/VM統合統計(フォールバック率/時間の一括出力)
|
- JIT/VM統合統計(フォールバック率/時間の一括出力)✅ 済(最小)
|
||||||
- 10_4c:
|
- 10_4c:
|
||||||
- リーチャビリティ観測の深さ拡張(depth=2→N)と軽量ダンプ
|
- リーチャビリティ観測の深さ拡張(depth=2→N)と軽量ダンプ
|
||||||
- (将来)実Mark/TraverseのPoC(解放はしない)
|
- (将来)実Mark/TraverseのPoC(解放はしない)
|
||||||
@ -106,13 +269,32 @@ NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 ./target/release/nyash --backend vm examp
|
|||||||
- 10_6b:
|
- 10_6b:
|
||||||
- スケジューラ: poll予算の設定ファイル化、将来のscript API検討(継続)
|
- スケジューラ: poll予算の設定ファイル化、将来のscript API検討(継続)
|
||||||
- 10_7:
|
- 10_7:
|
||||||
- 最小PHI(単純ダイアモンド)の導入(`NYASH_JIT_PHI_MIN=1`ガード)
|
- Hostハンドルレジストリ導入(JIT側はHandleのみを見る)
|
||||||
- IRBuilder APIの整理(block param/分岐引数の正式化)とCranelift実装の安定化
|
- IRBuilder APIの整理(block param/分岐引数の正式化)とCranelift実装の安定化(暫定APIの整形)
|
||||||
- 副作用命令のJIT扱い(方針: 当面VMへ、将来はHostCall化)
|
- 副作用命令のJIT扱い(方針: 当面VMへ、将来はHostCall化)
|
||||||
- CFG検証と`NYASH_JIT_DUMP=1`でのCFG可視化
|
- CFG検証と`NYASH_JIT_DUMP=1`でのCFG可視化(ブロックと引数のダンプ)✅ 初期強化済み(更なる整形・網羅は継続)
|
||||||
|
- JitConfigBoxで設定の箱集約(env/CLI整合、テスト容易化)✅ Rust側の`JitConfig`導入済(Box化は後続)
|
||||||
|
- Bool戻り最小化: `hint_ret_bool`をCranelift側に配線し、b1→i64とb1ネイティブを切替可能に
|
||||||
|
- Compare/BinOpの副作用なし領域の拡大(Copy/Const/Castは開始済み、And/Orは当面VMフォールバック)
|
||||||
|
- PHI(b1)の扱い整理: ブロック引数のb1対応を正式化(i64 0/1正規化とb1の両立)
|
||||||
|
- 統計/ダンプの整備: `b1_norm_count`のJSON/テキスト両方での検証手順をドキュメント化
|
||||||
|
- b1 PHI格子の精度UP(簡易エイリアス/型注釈の取り込み)
|
||||||
|
- ネイティブb1 ABI: ツールチェーンcap連動で返り/引数b1署名への切替(切替点は一本化済)
|
||||||
|
- VMのf64演算/比較パリティ(10.8へ移送)
|
||||||
|
- JitStatsBox: `perFunction()` の箱API追加(summaryを介さず単体取得)
|
||||||
|
- b1 PHIタグの頑健化(Load/Store越しの由来推定を型/格子解析で補強)
|
||||||
|
- VMのf64演算/比較パリティ(10.8へ移送予定)
|
||||||
|
- JSONスキーマのversion管理とサンプル更新(`v1`整理済み、今後の拡張計画をドキュメント化)
|
||||||
- ベンチ:
|
- ベンチ:
|
||||||
- `examples/ny_bench.nyash`のケース追加(関数呼出/Map set-get)とループ回数のenv化
|
- `examples/ny_bench.nyash`のケース追加(関数呼出/Map set-get)とループ回数のenv化
|
||||||
|
|
||||||
|
### 🛠️ メンテ・ビルドノート(環境依存)
|
||||||
|
- WSL配下(`/mnt/c/...`)で `cargo build` 時に `Invalid cross-device link (os error 18)` が発生する場合あり
|
||||||
|
- 対策: リポジトリをLinux FS側(例: `~/work/nyash`)に配置/`CARGO_TARGET_DIR` と `TMPDIR` を同一FS配下へ設定
|
||||||
|
- 備考: リンク動作とFS差分が原因のため、コード修正ではなく環境調整で解決
|
||||||
|
- Codex CLIの実行権限・サンドボックス設定忘れで「ビルドできない」誤検知になる場合あり
|
||||||
|
- 対策: `codex --ask-for-approval never --sandbox danger-full-access` を付与して起動する(この指定忘れが原因だったケースあり)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
10_d まとめ(完了)
|
10_d まとめ(完了)
|
||||||
|
|||||||
52
docs/development/current/JIT_10_7_known_issues.txt
Normal file
52
docs/development/current/JIT_10_7_known_issues.txt
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
JIT Phase 10_7 — Known Issues and Future Refinements (2025-08-27)
|
||||||
|
|
||||||
|
Scope: LowerCore(Core-1), Branch/PHI wiring, ABI(min), Stats/CFG dump
|
||||||
|
|
||||||
|
1) b1 PHI tagging heuristics
|
||||||
|
- Symptom: (b1) tag may not appear for some PHI nodes when inputs are routed via variables (Load/Store) or multi-step copies.
|
||||||
|
- Current mitigation:
|
||||||
|
- Pre-scan: mark Compare/Const Bool as boolean producers.
|
||||||
|
- Propagate through Copy; close over PHI if all inputs are boolean-like.
|
||||||
|
- CFG dump heuristic: mark (b1) if pre-analysis true OR all inputs look boolean OR the PHI output is used as branch condition.
|
||||||
|
- Residual risk: Complex IR (loads/stores, deep copies, optimizer rewrites) can hide provenance; tag may still be missed.
|
||||||
|
- Future: Track boolean type via MIR type system or a dedicated lattice in analysis pass; extend provenance across Load/Store when safe.
|
||||||
|
|
||||||
|
2) f64 arithmetic/compare on VM backend
|
||||||
|
- Symptom: VM path errors on f64 BinOp/Compare (e.g., Add/>, mixed types).
|
||||||
|
- Cause: VM does not yet implement floating-point arithmetic/compare ops.
|
||||||
|
- Current mitigation: Examples using f64 are marked for Cranelift-enabled JIT environments; VM returns type errors as expected.
|
||||||
|
- Future: Implement f64 BinOp/Compare in VM or add auto-promotion layer consistent with JIT behavior.
|
||||||
|
|
||||||
|
3) JIT execution stub semantics
|
||||||
|
- Symptom: Previously, enabling NYASH_JIT_EXEC could alter results via stub returns when Cranelift was not enabled.
|
||||||
|
- Fix: Stub disabled; compilation skipped without Cranelift, full VM fallback retains semantics.
|
||||||
|
- Future: None (resolved). Keep behavior documented.
|
||||||
|
|
||||||
|
4) ret_bool_hint_count visibility
|
||||||
|
- Symptom: Counter can remain 0 when MIR signatures don’t mark Bool return even if code returns boolean values via VM-level boxes.
|
||||||
|
- Cause: Counter increments on MIR signature Bool return detection; not on dynamic boolean returns.
|
||||||
|
- Future: Consider counting dynamic sites or adding per-function attributes post-inference.
|
||||||
|
|
||||||
|
5) PHI dump dependency on phi_min
|
||||||
|
- Symptom: Detailed PHI dump (with (b1) tags) only shows when NYASH_JIT_PHI_MIN=1.
|
||||||
|
- Current stance: Intended; PHI path is opt-in while stabilizing.
|
||||||
|
- Future: Always-on summary rows possible; keep detail behind flag to reduce noise.
|
||||||
|
|
||||||
|
6) Stats scope and sources
|
||||||
|
- Symptom: JitStatsBox and unified stats currently reflect process-local counters; function-level detail via top5 only.
|
||||||
|
- Future: Add per-function structured stats via Box API; expose compilation state/timings when available.
|
||||||
|
|
||||||
|
7) Toolchain capability gating (b1 ABI)
|
||||||
|
- Symptom: Native boolean (b1) return in signatures remains disabled (cap false).
|
||||||
|
- Current stance: Switch point centralized; flips on when toolchain supports b1 in signatures.
|
||||||
|
- Future: Periodically probe toolchain; add CI check to toggle automatically.
|
||||||
|
|
||||||
|
8) Warning noise (unexpected cfg: llvm)
|
||||||
|
- Symptom: Warnings about unknown `llvm` feature in conditional cfg.
|
||||||
|
- Cause: Feature not declared in Cargo.toml; code guards are placeholders.
|
||||||
|
- Future: Add feature or silence warnings for non-supported builds.
|
||||||
|
|
||||||
|
9) Documentation sync
|
||||||
|
- Symptom: Some flags visible via CLI and boxes; ensure README/CURRENT_TASK stay aligned.
|
||||||
|
- Future: Add short “JIT quick flags” section with examples in docs.
|
||||||
|
|
||||||
68
docs/development/current/jit-enhancements-20250827.md
Normal file
68
docs/development/current/jit-enhancements-20250827.md
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# JIT機能拡張 - 2025年8月27日
|
||||||
|
|
||||||
|
## ChatGPT5による最新実装
|
||||||
|
|
||||||
|
### 1. PHI可視化強化
|
||||||
|
- CFGダンプに「PHIがboolean(b1)かどうか」を明示表示
|
||||||
|
- 例: `dst v12 (b1) <- 1:8, 2:9`
|
||||||
|
- PHI命令の型情報が一目で分かるように改善
|
||||||
|
|
||||||
|
### 2. JIT統計の上位表示
|
||||||
|
- 統合JIT統計(JSON)に `top5` を追加
|
||||||
|
- 関数名
|
||||||
|
- ヒット数
|
||||||
|
- compiled状態
|
||||||
|
- handle番号
|
||||||
|
- 例実行で `top5` に `main` が入ることを確認
|
||||||
|
|
||||||
|
### 3. 返り値ヒントの観測
|
||||||
|
- `ret_bool_hint_count` をJIT統計に追加
|
||||||
|
- JitStatsBox/統合JIT統計の両方で確認可能
|
||||||
|
- ブール返り値の最適化機会を可視化
|
||||||
|
|
||||||
|
### 4. 新しい例の追加
|
||||||
|
|
||||||
|
#### `examples/jit_stats_bool_ret.nyash`
|
||||||
|
- 統計JSONをプリントする最小デモ
|
||||||
|
- 最後にブールを返す
|
||||||
|
- JIT統計の動作確認用
|
||||||
|
|
||||||
|
#### `examples/jit_mixed_f64_compare.nyash`
|
||||||
|
- f64比較のデモ
|
||||||
|
- **注意**: VMのf64演算/比較未対応のため、Cranelift有効環境向けサンプル
|
||||||
|
|
||||||
|
## 使い方(観測)
|
||||||
|
|
||||||
|
### 統計+JSON出力
|
||||||
|
```bash
|
||||||
|
NYASH_JIT_STATS=1 NYASH_JIT_STATS_JSON=1 NYASH_JIT_THRESHOLD=1 \
|
||||||
|
./target/release/nyash --backend vm examples/jit_stats_bool_ret.nyash
|
||||||
|
```
|
||||||
|
|
||||||
|
JSONに以下が出力される:
|
||||||
|
- `abi_mode`
|
||||||
|
- `b1_norm_count`
|
||||||
|
- `ret_bool_hint_count`
|
||||||
|
- `top5`
|
||||||
|
|
||||||
|
### CFG/PHIダンプ
|
||||||
|
```bash
|
||||||
|
NYASH_JIT_DUMP=1 ./target/release/nyash --backend vm examples/phi_bool_merge.nyash
|
||||||
|
```
|
||||||
|
- b1 PHIには `(b1)` タグが付与される
|
||||||
|
|
||||||
|
## 注意事項
|
||||||
|
|
||||||
|
- VMのf64演算/比較は未対応
|
||||||
|
- `jit_mixed_f64_compare.nyash` はCranelift有効環境(JIT実行)での確認用
|
||||||
|
- VMでの実行はエラーになる
|
||||||
|
|
||||||
|
## 実装の意義
|
||||||
|
|
||||||
|
これらの機能拡張により:
|
||||||
|
1. **可視性の向上**: PHI命令の型情報が明確に
|
||||||
|
2. **統計の充実**: top5による頻繁に呼ばれる関数の把握
|
||||||
|
3. **最適化ヒント**: ブール返り値のカウントによる最適化機会の発見
|
||||||
|
4. **デバッグ支援**: より詳細な情報による問題解析の容易化
|
||||||
|
|
||||||
|
Box-First方法論の「観測箱」の具現化として、これらの機能は論文の実証例となる。
|
||||||
@ -0,0 +1,239 @@
|
|||||||
|
# Phase 10.1 - Python統合計画(ChatGPT5高速開発版)
|
||||||
|
最終更新: 2025-08-27
|
||||||
|
|
||||||
|
## 🚀 概要:2週間での爆速実装
|
||||||
|
|
||||||
|
ChatGPT5の最小Box設計により、元の1ヶ月計画を**2週間**に圧縮。Nyash既存アーキテクチャ(MirBuilder 100%実装済み、HandleRegistry 80%実装済み)を最大活用。
|
||||||
|
|
||||||
|
## 📦 ChatGPT5の6つの必須Box(最小実装)
|
||||||
|
|
||||||
|
### 1. **PythonParserBox** - CPython AST取得(3日)
|
||||||
|
```rust
|
||||||
|
// 既存: pyo3統合済み
|
||||||
|
// 追加: JSON出力とバージョン固定
|
||||||
|
pub struct PythonParserBox {
|
||||||
|
base: BoxBase,
|
||||||
|
py_helper: Arc<Mutex<PyHelper>>,
|
||||||
|
version: String, // "3.11"固定
|
||||||
|
}
|
||||||
|
|
||||||
|
// メソッド(最小限)
|
||||||
|
- parse_to_json(src: String) -> String // ast.parse() → JSON
|
||||||
|
- get_version() -> String // "3.11"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. **Py2NyASTBox** - AST変換(3日)
|
||||||
|
```rust
|
||||||
|
// 新規実装
|
||||||
|
pub struct Py2NyASTBox {
|
||||||
|
base: BoxBase,
|
||||||
|
normalizer: AstNormalizer,
|
||||||
|
}
|
||||||
|
|
||||||
|
// メソッド(制御フロー正規化)
|
||||||
|
- convert(json: String) -> NyashAst
|
||||||
|
- normalize_for_else(ast: &mut PyAst) // for/else → if分岐
|
||||||
|
- normalize_comprehensions(ast: &mut PyAst)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. **MirBuilderBox** - MIR生成(0日 - 既存活用)
|
||||||
|
```rust
|
||||||
|
// 既存実装100%活用
|
||||||
|
// 追加: Python由来フラグのみ
|
||||||
|
pub struct MirBuilderBox {
|
||||||
|
// 既存フィールド
|
||||||
|
is_python_origin: bool, // 追加
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. **BoundaryBox** - 型変換(2日)
|
||||||
|
```rust
|
||||||
|
// Python版のHandleRegistry相当
|
||||||
|
pub struct BoundaryBox {
|
||||||
|
base: BoxBase,
|
||||||
|
handle_registry: Arc<Mutex<HandleRegistry>>, // 既存80%活用
|
||||||
|
}
|
||||||
|
|
||||||
|
// メソッド
|
||||||
|
- py_to_jit(py_val: PyValBox) -> JitValue
|
||||||
|
- jit_to_py(jit_val: JitValue) -> PyValBox
|
||||||
|
- register_handle(obj: Arc<dyn NyashBox>) -> u64
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. **PyRuntimeBox** - 実行制御(2日)
|
||||||
|
```rust
|
||||||
|
pub struct PyRuntimeBox {
|
||||||
|
base: BoxBase,
|
||||||
|
fallback_stats: FallbackStats,
|
||||||
|
}
|
||||||
|
|
||||||
|
// メソッド(関数単位フォールバック)
|
||||||
|
- execute_function(name: &str, args: Vec<JitValue>) -> JitValue
|
||||||
|
- should_fallback(func_ast: &PyAst) -> bool // Phase1機能判定
|
||||||
|
- fallback_to_cpython(code: &str) -> PyObject
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. **ObservabilityBox** - 統計収集(1日)
|
||||||
|
```rust
|
||||||
|
// 既存のJIT統計システム(70%実装済み)を拡張
|
||||||
|
pub struct ObservabilityBox {
|
||||||
|
base: BoxBase,
|
||||||
|
stats_collector: StatsCollector,
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONLフォーマット出力
|
||||||
|
- log_attempt(module: &str, func: &str, compiled: bool, reason: Option<&str>)
|
||||||
|
- output_jsonl() -> String
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🗓️ 実装タイムライン(2週間)
|
||||||
|
|
||||||
|
### Week 1: 基盤実装(7日)
|
||||||
|
- **Day 1-3**: PythonParserBox実装
|
||||||
|
- pyo3統合(既存活用)
|
||||||
|
- Python 3.11固定
|
||||||
|
- JSON出力実装
|
||||||
|
|
||||||
|
- **Day 4-6**: Py2NyASTBox実装
|
||||||
|
- 制御フロー正規化
|
||||||
|
- for/else, while/else変換
|
||||||
|
- Phase1機能のみサポート
|
||||||
|
|
||||||
|
- **Day 7**: ObservabilityBox実装
|
||||||
|
- 既存JIT統計拡張
|
||||||
|
- JSONLフォーマット
|
||||||
|
|
||||||
|
### Week 2: 統合と検証(7日)
|
||||||
|
- **Day 8-9**: BoundaryBox実装
|
||||||
|
- HandleRegistry活用
|
||||||
|
- 型変換ルール確立
|
||||||
|
|
||||||
|
- **Day 10-11**: PyRuntimeBox実装
|
||||||
|
- 関数単位フォールバック
|
||||||
|
- CPython連携
|
||||||
|
|
||||||
|
- **Day 12-13**: 統合テスト
|
||||||
|
- Differential Testing
|
||||||
|
- ベンチマーク実行
|
||||||
|
|
||||||
|
- **Day 14**: ドキュメント・リリース
|
||||||
|
- 使用例作成
|
||||||
|
- パフォーマンス測定
|
||||||
|
|
||||||
|
## 📊 既存アーキテクチャとの整合性
|
||||||
|
|
||||||
|
### 活用率
|
||||||
|
- **MirBuilderBox**: 100%(変更なし)
|
||||||
|
- **HandleRegistry**: 80%(BoundaryBoxで再利用)
|
||||||
|
- **JIT統計**: 70%(ObservabilityBoxで拡張)
|
||||||
|
- **VM/JIT実行**: 100%(そのまま使用)
|
||||||
|
|
||||||
|
### 新規実装
|
||||||
|
- **PythonParserBox**: 30%(pyo3部分は既存)
|
||||||
|
- **Py2NyASTBox**: 100%新規
|
||||||
|
- **PyRuntimeBox**: 100%新規
|
||||||
|
|
||||||
|
## 🎯 Phase 1でサポートする機能(Codex先生推奨)
|
||||||
|
|
||||||
|
### 必須実装
|
||||||
|
1. **LEGB + locals/freevars** - スコーピング規則
|
||||||
|
2. **デフォルト引数の評価タイミング** - 定義時評価
|
||||||
|
3. **イテレータベースのfor文**
|
||||||
|
4. **for/else + while/else**
|
||||||
|
5. **Python真偽値判定**
|
||||||
|
6. **短絡評価**
|
||||||
|
|
||||||
|
### サポートする文
|
||||||
|
- def(関数定義)
|
||||||
|
- if/elif/else
|
||||||
|
- for(else節対応)
|
||||||
|
- while(else節対応)
|
||||||
|
- break/continue
|
||||||
|
- return
|
||||||
|
|
||||||
|
### サポートする式
|
||||||
|
- 算術演算子(+,-,*,/,//,%)
|
||||||
|
- 比較演算子(==,!=,<,>,<=,>=)
|
||||||
|
- 論理演算子(and,or,not)
|
||||||
|
- 関数呼び出し
|
||||||
|
- リテラル(数値/文字列/bool)
|
||||||
|
|
||||||
|
## 📈 成功指標(2週間後)
|
||||||
|
|
||||||
|
### 定量的
|
||||||
|
- **関数コンパイル率**: 70%以上(Phase 1機能)
|
||||||
|
- **実行速度**: 純Pythonループで2倍以上
|
||||||
|
- **メモリ効率**: CPython比50%削減
|
||||||
|
|
||||||
|
### 定性的
|
||||||
|
- **統計可視化**: JSONL形式で全実行を記録
|
||||||
|
- **デバッグ容易性**: 関数単位でフォールバック理由明示
|
||||||
|
- **将来拡張性**: Phase 2-4への明確な道筋
|
||||||
|
|
||||||
|
## 🔧 実装例(最終形)
|
||||||
|
|
||||||
|
```nyash
|
||||||
|
// Nyashから使用
|
||||||
|
local py = new PythonParserBox()
|
||||||
|
local converter = new Py2NyASTBox()
|
||||||
|
local builder = new MirBuilderBox()
|
||||||
|
local runtime = new PyRuntimeBox()
|
||||||
|
local stats = new ObservabilityBox()
|
||||||
|
|
||||||
|
// Pythonコードをコンパイル・実行
|
||||||
|
local code = "def fib(n): return n if n <= 1 else fib(n-1) + fib(n-2)"
|
||||||
|
local json_ast = py.parse_to_json(code)
|
||||||
|
local ny_ast = converter.convert(json_ast)
|
||||||
|
local mir = builder.build(ny_ast)
|
||||||
|
|
||||||
|
// 実行(自動的にJIT/VMで高速化)
|
||||||
|
local result = runtime.execute_function("fib", [10])
|
||||||
|
print(result) // 55
|
||||||
|
|
||||||
|
// 統計出力
|
||||||
|
print(stats.output_jsonl())
|
||||||
|
// {"mod":"test","func":"fib","attempt":1,"jitted":true,"native":true}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚨 重要な設計判断
|
||||||
|
|
||||||
|
### 1. 関数単位の境界
|
||||||
|
- ファイル単位ではなく**関数単位**でコンパイル/フォールバック
|
||||||
|
- 未対応機能を含む関数のみCPython実行
|
||||||
|
|
||||||
|
### 2. Python 3.11固定
|
||||||
|
- AST安定性の確保
|
||||||
|
- 将来のバージョンアップは別Phase
|
||||||
|
|
||||||
|
### 3. 箱境界の明確化
|
||||||
|
- 各Boxは単一責任
|
||||||
|
- 相互依存を最小化
|
||||||
|
- テスト可能な粒度
|
||||||
|
|
||||||
|
### 4. 既存資産の最大活用
|
||||||
|
- MirBuilder/VM/JITはそのまま使用
|
||||||
|
- 新規実装は変換層のみ
|
||||||
|
|
||||||
|
## 🎉 期待される成果
|
||||||
|
|
||||||
|
### 即時的効果(2週間後)
|
||||||
|
- Pythonコードの70%がNyashで高速実行
|
||||||
|
- バグ検出力の飛躍的向上(Differential Testing)
|
||||||
|
- 統計による最適化ポイントの可視化
|
||||||
|
|
||||||
|
### 長期的効果
|
||||||
|
- Python→Nyash→Native の世界初パイプライン確立
|
||||||
|
- Nyash言語の成熟度向上
|
||||||
|
- エコシステムの爆発的拡大
|
||||||
|
|
||||||
|
## 📝 次のステップ
|
||||||
|
|
||||||
|
1. **Phase 10.7完了確認** - JIT統計JSONの安定化
|
||||||
|
2. **PythonParserBox実装開始** - pyo3統合から着手
|
||||||
|
3. **テストコーパス準備** - Python標準ライブラリから抜粋
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**作成者**: Claude(Claude Code)
|
||||||
|
**承認者**: ChatGPT5(予定)
|
||||||
|
**開始予定**: Phase 10.7完了直後
|
||||||
@ -0,0 +1,135 @@
|
|||||||
|
Phase 10.7 — JIT CFG/PHI/ABI Hardening (Master Plan)
|
||||||
|
|
||||||
|
Intent
|
||||||
|
- Deliver a practical, observable JIT path for control flow and minimal data flow while keeping VM as the safe fallback.
|
||||||
|
- Decompose into small, shippable sub-phases with flags and examples, minimizing blast radius.
|
||||||
|
|
||||||
|
Related docs
|
||||||
|
- 10.7a details: phase_10_7a_jit_phi_cfg_and_abi_min.txt
|
||||||
|
- Current work log: docs/development/current/CURRENT_TASK.md
|
||||||
|
- Cranelift JIT backend notes: phase_10_cranelift_jit_backend.md
|
||||||
|
|
||||||
|
Flags and CLI (common across 10.7)
|
||||||
|
- Env: NYASH_JIT_EXEC, NYASH_JIT_STATS, NYASH_JIT_STATS_JSON, NYASH_JIT_DUMP, NYASH_JIT_THRESHOLD, NYASH_JIT_PHI_MIN, NYASH_JIT_HOSTCALL
|
||||||
|
- CLI: --jit-exec --jit-stats --jit-stats-json --jit-dump --jit-threshold N --jit-phi-min --jit-hostcall
|
||||||
|
|
||||||
|
Examples to validate
|
||||||
|
- examples/jit_branch_demo.nyash
|
||||||
|
- examples/jit_loop_early_return.nyash
|
||||||
|
- examples/jit_phi_demo.nyash
|
||||||
|
- examples/jit_array_param_call.nyash, jit_map_param_call.nyash (when hostcall is enabled)
|
||||||
|
|
||||||
|
Sub-phases (10.7a → 10.7h)
|
||||||
|
|
||||||
|
10.7a — Minimal branch/PHI/ABI (Done or in-flight)
|
||||||
|
- Branch wiring with b1 and i64!=0 normalization
|
||||||
|
- Minimal PHI: single-value diamond via block param
|
||||||
|
- Independent ABI (JitValue) with integer-first calling
|
||||||
|
- Panic-safe dispatch (catch_unwind) and VM fallback
|
||||||
|
- Minimal hostcall bridge (Array/Map) behind NYASH_JIT_HOSTCALL
|
||||||
|
- Observability: unified JIT summary/JSON, lower summary (argc/phi_min), CFG edge dump (phi_min)
|
||||||
|
|
||||||
|
Acceptance: see 10.7a doc
|
||||||
|
|
||||||
|
10.7b — PHI generalization and block-parameterization
|
||||||
|
- Support multiple PHIs in a merge block; pass N values via block params
|
||||||
|
- LowerCore: gather PHI inputs per successor, produce explicit arg lists; handle straight merges and simple loops
|
||||||
|
- IRBuilder API formalization:
|
||||||
|
- ensure_block_params_i64(index, count)
|
||||||
|
- br_if_with_args(then_idx, else_idx, then_n, else_n)
|
||||||
|
- jump_with_args(target_idx, n)
|
||||||
|
- CraneliftBuilder: append N I64 params per block, materialize current block params onto stack when requested
|
||||||
|
- Validation: multi-PHI diamonds, if-else chains, early returns; keep loops as simple as possible in 10.7b
|
||||||
|
|
||||||
|
10.7c — Independent host-handle registry (JIT↔Host decoupling)
|
||||||
|
- Introduce JitHandleRegistry: u64 ↔ Arc<dyn NyashBox>
|
||||||
|
- JIT hostcalls accept/return only handles and PODs; VMValue↔JitValue boundary converts BoxRef <-> Handle via registry
|
||||||
|
- Rooting policy: enter/pin roots for handles crossing the boundary; leave on return
|
||||||
|
- Flags: NYASH_JIT_HOSTCALL=1 (still gated); add NYASH_JIT_HANDLE_DEBUG for dumps
|
||||||
|
- Deliverables: array/map operations via handles, no direct VMValue access on JIT side
|
||||||
|
|
||||||
|
10.7d — Side-effect boundary and hostcall coverage expansion
|
||||||
|
- Keep side-effecting ops (print/IO) on VM for now; document deopt path
|
||||||
|
- Expand safe hostcalls (read-only or confined):
|
||||||
|
- String.len / slice basics (POD returns)
|
||||||
|
- Map.size; Map.get with integer and string keys (key boxing handled at boundary)
|
||||||
|
- Record effect categories on lowered calls for future optimizer (metadata only)
|
||||||
|
|
||||||
|
10.7e — CFG diagnostics and visualization
|
||||||
|
- Add DOT export for CLIF CFG with block params and PHI bindings
|
||||||
|
- Env: NYASH_JIT_DOT=path.dot (produces per-function graph)
|
||||||
|
- Optional: ASCII CFG summary for CI logs
|
||||||
|
- Cross-check with VM-side MIR printer for block and PHI consistency
|
||||||
|
|
||||||
|
10.7f — JitConfigBox (configuration as a Box)
|
||||||
|
- Encapsulate all JIT toggles into a runtime-managed box (JitConfigBox)
|
||||||
|
- Harmonize env/CLI with programmatic overrides for tests
|
||||||
|
- Feed config into JIT engine and lowerers (no direct env reads inside hot paths)
|
||||||
|
- Serialization: dump/load config JSON for reproducible runs
|
||||||
|
|
||||||
|
10.7g — Stability, tests, and benchmarks
|
||||||
|
- Golden tests: ensure JIT/VM outputs match on curated programs
|
||||||
|
- Fallback ratio regression guard: alert when fallback_rate spikes beyond threshold
|
||||||
|
- JSON schema stability for stats; include version field
|
||||||
|
- Microbenchmarks: branch-heavy, phi-heavy, and hostcall-heavy cases; gated perf checks
|
||||||
|
|
||||||
|
10.7h — Type widening for native ABI
|
||||||
|
- Add native F64 and Bool parameter/return paths in CLIF
|
||||||
|
- Condition handling: keep b1 where possible; map Bool to b1 cleanly
|
||||||
|
- Conversions: explicit i64<->f64 ops in lowerer where needed (no silent truncation)
|
||||||
|
- Update adapter and closure trampoline to select proper function signatures (arity×type shapes)
|
||||||
|
|
||||||
|
Out of scope for 10.7 (shift to 10.8/10.9)
|
||||||
|
- Global register allocation strategies or codegen-level optimizations
|
||||||
|
- Deoptimization machinery beyond simple VM fallback
|
||||||
|
- Advanced exception propagation across JIT/VM boundary
|
||||||
|
|
||||||
|
Risks and mitigation
|
||||||
|
- PHI N-arity correctness: introduce targeted unit tests over synthetic MIR
|
||||||
|
- Handle registry leaks: counting diagnostics, strict mode; tie roots to scope regions
|
||||||
|
- CLIF block-param misuse: deterministic block order + seal discipline + assertions in builder
|
||||||
|
|
||||||
|
Verification checklist (roll-up)
|
||||||
|
- cargo check (± --features cranelift-jit)
|
||||||
|
- Run examples with JIT flags; diff with pure VM runs
|
||||||
|
- Inspect: unified JIT summary/JSON, lower logs, CFG dumps/DOT
|
||||||
|
- Leak/roots checks when NYASH_GC_TRACE=1/2/3 and strict barrier mode is on
|
||||||
|
|
||||||
|
Suggested timeline (tentative)
|
||||||
|
- 10.7a: Minimal branch/PHI/ABI (done / in-flight)
|
||||||
|
- 10.7b: PHI generalization + builder API formalization (1–2 days)
|
||||||
|
- 10.7c: Host-handle registry PoC (1–2 days)
|
||||||
|
- 10.7d/e: Hostcall coverage + CFG DOT (2–3 days)
|
||||||
|
- 10.7f: JitConfigBox + integration (1 day)
|
||||||
|
- 10.7g/h: QA + type widening for f64/bool (2–4 days)
|
||||||
|
|
||||||
|
10.7z — Follow-ups and Open Items (post-10.7)
|
||||||
|
- b1 PHI tagging robustness
|
||||||
|
- Problem: Provenance can be obscured by Load/Store and multi-step copies; (b1) tag may be missed in dumps.
|
||||||
|
- Action:
|
||||||
|
- Add a lightweight boolean-lattice analysis over MIR to classify boolean-producing values independent of path shape.
|
||||||
|
- Extend dump to include phi_summary JSON or structured rows when NYASH_JIT_STATS_JSON is on.
|
||||||
|
- Placement: 10.7g (stability/tests) — does not block 10.7 close.
|
||||||
|
|
||||||
|
- VM f64 arithmetic/compare parity
|
||||||
|
- Problem: VM backend currently errors on f64 BinOp/Compare; JIT (Cranelift) supports f64 when enabled.
|
||||||
|
- Action: Implement f64 ops in VM or add consistent auto-promotion; add golden tests.
|
||||||
|
- Placement: 10.8 (VM parity/perf) — out-of-scope for 10.7.
|
||||||
|
|
||||||
|
- Native b1 ABI in function signatures
|
||||||
|
- Problem: Toolchain capability for b1 return/params is currently disabled.
|
||||||
|
- Action: Keep centralized switch; add CI probe to flip automatically when supported; wire return path fully.
|
||||||
|
- Placement: 10.7h or later (gated by toolchain).
|
||||||
|
|
||||||
|
- Stats/diagnostics polish
|
||||||
|
- Action: Version the unified JIT JSON schema; expose phi(b1) slot counts in JSON; enrich JitStatsBox with summary/topN (partially done).
|
||||||
|
- Placement: 10.7g.
|
||||||
|
|
||||||
|
- Build warnings (unexpected cfg: llvm)
|
||||||
|
- Problem: Warning noise from unused/unknown cfg.
|
||||||
|
- Action: Declare feature flags in Cargo.toml or gate code behind existing features; optionally silence for non-supported builds.
|
||||||
|
- Placement: 10.7g (cleanup) — non-blocking.
|
||||||
|
|
||||||
|
- Documentation sync
|
||||||
|
- Action: Add a "JIT quick flags" section with common env/CLI combos; ensure CURRENT_TASK and examples remain aligned.
|
||||||
|
- Placement: 10.7e (docs) — non-blocking.
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
Phase 10.7a — JIT Branch/PHI/Independent ABI: Minimal Path Hardening (Plan)
|
||||||
|
|
||||||
|
Purpose
|
||||||
|
- Solidify the first functional slice of the JIT control-flow path:
|
||||||
|
- Branch wiring (b1 and i64!=0 normalization)
|
||||||
|
- Minimal PHI via block parameters (single-value diamond)
|
||||||
|
- Independent ABI (JitValue) and safe dispatch + fallback
|
||||||
|
- Observable, toggleable, and easy to verify via flags + examples
|
||||||
|
|
||||||
|
Scope (10.7a only)
|
||||||
|
1) Minimal PHI handoff
|
||||||
|
- LowerCore: detect single-PHI merge; pass i64 value via block param
|
||||||
|
- CraneliftBuilder: ensure block param (I64), push/pop on branch/jump with args
|
||||||
|
- Flag gate: NYASH_JIT_PHI_MIN=1
|
||||||
|
|
||||||
|
2) Branch wiring and condition normalization
|
||||||
|
- Keep compare result on stack as b1 when possible
|
||||||
|
- If top-of-stack is I64, normalize by icmp_imm != 0
|
||||||
|
- Implement br_if/jump to pre-created blocks (deterministic order)
|
||||||
|
|
||||||
|
3) Independent ABI (minimum viable)
|
||||||
|
- JitValue(I64/F64/Bool/Handle) in/out; normalize non-i64 args to i64 for now
|
||||||
|
- TLS split: legacy VM args (for hostcalls) and JIT args
|
||||||
|
- Engine.execute_handle uses catch_unwind; panic → VM fallback with stats
|
||||||
|
|
||||||
|
4) Minimal hostcall bridge (safe; off by default)
|
||||||
|
- Symbols: nyash.array.{len,get,set,push}, nyash.map.{size}
|
||||||
|
- Gate: NYASH_JIT_HOSTCALL=1
|
||||||
|
- Only integer indices/values (PoC); other types map to handles later
|
||||||
|
|
||||||
|
5) Observability and ergonomics
|
||||||
|
- Flags: NYASH_JIT_EXEC, NYASH_JIT_STATS, NYASH_JIT_STATS_JSON, NYASH_JIT_DUMP, NYASH_JIT_THRESHOLD, NYASH_JIT_PHI_MIN, NYASH_JIT_HOSTCALL
|
||||||
|
- CLI: --jit-exec --jit-stats --jit-stats-json --jit-dump --jit-threshold N --jit-phi-min --jit-hostcall
|
||||||
|
- Unified JIT summary on VM exit (sites/compiled/hits/exec_ok/trap/fallback_rate)
|
||||||
|
- Lower log includes argc/phi_min + CFG light dump (phi edges) when NYASH_JIT_DUMP=1
|
||||||
|
|
||||||
|
Non-Goals (later 10.7b+)
|
||||||
|
- Full PHI generalization (multiple values, loops, complex CFG forms)
|
||||||
|
- Non-i64 native path (true F64/Bool return/params in CLIF)
|
||||||
|
- Side-effect instruction lowering (print, IO) — keep in VM path
|
||||||
|
- Host handle registry for real object bridging (u64↔Arc<dyn NyashBox>)
|
||||||
|
|
||||||
|
Deliverables
|
||||||
|
- Working minimal PHI + branch JIT execution on curated examples:
|
||||||
|
- examples/jit_branch_demo.nyash
|
||||||
|
- examples/jit_loop_early_return.nyash
|
||||||
|
- examples/jit_phi_demo.nyash (single-PHI diamond)
|
||||||
|
- Fallback correctness: traps/panic → VM path; results match VM
|
||||||
|
- Configurable via CLI flags; metrics visible via JIT summary/JSON
|
||||||
|
|
||||||
|
Acceptance Criteria
|
||||||
|
- With: --backend vm --jit-exec --jit-stats --jit-threshold 1
|
||||||
|
- For branch/phi examples, JIT executes without panic
|
||||||
|
- VM fallback occurs only for unsupported ops (logged)
|
||||||
|
- JIT summary shows exec_ok > 0 and reasonable fallback_rate
|
||||||
|
- With: --jit-phi-min
|
||||||
|
- CFG dump lists phi_min edges and blocks count
|
||||||
|
- Results match the same program without JIT enabled
|
||||||
|
|
||||||
|
Risk & Mitigation
|
||||||
|
- Mismatch between VMValue and JitValue adapters
|
||||||
|
- Mitigation: normalize non-i64 to i64 defensively; expand tests on adapters
|
||||||
|
- Cranelift block parameter misuse
|
||||||
|
- Mitigation: deterministic block order and explicit ensure_block_param_i64()
|
||||||
|
- Panic in hostcall stubs (unexpected Box types)
|
||||||
|
- Mitigation: gated by NYASH_JIT_HOSTCALL=1; default off; fallback to VM on panic
|
||||||
|
|
||||||
|
Verification Checklist
|
||||||
|
- cargo check (w/ and w/o --features cranelift-jit)
|
||||||
|
- Run examples with JIT flags; compare outputs with pure VM
|
||||||
|
- Inspect logs: lower summary + CFG dump + unified summary/JSON
|
||||||
|
|
||||||
|
Timeline (10.7a)
|
||||||
|
Day 1:
|
||||||
|
- Finalize ABI normalization and branch wiring; add unified stats (done)
|
||||||
|
- Wire CLI flags and JSON stats (done)
|
||||||
|
Day 2:
|
||||||
|
- Harden minimal PHI path and CFG dump (done)
|
||||||
|
- Curate examples and sanity-run on flags
|
||||||
|
Day 3:
|
||||||
|
- Stabilize logging format, trim rough edges, doc polish
|
||||||
|
→ Then roll into 10.7b (multi-PHI, multi-arg block params, real handle registry)
|
||||||
|
|
||||||
|
Follow-ups (10.7b/10.7c seeds)
|
||||||
|
- Host handle registry (u64↔Arc) and type-safe bridging
|
||||||
|
- True F64/Bool native ABI, multi-arg params/returns
|
||||||
|
- CFG visualization improvements (dot export) and JitConfigBox
|
||||||
|
|
||||||
|
Refs
|
||||||
|
- docs/development/current/CURRENT_TASK.md (10_7 items)
|
||||||
|
- src/jit/{lower,engine,manager,abi,rt}/
|
||||||
|
- examples: jit_* demos
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
# Phase 10.7h — Native ABI Types (F64/Bool)
|
||||||
|
|
||||||
|
Goal
|
||||||
|
- Extend the minimal i64-only JIT ABI to support f64 and bool as native parameter/return types in CLIF.
|
||||||
|
|
||||||
|
Principles
|
||||||
|
- Keep JIT independent from VM internals (use JitValue + adapters at boundary)
|
||||||
|
- Avoid silent truncation; perform explicit conversions in the lowerer
|
||||||
|
- Maintain safety-first fallback to VM for unsupported ops
|
||||||
|
|
||||||
|
Plan
|
||||||
|
1) JitValue widening
|
||||||
|
- JitValue already has I64/F64/Bool/Handle — keep this as the ABI surface
|
||||||
|
- Adapter: refine to/from VMValue mappings (no lossy coercion by default)
|
||||||
|
|
||||||
|
2) CLIF signature selection
|
||||||
|
- Augment CraneliftBuilder to build signatures based on (arity × type shape)
|
||||||
|
- Start with small shapes: (I64|F64|Bool)* → I64|F64|Bool
|
||||||
|
- Closure trampoline: transmute to matching extern "C" fn type; dispatch by shape id
|
||||||
|
|
||||||
|
3) Condition handling
|
||||||
|
- Bool: prefer b1 in IR; allow i64!=0 normalization when comparing integers
|
||||||
|
- Comparisons yield b1; lower branch consumes b1 directly
|
||||||
|
|
||||||
|
4) Conversions in lowerer (explicit only)
|
||||||
|
- add_const_f64, add_convert_{i64_to_f64, f64_to_i64}
|
||||||
|
- prohibit implicit int<->float coercion in arithmetic; gate conversions via explicit MIR ops or intrinsics
|
||||||
|
|
||||||
|
5) Observability and flags
|
||||||
|
- NYASH_JIT_NATIVE_F64=1 / NYASH_JIT_NATIVE_BOOL=1 to enable paths
|
||||||
|
- Dump: show chosen signature shape and conversions when NYASH_JIT_DUMP=1
|
||||||
|
|
||||||
|
6) Rollout
|
||||||
|
- Phase A: const/binop/ret for f64; comparisons yield b1
|
||||||
|
- Phase B: mixed-type ops via explicit converts
|
||||||
|
- Phase C: HostCall bridging for f64/bool PODs (read-only first)
|
||||||
|
|
||||||
|
Risks / Mitigation
|
||||||
|
- Signature explosion: start with a few common shapes; fallback to i64 path
|
||||||
|
- Platform ABI mismatches: rely on Cranelift default call conv; e2e-perf and correctness first
|
||||||
|
|
||||||
|
Acceptance
|
||||||
|
- Examples with pure f64 pipelines run under JIT with matching results vs VM
|
||||||
|
- No silent lossy conversions; conversions visible in MIR/Lower logs
|
||||||
|
|
||||||
@ -0,0 +1,175 @@
|
|||||||
|
# Phase 10.9 - ビルトインBox JITサポート
|
||||||
|
|
||||||
|
## 🎯 目的
|
||||||
|
ビルトインBoxをJITで使えるようにし、Python統合(Phase 10.1)への道を開く。
|
||||||
|
|
||||||
|
## 📦 対象Box(優先順位順)
|
||||||
|
|
||||||
|
### 第1段階:読み取り専用メソッド
|
||||||
|
```nyash
|
||||||
|
// StringBox
|
||||||
|
str.length() // → i64
|
||||||
|
str.isEmpty() // → bool
|
||||||
|
str.charAt(idx) // → String(新Box生成)
|
||||||
|
|
||||||
|
// ArrayBox
|
||||||
|
arr.length() // → i64
|
||||||
|
arr.isEmpty() // → bool
|
||||||
|
arr.get(idx) // → Box(既存参照)
|
||||||
|
|
||||||
|
// IntegerBox/FloatBox
|
||||||
|
int.toFloat() // → f64
|
||||||
|
float.toInt() // → i64
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第2段階:Box生成
|
||||||
|
```nyash
|
||||||
|
// new演算子のJIT化
|
||||||
|
new StringBox("hello") // → Handle
|
||||||
|
new IntegerBox(42) // → Handle(または直接i64)
|
||||||
|
new ArrayBox() // → Handle
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第3段階:書き込みメソッド
|
||||||
|
```nyash
|
||||||
|
// 状態変更を伴う操作
|
||||||
|
arr.push(item) // Mutex操作必要
|
||||||
|
arr.set(idx, value) // 境界チェック必要
|
||||||
|
map.set(key, value) // ハッシュ操作
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 実装戦略
|
||||||
|
|
||||||
|
### 1. HandleRegistry活用
|
||||||
|
```rust
|
||||||
|
// 既存のHandleRegistry(80%実装済み)を拡張
|
||||||
|
pub fn jit_get_box_method(handle: u64, method: &str) -> Option<MethodPtr> {
|
||||||
|
// ハンドル → Box → メソッドポインタ
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. HostCall拡張
|
||||||
|
```rust
|
||||||
|
// 現在の限定的なHostCallを段階的に拡張
|
||||||
|
enum HostCallKind {
|
||||||
|
// 既存
|
||||||
|
ArrayIsEmpty,
|
||||||
|
StringLength,
|
||||||
|
|
||||||
|
// Phase 10.9で追加
|
||||||
|
StringIsEmpty,
|
||||||
|
StringCharAt,
|
||||||
|
ArrayGet,
|
||||||
|
IntToFloat,
|
||||||
|
FloatToInt,
|
||||||
|
|
||||||
|
// new演算子サポート
|
||||||
|
NewStringBox,
|
||||||
|
NewIntegerBox,
|
||||||
|
NewArrayBox,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 型安全性の確保
|
||||||
|
```rust
|
||||||
|
// JIT時の型チェック
|
||||||
|
match method {
|
||||||
|
"length" => {
|
||||||
|
// StringBox/ArrayBoxのみ許可
|
||||||
|
verify_box_type(handle, &[BoxType::String, BoxType::Array])?
|
||||||
|
}
|
||||||
|
"isEmpty" => {
|
||||||
|
// より多くのBoxで使用可能
|
||||||
|
verify_box_type(handle, &[BoxType::String, BoxType::Array, BoxType::Map])?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 成功指標
|
||||||
|
|
||||||
|
### 機能面
|
||||||
|
- [ ] StringBox.length() がJITで実行可能
|
||||||
|
- [ ] ArrayBox.isEmpty() がJITで実行可能
|
||||||
|
- [ ] new StringBox() がJITで生成可能
|
||||||
|
- [ ] 型チェックが正しく動作
|
||||||
|
|
||||||
|
### 性能面
|
||||||
|
- [ ] HostCall経由でも10倍以上高速化
|
||||||
|
- [ ] Handle解決のオーバーヘッド最小化
|
||||||
|
- [ ] Mutex競合の回避(読み取り専用)
|
||||||
|
|
||||||
|
### Python統合への貢献
|
||||||
|
- [ ] PythonParserBoxの基本メソッドが使用可能
|
||||||
|
- [ ] MirBuilderBoxへのデータ受け渡し可能
|
||||||
|
- [ ] 最小限のPython→Nyash変換が動作
|
||||||
|
|
||||||
|
## 🚧 技術的課題
|
||||||
|
|
||||||
|
### 1. Arc<Mutex>パターンとの整合性
|
||||||
|
```rust
|
||||||
|
// 読み取り専用でもMutexロックが必要?
|
||||||
|
// → 読み取り専用APIを別途用意?
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Box生成時のメモリ管理
|
||||||
|
```rust
|
||||||
|
// JIT内でのArc生成
|
||||||
|
// → HandleRegistryで一元管理
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. エラーハンドリング
|
||||||
|
```rust
|
||||||
|
// パニックしない設計
|
||||||
|
// → Result型での丁寧なエラー伝播
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📈 実装ロードマップ
|
||||||
|
|
||||||
|
### Week 1:基盤整備
|
||||||
|
- HandleRegistry拡張
|
||||||
|
- HostCallインターフェース設計
|
||||||
|
- 型チェック機構
|
||||||
|
|
||||||
|
### Week 2:読み取りメソッド実装
|
||||||
|
- StringBox:length, isEmpty, charAt
|
||||||
|
- ArrayBox:length, isEmpty, get
|
||||||
|
- 数値変換:toInt, toFloat
|
||||||
|
|
||||||
|
### Week 3:Box生成サポート
|
||||||
|
- new演算子のMIR→JIT変換
|
||||||
|
- コンストラクタ呼び出し
|
||||||
|
- HandleRegistry登録
|
||||||
|
|
||||||
|
### Week 4:テストと最適化
|
||||||
|
- E2Eテストスイート
|
||||||
|
- パフォーマンス測定
|
||||||
|
- Python統合の動作確認
|
||||||
|
|
||||||
|
## 🎉 期待される成果
|
||||||
|
|
||||||
|
```nyash
|
||||||
|
// これが高速に動く!
|
||||||
|
static box FastPython {
|
||||||
|
main() {
|
||||||
|
local py = new PythonParserBox() // JITで生成!
|
||||||
|
local code = "def add(a, b): return a + b"
|
||||||
|
local ast = py.parse(code) // JITで実行!
|
||||||
|
|
||||||
|
local builder = new MirBuilderBox() // JITで生成!
|
||||||
|
local mir = builder.build(ast) // JITで実行!
|
||||||
|
|
||||||
|
// Python関数がネイティブ速度で動く!
|
||||||
|
return "Python is now Native!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 次のステップ
|
||||||
|
|
||||||
|
→ Phase 10.10:プラグインBox JITサポート
|
||||||
|
→ Phase 10.1:Python統合(いよいよ実現!)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
作成者:Claude(Nyashくんの要望により)
|
||||||
|
目的:「うるさい、Nyashつかえ」を真に実現するため
|
||||||
76
docs/reference/jit/jit_stats_json_v1.md
Normal file
76
docs/reference/jit/jit_stats_json_v1.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
JIT Stats JSON Schema — Version 1
|
||||||
|
=================================
|
||||||
|
|
||||||
|
This document describes the fields emitted in the JIT statistics JSON outputs (version=1).
|
||||||
|
|
||||||
|
Sources
|
||||||
|
- Unified JIT stats (VM-side): printed when `NYASH_JIT_STATS_JSON=1` (or CLI equivalent)
|
||||||
|
- Box API: `new JitStatsBox().toJson()` returns a compact JSON, `summary()` returns a pretty JSON summary (via VM dispatch)
|
||||||
|
|
||||||
|
Version
|
||||||
|
- `version`: number — Schema version (currently `1`)
|
||||||
|
|
||||||
|
Unified JIT Stats (JSON)
|
||||||
|
- `version`: number — Schema version (1)
|
||||||
|
- `sites`: number — Number of JIT call sites observed
|
||||||
|
- `compiled`: number — Count of functions compiled by the JIT
|
||||||
|
- `hits`: number — Sum of entries across all functions (hotness hits)
|
||||||
|
- `exec_ok`: number — Count of successful JIT executions
|
||||||
|
- `trap`: number — Count of JIT executions that trapped and fell back to VM
|
||||||
|
- `fallback_rate`: number — Ratio `trap / (exec_ok + trap)` (0 when denominator is 0)
|
||||||
|
- `handles`: number — Current number of live JIT handles in the registry
|
||||||
|
- `abi_mode`: string — `"i64_bool"` or `"b1_bool"` (when toolchain supports b1)
|
||||||
|
- `abi_b1_enabled`: boolean — Whether b1 ABI is requested by config
|
||||||
|
- `abi_b1_supported`: boolean — Whether current toolchain supports b1 in signatures
|
||||||
|
- `b1_norm_count`: number — Count of b1 normalizations (e.g., i64!=0 to b1)
|
||||||
|
- `ret_bool_hint_count`: number — Count of functions lowered with return-bool hint
|
||||||
|
- `phi_total_slots`: number — Total PHI slots encountered (always-on, LowerCore-based)
|
||||||
|
- `phi_b1_slots`: number — PHI slots classified as boolean (heuristics)
|
||||||
|
- `top5`: array of objects — Top hot functions
|
||||||
|
- `name`: string
|
||||||
|
- `hits`: number
|
||||||
|
- `compiled`: boolean
|
||||||
|
- `handle`: number (0 when not compiled)
|
||||||
|
|
||||||
|
Compact Box JSON — `JitStatsBox.toJson()`
|
||||||
|
- `version`: number — Schema version (1)
|
||||||
|
- `abi_mode`: string — Current ABI mode for booleans (see above)
|
||||||
|
- `abi_b1_enabled`: boolean — Whether b1 ABI is requested by config
|
||||||
|
- `abi_b1_supported`: boolean — Whether current toolchain supports b1 in signatures
|
||||||
|
- `b1_norm_count`: number — b1 normalizations count
|
||||||
|
- `ret_bool_hint_count`: number — return-bool hint count
|
||||||
|
- `phi_total_slots`: number — Total PHI slots (accumulated)
|
||||||
|
- `phi_b1_slots`: number — Boolean PHI slots (accumulated)
|
||||||
|
|
||||||
|
Summary Box JSON — `JitStatsBox.summary()`
|
||||||
|
- Pretty-printed JSON containing:
|
||||||
|
- All fields from `toJson()`
|
||||||
|
- `top5`: same structure as unified stats
|
||||||
|
|
||||||
|
Notes
|
||||||
|
- Counters reflect process-lifetime totals (reset only on process start).
|
||||||
|
- `phi_*` counters are accumulated per LowerCore invocation (function lower), independent of dump flags.
|
||||||
|
- `abi_mode` switches to `b1_bool` when toolchain support is detected and b1 ABI is enabled.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"sites": 1,
|
||||||
|
"compiled": 0,
|
||||||
|
"hits": 1,
|
||||||
|
"exec_ok": 0,
|
||||||
|
"trap": 0,
|
||||||
|
"fallback_rate": 0.0,
|
||||||
|
"handles": 0,
|
||||||
|
"abi_mode": "i64_bool",
|
||||||
|
"abi_b1_enabled": false,
|
||||||
|
"abi_b1_supported": false,
|
||||||
|
"b1_norm_count": 0,
|
||||||
|
"ret_bool_hint_count": 0,
|
||||||
|
"phi_total_slots": 2,
|
||||||
|
"phi_b1_slots": 1,
|
||||||
|
"top5": [ { "name": "main", "hits": 1, "compiled": false, "handle": 0 } ]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
13
examples/jit_array_is_empty.nyash
Normal file
13
examples/jit_array_is_empty.nyash
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Param-array JIT HostCall PoC (isEmpty)
|
||||||
|
box Utils {
|
||||||
|
isEmpty(a) {
|
||||||
|
return a.isEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a = new ArrayBox()
|
||||||
|
u = new Utils()
|
||||||
|
print(u.isEmpty(a))
|
||||||
|
a.push(1)
|
||||||
|
print(u.isEmpty(a))
|
||||||
|
|
||||||
13
examples/jit_compare_i64_boolret.nyash
Normal file
13
examples/jit_compare_i64_boolret.nyash
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// i64 compare returning boolean directly (ret_bool hint)
|
||||||
|
// Enable: NYASH_JIT_THRESHOLD=1 NYASH_JIT_DUMP=1
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local a, b
|
||||||
|
a = 10
|
||||||
|
b = 4
|
||||||
|
// return boolean result directly
|
||||||
|
return a > b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
13
examples/jit_config_demo.nyash
Normal file
13
examples/jit_config_demo.nyash
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// JitConfigBox demo
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local jc
|
||||||
|
jc = new JitConfigBox()
|
||||||
|
jc.enable("exec")
|
||||||
|
jc.setThreshold(1)
|
||||||
|
jc.enable("phi_min")
|
||||||
|
jc.apply()
|
||||||
|
print(jc.summary())
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
19
examples/jit_copy_const_cast.nyash
Normal file
19
examples/jit_copy_const_cast.nyash
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Copy/Const/Cast coverage demo
|
||||||
|
// Use with: NYASH_JIT_THRESHOLD=1 NYASH_JIT_DUMP=1
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local a, b, f
|
||||||
|
a = 42
|
||||||
|
b = a // Copy
|
||||||
|
f = 100.0
|
||||||
|
// Mixed compare (int vs float) to exercise promote/cast path in optimizer
|
||||||
|
if (b < f) {
|
||||||
|
// Return boolean directly
|
||||||
|
return b < f
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
14
examples/jit_direct_bool_ret.nyash
Normal file
14
examples/jit_direct_bool_ret.nyash
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// jit-direct: boolean return normalization
|
||||||
|
// Build: cargo build --release --features cranelift-jit
|
||||||
|
// Run: NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 \
|
||||||
|
// ./target/release/nyash --jit-direct examples/jit_direct_bool_ret.nyash
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local a, b
|
||||||
|
a = 3
|
||||||
|
b = 5
|
||||||
|
return a < b // expect true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
15
examples/jit_direct_f64_ret.nyash
Normal file
15
examples/jit_direct_f64_ret.nyash
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// jit-direct: f64 return demo
|
||||||
|
// Build: cargo build --release --features cranelift-jit
|
||||||
|
// Run: NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_NATIVE_F64=1 \
|
||||||
|
// ./target/release/nyash --jit-direct examples/jit_direct_f64_ret.nyash
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local x, y, s
|
||||||
|
x = 1.5
|
||||||
|
y = 2.25
|
||||||
|
s = x + y
|
||||||
|
return s // expect 3.75
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
15
examples/jit_direct_local_store_load.nyash
Normal file
15
examples/jit_direct_local_store_load.nyash
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// jit-direct: minimal local Store/Load path
|
||||||
|
// Build: cargo build --release --features cranelift-jit
|
||||||
|
// Run: NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 \
|
||||||
|
// ./target/release/nyash --jit-direct examples/jit_direct_local_store_load.nyash
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local x, y
|
||||||
|
x = 1
|
||||||
|
y = 2
|
||||||
|
x = x + y // store/load around add
|
||||||
|
return x // expect 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
12
examples/jit_f64_arith.nyash
Normal file
12
examples/jit_f64_arith.nyash
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// JIT native f64 arithmetic demo
|
||||||
|
// Enable: NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_NATIVE_F64=1
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local x, y
|
||||||
|
x = 1.5
|
||||||
|
y = 2.25
|
||||||
|
return x + y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
19
examples/jit_f64_e2e_add_compare.nyash
Normal file
19
examples/jit_f64_e2e_add_compare.nyash
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// f64 E2E demo (Cranelift JIT)
|
||||||
|
// Build: cargo build --release --features cranelift-jit
|
||||||
|
// Run: NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_NATIVE_F64=1 \
|
||||||
|
// ./target/release/nyash --backend vm examples/jit_f64_e2e_add_compare.nyash
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local x, y
|
||||||
|
x = 1.5
|
||||||
|
y = 2.25
|
||||||
|
// 1) return f64 add
|
||||||
|
local s
|
||||||
|
s = x + y
|
||||||
|
print("sum=" + s)
|
||||||
|
// 2) return f64 compare as boolean
|
||||||
|
return s > 3.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
14
examples/jit_i64_binops.nyash
Normal file
14
examples/jit_i64_binops.nyash
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// i64 BinOp coverage demo (Add/Sub/Mul/Div/Mod)
|
||||||
|
// Enable: NYASH_JIT_THRESHOLD=1 NYASH_JIT_DUMP=1
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local a, b, r
|
||||||
|
a = 7
|
||||||
|
b = 3
|
||||||
|
r = a + b
|
||||||
|
r = r - (a * b)
|
||||||
|
r = r + (a / b)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
}
|
||||||
12
examples/jit_map_has_int_keys.nyash
Normal file
12
examples/jit_map_has_int_keys.nyash
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Param-map JIT HostCall PoC (has)
|
||||||
|
box Utils {
|
||||||
|
has12(m) {
|
||||||
|
return m.has(1) + m.has(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m = new MapBox()
|
||||||
|
m.set(1, 10)
|
||||||
|
u = new Utils()
|
||||||
|
print(u.has12(m))
|
||||||
|
|
||||||
14
examples/jit_map_int_keys_param_call.nyash
Normal file
14
examples/jit_map_int_keys_param_call.nyash
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Param-map JIT HostCall PoC with integer keys
|
||||||
|
box Utils {
|
||||||
|
sumKeys(m) {
|
||||||
|
// get(1) + get(2)
|
||||||
|
return m.get(1) + m.get(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m = new MapBox()
|
||||||
|
m.set(1, 10)
|
||||||
|
m.set(2, 20)
|
||||||
|
u = new Utils()
|
||||||
|
print(u.sumKeys(m))
|
||||||
|
|
||||||
12
examples/jit_mixed_f64_compare.nyash
Normal file
12
examples/jit_mixed_f64_compare.nyash
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Mixed f64 compare demo (int vs float)
|
||||||
|
// Enable: NYASH_JIT_THRESHOLD=1 NYASH_JIT_NATIVE_F64=1
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local a, b
|
||||||
|
a = 1.5 + 2.5
|
||||||
|
b = 3.5
|
||||||
|
// f64 compare
|
||||||
|
return a > b
|
||||||
|
}
|
||||||
|
}
|
||||||
19
examples/jit_multi_phi_demo.nyash
Normal file
19
examples/jit_multi_phi_demo.nyash
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Multi-PHI diamond demo
|
||||||
|
// Enable: NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_PHI_MIN=1
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local a, b, x, y
|
||||||
|
a = 2
|
||||||
|
b = 5
|
||||||
|
if (a < b) {
|
||||||
|
x = a + 1
|
||||||
|
y = b + 2
|
||||||
|
} else {
|
||||||
|
x = a + 3
|
||||||
|
y = b + 4
|
||||||
|
}
|
||||||
|
return x + y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
24
examples/jit_stats_bool_ret.nyash
Normal file
24
examples/jit_stats_bool_ret.nyash
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// JIT stats + boolean return hint demo
|
||||||
|
// Enable: NYASH_JIT_THRESHOLD=1 NYASH_JIT_DUMP=1
|
||||||
|
// Optional: NYASH_JIT_EXEC=1 (Cranelift未有効でもVM結果は不変)
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local a, b, c, s, r
|
||||||
|
a = 3
|
||||||
|
b = 5
|
||||||
|
// Force boolean value via branch and PHI-like merge
|
||||||
|
if (a < b) {
|
||||||
|
c = true
|
||||||
|
} else {
|
||||||
|
c = false
|
||||||
|
}
|
||||||
|
r = c
|
||||||
|
// Observe JIT runtime stats (JSON string)
|
||||||
|
s = new JitStatsBox()
|
||||||
|
print(s.toString())
|
||||||
|
// Return boolean directly (exercise ret_bool_hint)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
13
examples/jit_stats_summary_demo.nyash
Normal file
13
examples/jit_stats_summary_demo.nyash
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// JitStatsBox.summary demo (pretty JSON)
|
||||||
|
// Enable: NYASH_JIT_STATS=1 NYASH_JIT_THRESHOLD=1
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local s
|
||||||
|
s = new JitStatsBox()
|
||||||
|
print(s.summary())
|
||||||
|
// Also show per-function stats JSON array
|
||||||
|
print(s.perFunction())
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
9
examples/jit_stats_tojson.nyash
Normal file
9
examples/jit_stats_tojson.nyash
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// print JitStatsBox.toJson()
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local s
|
||||||
|
s = new JitStatsBox()
|
||||||
|
print(s.toJson())
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
12
examples/jit_stats_top5.nyash
Normal file
12
examples/jit_stats_top5.nyash
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// JitStatsBox.top5 demo (JSON array)
|
||||||
|
// Enable: NYASH_JIT_STATS=1 NYASH_JIT_THRESHOLD=1
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local s
|
||||||
|
s = new JitStatsBox()
|
||||||
|
print(s.top5())
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
13
examples/jit_string_charcode_at.nyash
Normal file
13
examples/jit_string_charcode_at.nyash
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Param-string JIT HostCall PoC (charCodeAt)
|
||||||
|
box Utils {
|
||||||
|
codeAt(s, i) {
|
||||||
|
return s.charCodeAt(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u = new Utils()
|
||||||
|
print(u.codeAt("ABC", 0))
|
||||||
|
print(u.codeAt("ABC", 1))
|
||||||
|
print(u.codeAt("ABC", 2))
|
||||||
|
print(u.codeAt("ABC", 3)) // -1 (out of range)
|
||||||
|
|
||||||
11
examples/jit_string_is_empty.nyash
Normal file
11
examples/jit_string_is_empty.nyash
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// Param-string JIT HostCall PoC (isEmpty)
|
||||||
|
box Utils {
|
||||||
|
isEmpty(s) {
|
||||||
|
return s.isEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u = new Utils()
|
||||||
|
print(u.isEmpty(""))
|
||||||
|
print(u.isEmpty("nyash"))
|
||||||
|
|
||||||
11
examples/jit_string_param_length.nyash
Normal file
11
examples/jit_string_param_length.nyash
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// Param-string JIT HostCall PoC (length)
|
||||||
|
box Utils {
|
||||||
|
strlen(s) {
|
||||||
|
return s.length()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s = "Hello, Nyash!"
|
||||||
|
u = new Utils()
|
||||||
|
print(u.strlen(s))
|
||||||
|
|
||||||
16
examples/mix_num_bool_promote.nyash
Normal file
16
examples/mix_num_bool_promote.nyash
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Mixed numeric/boolean promote demo
|
||||||
|
// Enable: NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local a, f
|
||||||
|
a = 7
|
||||||
|
f = 7.5
|
||||||
|
// mixed compare (i64 < f64) should promote to f64 compare and yield b1
|
||||||
|
if (a < f) {
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
examples/ny_bench_f64.nyash
Normal file
53
examples/ny_bench_f64.nyash
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// f64 micro benchmark (Cranelift JIT)
|
||||||
|
// Requirements:
|
||||||
|
// - Build with: cargo build --release --features cranelift-jit
|
||||||
|
// - Run with: NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_NATIVE_F64=1 \
|
||||||
|
// ./target/release/nyash --backend vm examples/ny_bench_f64.nyash
|
||||||
|
// Notes:
|
||||||
|
// - VM backend alone may not support f64 BinOp/Compare; use JIT as above.
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local ITER, timer
|
||||||
|
ITER = 200000
|
||||||
|
timer = new TimerBox()
|
||||||
|
|
||||||
|
print("\n=== f64 Micro Bench (ITER=" + ITER + ") ===")
|
||||||
|
|
||||||
|
// 1) f64 add/mul loop
|
||||||
|
local i, x, y, z, t0, t1, ms, ops
|
||||||
|
i = 0
|
||||||
|
x = 1.5
|
||||||
|
y = 2.25
|
||||||
|
t0 = timer.now()
|
||||||
|
loop(i < ITER) {
|
||||||
|
z = x + y
|
||||||
|
x = z * 1.000001
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
t1 = timer.now()
|
||||||
|
ms = t1 - t0
|
||||||
|
ops = (ITER * 1000.0) / ms
|
||||||
|
print("[f64_addmul] elapsed_ms=" + ms + ", ops/sec=" + ops)
|
||||||
|
|
||||||
|
// 2) f64 compare loop
|
||||||
|
local c
|
||||||
|
i = 0
|
||||||
|
x = 10.0
|
||||||
|
y = 9.5
|
||||||
|
t0 = timer.now()
|
||||||
|
loop(i < ITER) {
|
||||||
|
c = x > y
|
||||||
|
y = y + 0.000001
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
t1 = timer.now()
|
||||||
|
ms = t1 - t0
|
||||||
|
ops = (ITER * 1000.0) / ms
|
||||||
|
print("[f64_compare] elapsed_ms=" + ms + ", ops/sec=" + ops)
|
||||||
|
|
||||||
|
print("\nDone (f64).")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
23
examples/phi_bool_merge.nyash
Normal file
23
examples/phi_bool_merge.nyash
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Boolean PHI merge demo (b1 internal path)
|
||||||
|
// Enable: NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_PHI_MIN=1
|
||||||
|
// Optional DOT: NYASH_JIT_DOT=tmp/phi_bool.dot
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local a, b, c
|
||||||
|
a = 3
|
||||||
|
b = 5
|
||||||
|
// Create a boolean via branch to force PHI merge of boolean
|
||||||
|
if (a < b) {
|
||||||
|
c = true
|
||||||
|
} else {
|
||||||
|
c = false
|
||||||
|
}
|
||||||
|
// Use merged boolean
|
||||||
|
if (c) {
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
examples/phi_bool_tag_demo.nyash
Normal file
18
examples/phi_bool_tag_demo.nyash
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// PHI with boolean inputs demo (shows (b1) tag)
|
||||||
|
// Enable: NYASH_JIT_PHI_MIN=1 NYASH_JIT_DUMP=1
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local a, b, x
|
||||||
|
a = 1
|
||||||
|
b = 2
|
||||||
|
if (a < b) {
|
||||||
|
x = (3 < 4)
|
||||||
|
} else {
|
||||||
|
x = (5 < 6)
|
||||||
|
}
|
||||||
|
// Use x so it stays live
|
||||||
|
if (x) { return 1 } else { return 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
12
examples/ret_bool_demo.nyash
Normal file
12
examples/ret_bool_demo.nyash
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Return boolean demo
|
||||||
|
// Enable: NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local a, b
|
||||||
|
a = 3
|
||||||
|
b = 5
|
||||||
|
// Return result of comparison directly (boolean)
|
||||||
|
return a < b
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -454,13 +454,15 @@ impl VM {
|
|||||||
|
|
||||||
// Optional: print VM stats
|
// Optional: print VM stats
|
||||||
self.maybe_print_stats();
|
self.maybe_print_stats();
|
||||||
|
// Optional: print concise JIT unified stats
|
||||||
|
self.maybe_print_jit_unified_stats();
|
||||||
|
|
||||||
// Optional: print cache stats summary
|
// Optional: print cache stats summary
|
||||||
if std::env::var("NYASH_VM_PIC_STATS").ok().as_deref() == Some("1") {
|
if std::env::var("NYASH_VM_PIC_STATS").ok().as_deref() == Some("1") {
|
||||||
self.print_cache_stats_summary();
|
self.print_cache_stats_summary();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional: print JIT stats summary (Phase 10_a)
|
// Optional: print JIT detailed summary (top functions)
|
||||||
if let Some(jm) = &self.jit_manager { jm.print_summary(); }
|
if let Some(jm) = &self.jit_manager { jm.print_summary(); }
|
||||||
|
|
||||||
// Optional: GC diagnostics if enabled
|
// Optional: GC diagnostics if enabled
|
||||||
@ -560,6 +562,8 @@ impl VM {
|
|||||||
jm.record_entry(&function.signature.name);
|
jm.record_entry(&function.signature.name);
|
||||||
// Try compile if hot (no-op for now, returns fake handle)
|
// Try compile if hot (no-op for now, returns fake handle)
|
||||||
let _ = jm.maybe_compile(&function.signature.name, function);
|
let _ = jm.maybe_compile(&function.signature.name, function);
|
||||||
|
// Record per-function lower stats captured during last JIT lower (if any)
|
||||||
|
// Note: The current engine encapsulates its LowerCore; expose via last_stats on a new instance as needed.
|
||||||
if jm.is_compiled(&function.signature.name) && std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
|
if jm.is_compiled(&function.signature.name) && std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
|
||||||
if let Some(h) = jm.handle_of(&function.signature.name) {
|
if let Some(h) = jm.handle_of(&function.signature.name) {
|
||||||
eprintln!("[JIT] dispatch would go to handle={} for {} (stub)", h, function.signature.name);
|
eprintln!("[JIT] dispatch would go to handle={} for {} (stub)", h, function.signature.name);
|
||||||
@ -581,12 +585,13 @@ impl VM {
|
|||||||
.filter_map(|pid| self.get_value(*pid).ok())
|
.filter_map(|pid| self.get_value(*pid).ok())
|
||||||
.collect();
|
.collect();
|
||||||
if std::env::var("NYASH_JIT_EXEC").ok().as_deref() == Some("1") {
|
if std::env::var("NYASH_JIT_EXEC").ok().as_deref() == Some("1") {
|
||||||
|
let jit_only = std::env::var("NYASH_JIT_ONLY").ok().as_deref() == Some("1");
|
||||||
// Root regionize args for JIT call
|
// Root regionize args for JIT call
|
||||||
self.enter_root_region();
|
self.enter_root_region();
|
||||||
self.pin_roots(args_vec.iter());
|
self.pin_roots(args_vec.iter());
|
||||||
if let Some(jm_ref) = self.jit_manager.as_ref() {
|
if let Some(jm_mut) = self.jit_manager.as_mut() {
|
||||||
if jm_ref.is_compiled(&function.signature.name) {
|
if jm_mut.is_compiled(&function.signature.name) {
|
||||||
if let Some(val) = jm_ref.execute_compiled(&function.signature.name, &args_vec) {
|
if let Some(val) = jm_mut.execute_compiled(&function.signature.name, &args_vec) {
|
||||||
// Exit scope before returning
|
// Exit scope before returning
|
||||||
self.leave_root_region();
|
self.leave_root_region();
|
||||||
self.scope_tracker.pop_scope();
|
self.scope_tracker.pop_scope();
|
||||||
@ -594,6 +599,29 @@ impl VM {
|
|||||||
} else if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") ||
|
} else if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") ||
|
||||||
std::env::var("NYASH_JIT_TRAP_LOG").ok().as_deref() == Some("1") {
|
std::env::var("NYASH_JIT_TRAP_LOG").ok().as_deref() == Some("1") {
|
||||||
eprintln!("[JIT] fallback: VM path taken for {}", function.signature.name);
|
eprintln!("[JIT] fallback: VM path taken for {}", function.signature.name);
|
||||||
|
if jit_only {
|
||||||
|
self.leave_root_region();
|
||||||
|
self.scope_tracker.pop_scope();
|
||||||
|
return Err(VMError::InvalidInstruction(format!("JIT-only enabled and JIT trap occurred for {}", function.signature.name)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if jit_only {
|
||||||
|
// Try to compile now and execute; if not possible, error out
|
||||||
|
let _ = jm_mut.maybe_compile(&function.signature.name, function);
|
||||||
|
if jm_mut.is_compiled(&function.signature.name) {
|
||||||
|
if let Some(val) = jm_mut.execute_compiled(&function.signature.name, &args_vec) {
|
||||||
|
self.leave_root_region();
|
||||||
|
self.scope_tracker.pop_scope();
|
||||||
|
return Ok(val);
|
||||||
|
} else {
|
||||||
|
self.leave_root_region();
|
||||||
|
self.scope_tracker.pop_scope();
|
||||||
|
return Err(VMError::InvalidInstruction(format!("JIT-only enabled and JIT execution failed for {}", function.signature.name)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.leave_root_region();
|
||||||
|
self.scope_tracker.pop_scope();
|
||||||
|
return Err(VMError::InvalidInstruction(format!("JIT-only enabled but function not compiled: {}", function.signature.name)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -802,6 +830,77 @@ impl VM {
|
|||||||
|
|
||||||
// Legacy box_trait::ResultBox is no longer handled here (migration complete)
|
// Legacy box_trait::ResultBox is no longer handled here (migration complete)
|
||||||
|
|
||||||
|
// JitStatsBox methods (process-local JIT counters)
|
||||||
|
if let Some(_jsb) = box_value.as_any().downcast_ref::<crate::boxes::jit_stats_box::JitStatsBox>() {
|
||||||
|
match method {
|
||||||
|
"toJson" | "toJSON" => {
|
||||||
|
return Ok(crate::boxes::jit_stats_box::JitStatsBox::new().to_json());
|
||||||
|
}
|
||||||
|
// Return detailed per-function stats as JSON array
|
||||||
|
// Each item: { name, phi_total, phi_b1, ret_bool_hint, hits, compiled, handle }
|
||||||
|
"perFunction" | "per_function" => {
|
||||||
|
if let Some(jm) = &self.jit_manager {
|
||||||
|
let v = jm.per_function_stats();
|
||||||
|
let arr: Vec<serde_json::Value> = v.into_iter().map(|(name, phi_t, phi_b1, rb, hits, compiled, handle)| {
|
||||||
|
serde_json::json!({
|
||||||
|
"name": name,
|
||||||
|
"phi_total": phi_t,
|
||||||
|
"phi_b1": phi_b1,
|
||||||
|
"ret_bool_hint": rb,
|
||||||
|
"hits": hits,
|
||||||
|
"compiled": compiled,
|
||||||
|
"handle": handle
|
||||||
|
})
|
||||||
|
}).collect();
|
||||||
|
let s = serde_json::to_string(&arr).unwrap_or_else(|_| "[]".to_string());
|
||||||
|
return Ok(Box::new(crate::box_trait::StringBox::new(s)));
|
||||||
|
}
|
||||||
|
return Ok(Box::new(crate::box_trait::StringBox::new("[]")));
|
||||||
|
}
|
||||||
|
"top5" => {
|
||||||
|
if let Some(jm) = &self.jit_manager {
|
||||||
|
let v = jm.top_hits(5);
|
||||||
|
let arr: Vec<serde_json::Value> = v.into_iter().map(|(name, hits, compiled, handle)| {
|
||||||
|
serde_json::json!({
|
||||||
|
"name": name,
|
||||||
|
"hits": hits,
|
||||||
|
"compiled": compiled,
|
||||||
|
"handle": handle
|
||||||
|
})
|
||||||
|
}).collect();
|
||||||
|
let s = serde_json::to_string(&arr).unwrap_or_else(|_| "[]".to_string());
|
||||||
|
return Ok(Box::new(crate::box_trait::StringBox::new(s)));
|
||||||
|
}
|
||||||
|
return Ok(Box::new(crate::box_trait::StringBox::new("[]")));
|
||||||
|
}
|
||||||
|
"summary" => {
|
||||||
|
let cfg = crate::jit::config::current();
|
||||||
|
let caps = crate::jit::config::probe_capabilities();
|
||||||
|
let abi_mode = if cfg.native_bool_abi && caps.supports_b1_sig { "b1_bool" } else { "i64_bool" };
|
||||||
|
let b1_norm = crate::jit::rt::b1_norm_get();
|
||||||
|
let ret_b1 = crate::jit::rt::ret_bool_hint_get();
|
||||||
|
let mut payload = serde_json::json!({
|
||||||
|
"abi_mode": abi_mode,
|
||||||
|
"abi_b1_enabled": cfg.native_bool_abi,
|
||||||
|
"abi_b1_supported": caps.supports_b1_sig,
|
||||||
|
"b1_norm_count": b1_norm,
|
||||||
|
"ret_bool_hint_count": ret_b1,
|
||||||
|
"top5": []
|
||||||
|
});
|
||||||
|
if let Some(jm) = &self.jit_manager {
|
||||||
|
let v = jm.top_hits(5);
|
||||||
|
let top5: Vec<serde_json::Value> = v.into_iter().map(|(name, hits, compiled, handle)| serde_json::json!({
|
||||||
|
"name": name, "hits": hits, "compiled": compiled, "handle": handle
|
||||||
|
})).collect();
|
||||||
|
if let Some(obj) = payload.as_object_mut() { obj.insert("top5".to_string(), serde_json::Value::Array(top5)); }
|
||||||
|
}
|
||||||
|
let s = serde_json::to_string_pretty(&payload).unwrap_or_else(|_| "{}".to_string());
|
||||||
|
return Ok(Box::new(crate::box_trait::StringBox::new(s)));
|
||||||
|
}
|
||||||
|
_ => return Ok(Box::new(crate::box_trait::VoidBox::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// StringBox methods
|
// StringBox methods
|
||||||
if let Some(string_box) = box_value.as_any().downcast_ref::<StringBox>() {
|
if let Some(string_box) = box_value.as_any().downcast_ref::<StringBox>() {
|
||||||
match method {
|
match method {
|
||||||
|
|||||||
@ -30,6 +30,119 @@ impl VM {
|
|||||||
return Ok(Box::new(StringBox::new(box_value.to_string_box().value)));
|
return Ok(Box::new(StringBox::new(box_value.to_string_box().value)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JitConfigBox methods
|
||||||
|
if let Some(jcb) = box_value.as_any().downcast_ref::<crate::boxes::jit_config_box::JitConfigBox>() {
|
||||||
|
match method {
|
||||||
|
"get" => {
|
||||||
|
if let Some(k) = _args.get(0) { return Ok(jcb.get_flag(&k.to_string_box().value).unwrap_or_else(|e| Box::new(StringBox::new(e.to_string())))); }
|
||||||
|
return Ok(Box::new(StringBox::new("get(name) requires 1 arg")));
|
||||||
|
}
|
||||||
|
"set" => {
|
||||||
|
if _args.len() >= 2 {
|
||||||
|
let k = _args[0].to_string_box().value;
|
||||||
|
let v = _args[1].to_string_box().value;
|
||||||
|
let on = matches!(v.as_str(), "1" | "true" | "True" | "on" | "ON");
|
||||||
|
return Ok(jcb.set_flag(&k, on).unwrap_or_else(|e| Box::new(StringBox::new(e.to_string()))));
|
||||||
|
}
|
||||||
|
return Ok(Box::new(StringBox::new("set(name, bool) requires 2 args")));
|
||||||
|
}
|
||||||
|
"getThreshold" => { return Ok(jcb.get_threshold()); }
|
||||||
|
"setThreshold" => {
|
||||||
|
if let Some(v) = _args.get(0) {
|
||||||
|
let iv = v.to_string_box().value.parse::<i64>().unwrap_or(0);
|
||||||
|
return Ok(jcb.set_threshold(iv).unwrap_or_else(|e| Box::new(StringBox::new(e.to_string()))));
|
||||||
|
}
|
||||||
|
return Ok(Box::new(StringBox::new("setThreshold(n) requires 1 arg")));
|
||||||
|
}
|
||||||
|
"apply" => { return Ok(jcb.apply()); }
|
||||||
|
"toJson" => { return Ok(jcb.to_json()); }
|
||||||
|
"fromJson" => {
|
||||||
|
if let Some(s) = _args.get(0) { return Ok(jcb.from_json(&s.to_string_box().value).unwrap_or_else(|e| Box::new(StringBox::new(e.to_string())))); }
|
||||||
|
return Ok(Box::new(StringBox::new("fromJson(json) requires 1 arg")));
|
||||||
|
}
|
||||||
|
"enable" => {
|
||||||
|
if let Some(k) = _args.get(0) { return Ok(jcb.set_flag(&k.to_string_box().value, true).unwrap_or_else(|e| Box::new(StringBox::new(e.to_string())))); }
|
||||||
|
return Ok(Box::new(StringBox::new("enable(name) requires 1 arg")));
|
||||||
|
}
|
||||||
|
"disable" => {
|
||||||
|
if let Some(k) = _args.get(0) { return Ok(jcb.set_flag(&k.to_string_box().value, false).unwrap_or_else(|e| Box::new(StringBox::new(e.to_string())))); }
|
||||||
|
return Ok(Box::new(StringBox::new("disable(name) requires 1 arg")));
|
||||||
|
}
|
||||||
|
"summary" => { return Ok(jcb.summary()); }
|
||||||
|
_ => { return Ok(Box::new(VoidBox::new())); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JitStatsBox methods
|
||||||
|
if let Some(jsb) = box_value.as_any().downcast_ref::<crate::boxes::jit_stats_box::JitStatsBox>() {
|
||||||
|
match method {
|
||||||
|
"toJson" => { return Ok(jsb.to_json()); }
|
||||||
|
"top5" => {
|
||||||
|
if let Some(jm) = &self.jit_manager {
|
||||||
|
let v = jm.top_hits(5);
|
||||||
|
let arr: Vec<serde_json::Value> = v.into_iter().map(|(name, hits, compiled, handle)| {
|
||||||
|
serde_json::json!({
|
||||||
|
"name": name,
|
||||||
|
"hits": hits,
|
||||||
|
"compiled": compiled,
|
||||||
|
"handle": handle
|
||||||
|
})
|
||||||
|
}).collect();
|
||||||
|
let s = serde_json::to_string(&arr).unwrap_or_else(|_| "[]".to_string());
|
||||||
|
return Ok(Box::new(StringBox::new(s)));
|
||||||
|
}
|
||||||
|
return Ok(Box::new(StringBox::new("[]")));
|
||||||
|
}
|
||||||
|
"summary" => {
|
||||||
|
let cfg = crate::jit::config::current();
|
||||||
|
let caps = crate::jit::config::probe_capabilities();
|
||||||
|
let abi_mode = if cfg.native_bool_abi && caps.supports_b1_sig { "b1_bool" } else { "i64_bool" };
|
||||||
|
let b1_norm = crate::jit::rt::b1_norm_get();
|
||||||
|
let ret_b1 = crate::jit::rt::ret_bool_hint_get();
|
||||||
|
let mut payload = serde_json::json!({
|
||||||
|
"abi_mode": abi_mode,
|
||||||
|
"abi_b1_enabled": cfg.native_bool_abi,
|
||||||
|
"abi_b1_supported": caps.supports_b1_sig,
|
||||||
|
"b1_norm_count": b1_norm,
|
||||||
|
"ret_bool_hint_count": ret_b1,
|
||||||
|
"top5": [],
|
||||||
|
"perFunction": []
|
||||||
|
});
|
||||||
|
if let Some(jm) = &self.jit_manager {
|
||||||
|
let v = jm.top_hits(5);
|
||||||
|
let top5: Vec<serde_json::Value> = v.into_iter().map(|(name, hits, compiled, handle)| serde_json::json!({
|
||||||
|
"name": name, "hits": hits, "compiled": compiled, "handle": handle
|
||||||
|
})).collect();
|
||||||
|
let perf = jm.per_function_stats();
|
||||||
|
let per_arr: Vec<serde_json::Value> = perf.into_iter().map(|(name, phi_t, phi_b1, rb, hits, compiled, handle)| serde_json::json!({
|
||||||
|
"name": name, "phi_total": phi_t, "phi_b1": phi_b1, "ret_bool_hint": rb, "hits": hits, "compiled": compiled, "handle": handle
|
||||||
|
})).collect();
|
||||||
|
if let Some(obj) = payload.as_object_mut() { obj.insert("top5".to_string(), serde_json::Value::Array(top5)); obj.insert("perFunction".to_string(), serde_json::Value::Array(per_arr)); }
|
||||||
|
}
|
||||||
|
let s = serde_json::to_string_pretty(&payload).unwrap_or_else(|_| "{}".to_string());
|
||||||
|
return Ok(Box::new(StringBox::new(s)));
|
||||||
|
}
|
||||||
|
"perFunction" | "per_function" => {
|
||||||
|
if let Some(jm) = &self.jit_manager {
|
||||||
|
let v = jm.per_function_stats();
|
||||||
|
let arr: Vec<serde_json::Value> = v.into_iter().map(|(name, phi_t, phi_b1, rb, hits, compiled, handle)| serde_json::json!({
|
||||||
|
"name": name,
|
||||||
|
"phi_total": phi_t,
|
||||||
|
"phi_b1": phi_b1,
|
||||||
|
"ret_bool_hint": rb,
|
||||||
|
"hits": hits,
|
||||||
|
"compiled": compiled,
|
||||||
|
"handle": handle,
|
||||||
|
})).collect();
|
||||||
|
let s = serde_json::to_string_pretty(&arr).unwrap_or_else(|_| "[]".to_string());
|
||||||
|
return Ok(Box::new(StringBox::new(s)));
|
||||||
|
}
|
||||||
|
return Ok(Box::new(StringBox::new("[]")));
|
||||||
|
}
|
||||||
|
_ => { return Ok(Box::new(VoidBox::new())); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// StringBox methods
|
// StringBox methods
|
||||||
if let Some(string_box) = box_value.as_any().downcast_ref::<StringBox>() {
|
if let Some(string_box) = box_value.as_any().downcast_ref::<StringBox>() {
|
||||||
match method {
|
match method {
|
||||||
|
|||||||
@ -50,5 +50,61 @@ impl VM {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
/// Print a concise unified JIT summary alongside VM stats when enabled
|
||||||
|
pub(super) fn maybe_print_jit_unified_stats(&self) {
|
||||||
|
// Show when either JIT stats requested or VM stats are on
|
||||||
|
let jit_enabled = std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1");
|
||||||
|
let vm_enabled = std::env::var("NYASH_VM_STATS").ok().map(|v| v != "0").unwrap_or(false);
|
||||||
|
let jit_json = std::env::var("NYASH_JIT_STATS_JSON").ok().as_deref() == Some("1");
|
||||||
|
if !jit_enabled && !vm_enabled { return; }
|
||||||
|
if let Some(jm) = &self.jit_manager {
|
||||||
|
// Gather basic counters
|
||||||
|
let sites = jm.sites();
|
||||||
|
let compiled = jm.compiled_count();
|
||||||
|
let total_hits: u64 = jm.total_hits();
|
||||||
|
let ok = jm.exec_ok_count();
|
||||||
|
let tr = jm.exec_trap_count();
|
||||||
|
let total_exec = ok + tr;
|
||||||
|
let fb_rate = if total_exec > 0 { (tr as f64) / (total_exec as f64) } else { 0.0 };
|
||||||
|
let handles = crate::jit::rt::handles::len();
|
||||||
|
let cfg = crate::jit::config::current();
|
||||||
|
let caps = crate::jit::config::probe_capabilities();
|
||||||
|
let abi_mode = if cfg.native_bool_abi && caps.supports_b1_sig { "b1_bool" } else { "i64_bool" };
|
||||||
|
let b1_norm = crate::jit::rt::b1_norm_get();
|
||||||
|
let ret_b1_hints = crate::jit::rt::ret_bool_hint_get();
|
||||||
|
if jit_json {
|
||||||
|
let payload = serde_json::json!({
|
||||||
|
"version": 1,
|
||||||
|
"sites": sites,
|
||||||
|
"compiled": compiled,
|
||||||
|
"hits": total_hits,
|
||||||
|
"exec_ok": ok,
|
||||||
|
"trap": tr,
|
||||||
|
"fallback_rate": fb_rate,
|
||||||
|
"handles": handles,
|
||||||
|
"abi_mode": abi_mode,
|
||||||
|
"abi_b1_enabled": cfg.native_bool_abi,
|
||||||
|
"abi_b1_supported": caps.supports_b1_sig,
|
||||||
|
"b1_norm_count": b1_norm,
|
||||||
|
"ret_bool_hint_count": ret_b1_hints,
|
||||||
|
"phi_total_slots": crate::jit::rt::phi_total_get(),
|
||||||
|
"phi_b1_slots": crate::jit::rt::phi_b1_get(),
|
||||||
|
"top5": jm.top_hits(5).into_iter().map(|(name, hits, compiled, handle)| {
|
||||||
|
serde_json::json!({
|
||||||
|
"name": name,
|
||||||
|
"hits": hits,
|
||||||
|
"compiled": compiled,
|
||||||
|
"handle": handle
|
||||||
|
})
|
||||||
|
}).collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
println!("{}", serde_json::to_string_pretty(&payload).unwrap_or_else(|_| String::from("{}")));
|
||||||
|
} else {
|
||||||
|
eprintln!("[JIT] summary: sites={} compiled={} hits={} exec_ok={} trap={} fallback_rate={:.2} handles={}",
|
||||||
|
sites, compiled, total_hits, ok, tr, fb_rate, handles);
|
||||||
|
eprintln!(" abi_mode={} b1_norm_count={} ret_bool_hint_count={}", abi_mode, b1_norm, ret_b1_hints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -272,6 +272,26 @@ impl BuiltinBoxFactory {
|
|||||||
}
|
}
|
||||||
Ok(Box::new(DebugBox::new()))
|
Ok(Box::new(DebugBox::new()))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// JitStatsBox (runtime counters & modes)
|
||||||
|
self.register("JitStatsBox", |args| {
|
||||||
|
if !args.is_empty() {
|
||||||
|
return Err(RuntimeError::InvalidOperation {
|
||||||
|
message: format!("JitStatsBox constructor expects 0 arguments, got {}", args.len()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(Box::new(crate::boxes::jit_stats_box::JitStatsBox::new()))
|
||||||
|
});
|
||||||
|
|
||||||
|
// JitConfigBox (runtime JIT configuration as a Box)
|
||||||
|
self.register("JitConfigBox", |args| {
|
||||||
|
if !args.is_empty() {
|
||||||
|
return Err(RuntimeError::InvalidOperation {
|
||||||
|
message: format!("JitConfigBox constructor expects 0 arguments, got {}", args.len()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(Box::new(crate::boxes::jit_config_box::JitConfigBox::new()))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register I/O types
|
/// Register I/O types
|
||||||
|
|||||||
@ -33,7 +33,7 @@ pub const BUILTIN_BOXES: &[&str] = &[
|
|||||||
"SoundBox", "DebugBox", "MethodBox", "ConsoleBox",
|
"SoundBox", "DebugBox", "MethodBox", "ConsoleBox",
|
||||||
"BufferBox", "RegexBox", "JSONBox", "StreamBox",
|
"BufferBox", "RegexBox", "JSONBox", "StreamBox",
|
||||||
"HTTPClientBox", "IntentBox", "P2PBox", "SocketBox",
|
"HTTPClientBox", "IntentBox", "P2PBox", "SocketBox",
|
||||||
"HTTPServerBox", "HTTPRequestBox", "HTTPResponseBox"
|
"HTTPServerBox", "HTTPRequestBox", "HTTPResponseBox", "JitConfigBox"
|
||||||
];
|
];
|
||||||
|
|
||||||
/// 🔥 ビルトインBox判定関数 - pack透明化システムの核心
|
/// 🔥 ビルトインBox判定関数 - pack透明化システムの核心
|
||||||
|
|||||||
139
src/boxes/jit_config_box.rs
Normal file
139
src/boxes/jit_config_box.rs
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
use crate::box_trait::{NyashBox, StringBox, BoolBox, IntegerBox, VoidBox, BoxCore, BoxBase};
|
||||||
|
use crate::jit::config::JitConfig;
|
||||||
|
use crate::interpreter::RuntimeError;
|
||||||
|
use std::any::Any;
|
||||||
|
use std::sync::RwLock;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct JitConfigBox {
|
||||||
|
base: BoxBase,
|
||||||
|
pub config: RwLock<JitConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JitConfigBox {
|
||||||
|
pub fn new() -> Self { Self { base: BoxBase::new(), config: RwLock::new(JitConfig::from_env()) } }
|
||||||
|
/// Update internal config flags from runtime capability probe
|
||||||
|
pub fn from_runtime_probe(&self) -> Box<dyn NyashBox> {
|
||||||
|
let caps = crate::jit::config::probe_capabilities();
|
||||||
|
let mut cfg = self.config.write().unwrap();
|
||||||
|
if cfg.native_bool_abi && !caps.supports_b1_sig { cfg.native_bool_abi = false; }
|
||||||
|
Box::new(VoidBox::new())
|
||||||
|
}
|
||||||
|
pub fn set_flag(&self, name: &str, on: bool) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||||
|
let mut cfg = self.config.write().unwrap();
|
||||||
|
match name {
|
||||||
|
"exec" => cfg.exec = on,
|
||||||
|
"stats" => cfg.stats = on,
|
||||||
|
"stats_json" => cfg.stats_json = on,
|
||||||
|
"dump" => cfg.dump = on,
|
||||||
|
"phi_min" => cfg.phi_min = on,
|
||||||
|
"hostcall" => cfg.hostcall = on,
|
||||||
|
"handle_debug" => cfg.handle_debug = on,
|
||||||
|
"native_f64" => cfg.native_f64 = on,
|
||||||
|
"native_bool" => cfg.native_bool = on,
|
||||||
|
"bool_abi" | "native_bool_abi" => cfg.native_bool_abi = on,
|
||||||
|
"ret_b1" | "ret_bool_b1" => cfg.ret_bool_b1 = on,
|
||||||
|
_ => return Err(RuntimeError::InvalidOperation { message: format!("Unknown flag: {}", name) }),
|
||||||
|
}
|
||||||
|
Ok(Box::new(VoidBox::new()))
|
||||||
|
}
|
||||||
|
pub fn get_flag(&self, name: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||||
|
let cfg = self.config.read().unwrap();
|
||||||
|
let b = match name {
|
||||||
|
"exec" => cfg.exec,
|
||||||
|
"stats" => cfg.stats,
|
||||||
|
"stats_json" => cfg.stats_json,
|
||||||
|
"dump" => cfg.dump,
|
||||||
|
"phi_min" => cfg.phi_min,
|
||||||
|
"hostcall" => cfg.hostcall,
|
||||||
|
"handle_debug" => cfg.handle_debug,
|
||||||
|
"native_f64" => cfg.native_f64,
|
||||||
|
"native_bool" => cfg.native_bool,
|
||||||
|
"bool_abi" | "native_bool_abi" => cfg.native_bool_abi,
|
||||||
|
"ret_b1" | "ret_bool_b1" => cfg.ret_bool_b1,
|
||||||
|
_ => return Err(RuntimeError::InvalidOperation { message: format!("Unknown flag: {}", name) }),
|
||||||
|
};
|
||||||
|
Ok(Box::new(BoolBox::new(b)))
|
||||||
|
}
|
||||||
|
pub fn set_threshold(&self, v: i64) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||||
|
let mut cfg = self.config.write().unwrap();
|
||||||
|
if v <= 0 { cfg.threshold = None; }
|
||||||
|
else { cfg.threshold = Some(v as u32); }
|
||||||
|
Ok(Box::new(VoidBox::new()))
|
||||||
|
}
|
||||||
|
pub fn get_threshold(&self) -> Box<dyn NyashBox> {
|
||||||
|
let cfg = self.config.read().unwrap();
|
||||||
|
Box::new(IntegerBox::new(cfg.threshold.map(|v| v as i64).unwrap_or(0)))
|
||||||
|
}
|
||||||
|
pub fn to_json(&self) -> Box<dyn NyashBox> {
|
||||||
|
let cfg = self.config.read().unwrap();
|
||||||
|
let val = serde_json::json!({
|
||||||
|
"exec": cfg.exec,
|
||||||
|
"stats": cfg.stats,
|
||||||
|
"stats_json": cfg.stats_json,
|
||||||
|
"dump": cfg.dump,
|
||||||
|
"threshold": cfg.threshold,
|
||||||
|
"phi_min": cfg.phi_min,
|
||||||
|
"hostcall": cfg.hostcall,
|
||||||
|
"handle_debug": cfg.handle_debug,
|
||||||
|
"native_f64": cfg.native_f64,
|
||||||
|
"native_bool": cfg.native_bool,
|
||||||
|
"native_bool_abi": cfg.native_bool_abi,
|
||||||
|
"ret_bool_b1": cfg.ret_bool_b1,
|
||||||
|
});
|
||||||
|
Box::new(StringBox::new(val.to_string()))
|
||||||
|
}
|
||||||
|
pub fn from_json(&self, s: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||||
|
let mut cfg = self.config.write().unwrap();
|
||||||
|
let v: serde_json::Value = serde_json::from_str(s).map_err(|e| RuntimeError::InvalidOperation { message: format!("Invalid JSON: {}", e) })?;
|
||||||
|
if let Some(b) = v.get("exec").and_then(|x| x.as_bool()) { cfg.exec = b; }
|
||||||
|
if let Some(b) = v.get("stats").and_then(|x| x.as_bool()) { cfg.stats = b; }
|
||||||
|
if let Some(b) = v.get("stats_json").and_then(|x| x.as_bool()) { cfg.stats_json = b; }
|
||||||
|
if let Some(b) = v.get("dump").and_then(|x| x.as_bool()) { cfg.dump = b; }
|
||||||
|
if let Some(n) = v.get("threshold").and_then(|x| x.as_u64()) { cfg.threshold = Some(n as u32); }
|
||||||
|
if let Some(b) = v.get("phi_min").and_then(|x| x.as_bool()) { cfg.phi_min = b; }
|
||||||
|
if let Some(b) = v.get("hostcall").and_then(|x| x.as_bool()) { cfg.hostcall = b; }
|
||||||
|
if let Some(b) = v.get("handle_debug").and_then(|x| x.as_bool()) { cfg.handle_debug = b; }
|
||||||
|
if let Some(b) = v.get("native_f64").and_then(|x| x.as_bool()) { cfg.native_f64 = b; }
|
||||||
|
if let Some(b) = v.get("native_bool").and_then(|x| x.as_bool()) { cfg.native_bool = b; }
|
||||||
|
if let Some(b) = v.get("native_bool_abi").and_then(|x| x.as_bool()) { cfg.native_bool_abi = b; }
|
||||||
|
if let Some(b) = v.get("ret_bool_b1").and_then(|x| x.as_bool()) { cfg.ret_bool_b1 = b; }
|
||||||
|
Ok(Box::new(VoidBox::new()))
|
||||||
|
}
|
||||||
|
pub fn apply(&self) -> Box<dyn NyashBox> {
|
||||||
|
let cfg = self.config.read().unwrap().clone();
|
||||||
|
// Apply to env for CLI parity
|
||||||
|
cfg.apply_env();
|
||||||
|
// Also set global current JIT config for hot paths (env-less)
|
||||||
|
crate::jit::config::set_current(cfg);
|
||||||
|
Box::new(VoidBox::new())
|
||||||
|
}
|
||||||
|
pub fn summary(&self) -> Box<dyn NyashBox> {
|
||||||
|
let cfg = self.config.read().unwrap();
|
||||||
|
let s = format!(
|
||||||
|
"exec={} stats={} json={} dump={} thr={:?} phi_min={} hostcall={} hdbg={} f64={} bool={}",
|
||||||
|
cfg.exec, cfg.stats, cfg.stats_json, cfg.dump, cfg.threshold,
|
||||||
|
cfg.phi_min, cfg.hostcall, cfg.handle_debug, cfg.native_f64, cfg.native_bool
|
||||||
|
);
|
||||||
|
Box::new(StringBox::new(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BoxCore for JitConfigBox {
|
||||||
|
fn box_id(&self) -> u64 { self.base.id }
|
||||||
|
fn parent_type_id(&self) -> Option<std::any::TypeId> { self.base.parent_type_id }
|
||||||
|
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "JitConfigBox") }
|
||||||
|
fn as_any(&self) -> &dyn Any { self }
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any { self }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NyashBox for JitConfigBox {
|
||||||
|
fn to_string_box(&self) -> StringBox { StringBox::new(self.summary().to_string_box().value) }
|
||||||
|
fn equals(&self, other: &dyn NyashBox) -> BoolBox { BoolBox::new(other.as_any().is::<JitConfigBox>()) }
|
||||||
|
fn type_name(&self) -> &'static str { "JitConfigBox" }
|
||||||
|
fn clone_box(&self) -> Box<dyn NyashBox> {
|
||||||
|
let cfg = self.config.read().unwrap().clone();
|
||||||
|
Box::new(JitConfigBox { base: self.base.clone(), config: RwLock::new(cfg) })
|
||||||
|
}
|
||||||
|
fn share_box(&self) -> Box<dyn NyashBox> { self.clone_box() }
|
||||||
|
}
|
||||||
41
src/boxes/jit_events_box.rs
Normal file
41
src/boxes/jit_events_box.rs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
use crate::box_trait::{NyashBox, StringBox, BoolBox, VoidBox, BoxCore, BoxBase};
|
||||||
|
use std::any::Any;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct JitEventsBox { base: BoxBase }
|
||||||
|
|
||||||
|
impl JitEventsBox { pub fn new() -> Self { Self { base: BoxBase::new() } } }
|
||||||
|
|
||||||
|
impl BoxCore for JitEventsBox {
|
||||||
|
fn box_id(&self) -> u64 { self.base.id }
|
||||||
|
fn parent_type_id(&self) -> Option<std::any::TypeId> { self.base.parent_type_id }
|
||||||
|
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "JitEventsBox") }
|
||||||
|
fn as_any(&self) -> &dyn Any { self }
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any { self }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NyashBox for JitEventsBox {
|
||||||
|
fn to_string_box(&self) -> StringBox { StringBox::new("JitEventsBox") }
|
||||||
|
fn equals(&self, other: &dyn NyashBox) -> BoolBox { BoolBox::new(other.as_any().is::<JitEventsBox>()) }
|
||||||
|
fn type_name(&self) -> &'static str { "JitEventsBox" }
|
||||||
|
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(Self { base: self.base.clone() }) }
|
||||||
|
fn share_box(&self) -> Box<dyn NyashBox> { self.clone_box() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JitEventsBox {
|
||||||
|
pub fn set_path(&self, path: &str) -> Box<dyn NyashBox> {
|
||||||
|
std::env::set_var("NYASH_JIT_EVENTS_PATH", path);
|
||||||
|
Box::new(VoidBox::new())
|
||||||
|
}
|
||||||
|
pub fn enable(&self, on: bool) -> Box<dyn NyashBox> {
|
||||||
|
if on { std::env::set_var("NYASH_JIT_EVENTS", "1"); }
|
||||||
|
else { std::env::remove_var("NYASH_JIT_EVENTS"); }
|
||||||
|
Box::new(VoidBox::new())
|
||||||
|
}
|
||||||
|
pub fn log(&self, kind: &str, function: &str, note_json: &str) -> Box<dyn NyashBox> {
|
||||||
|
let extra = serde_json::from_str::<serde_json::Value>(note_json).unwrap_or_else(|_| serde_json::json!({"note": note_json}));
|
||||||
|
crate::jit::events::emit(kind, function, None, None, extra);
|
||||||
|
Box::new(VoidBox::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
52
src/boxes/jit_policy_box.rs
Normal file
52
src/boxes/jit_policy_box.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
use crate::box_trait::{NyashBox, StringBox, BoolBox, VoidBox, BoxCore, BoxBase};
|
||||||
|
use std::any::Any;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct JitPolicyBox { base: BoxBase }
|
||||||
|
|
||||||
|
impl JitPolicyBox { pub fn new() -> Self { Self { base: BoxBase::new() } } }
|
||||||
|
|
||||||
|
impl BoxCore for JitPolicyBox {
|
||||||
|
fn box_id(&self) -> u64 { self.base.id }
|
||||||
|
fn parent_type_id(&self) -> Option<std::any::TypeId> { self.base.parent_type_id }
|
||||||
|
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "JitPolicyBox") }
|
||||||
|
fn as_any(&self) -> &dyn Any { self }
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any { self }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NyashBox for JitPolicyBox {
|
||||||
|
fn to_string_box(&self) -> StringBox {
|
||||||
|
let p = crate::jit::policy::current();
|
||||||
|
let s = format!("read_only={} whitelist={}", p.read_only, p.hostcall_whitelist.join(","));
|
||||||
|
StringBox::new(s)
|
||||||
|
}
|
||||||
|
fn equals(&self, other: &dyn NyashBox) -> BoolBox { BoolBox::new(other.as_any().is::<JitPolicyBox>()) }
|
||||||
|
fn type_name(&self) -> &'static str { "JitPolicyBox" }
|
||||||
|
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(Self { base: self.base.clone() }) }
|
||||||
|
fn share_box(&self) -> Box<dyn NyashBox> { self.clone_box() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods (exposed via VM dispatch):
|
||||||
|
impl JitPolicyBox {
|
||||||
|
pub fn set_flag(&self, name: &str, on: bool) -> Box<dyn NyashBox> {
|
||||||
|
let mut cur = crate::jit::policy::current();
|
||||||
|
match name {
|
||||||
|
"read_only" | "readonly" => cur.read_only = on,
|
||||||
|
_ => return Box::new(StringBox::new(format!("Unknown flag: {}", name)))
|
||||||
|
}
|
||||||
|
crate::jit::policy::set_current(cur);
|
||||||
|
Box::new(VoidBox::new())
|
||||||
|
}
|
||||||
|
pub fn get_flag(&self, name: &str) -> Box<dyn NyashBox> {
|
||||||
|
let cur = crate::jit::policy::current();
|
||||||
|
let v = match name { "read_only" | "readonly" => cur.read_only, _ => false };
|
||||||
|
Box::new(BoolBox::new(v))
|
||||||
|
}
|
||||||
|
pub fn set_whitelist_csv(&self, csv: &str) -> Box<dyn NyashBox> {
|
||||||
|
let mut cur = crate::jit::policy::current();
|
||||||
|
cur.hostcall_whitelist = csv.split(',').map(|t| t.trim().to_string()).filter(|s| !s.is_empty()).collect();
|
||||||
|
crate::jit::policy::set_current(cur);
|
||||||
|
Box::new(VoidBox::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
41
src/boxes/jit_stats_box.rs
Normal file
41
src/boxes/jit_stats_box.rs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
use crate::box_trait::{NyashBox, StringBox, BoolBox, IntegerBox, VoidBox, BoxCore, BoxBase};
|
||||||
|
use std::any::Any;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct JitStatsBox { base: BoxBase }
|
||||||
|
|
||||||
|
impl JitStatsBox {
|
||||||
|
pub fn new() -> Self { Self { base: BoxBase::new() } }
|
||||||
|
pub fn to_json(&self) -> Box<dyn NyashBox> {
|
||||||
|
let cfg = crate::jit::config::current();
|
||||||
|
let caps = crate::jit::config::probe_capabilities();
|
||||||
|
let mode = if cfg.native_bool_abi && caps.supports_b1_sig { "b1_bool" } else { "i64_bool" };
|
||||||
|
let payload = serde_json::json!({
|
||||||
|
"version": 1,
|
||||||
|
"abi_mode": mode,
|
||||||
|
"abi_b1_enabled": cfg.native_bool_abi,
|
||||||
|
"abi_b1_supported": caps.supports_b1_sig,
|
||||||
|
"b1_norm_count": crate::jit::rt::b1_norm_get(),
|
||||||
|
"ret_bool_hint_count": crate::jit::rt::ret_bool_hint_get(),
|
||||||
|
"phi_total_slots": crate::jit::rt::phi_total_get(),
|
||||||
|
"phi_b1_slots": crate::jit::rt::phi_b1_get(),
|
||||||
|
});
|
||||||
|
Box::new(StringBox::new(payload.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BoxCore for JitStatsBox {
|
||||||
|
fn box_id(&self) -> u64 { self.base.id }
|
||||||
|
fn parent_type_id(&self) -> Option<std::any::TypeId> { self.base.parent_type_id }
|
||||||
|
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "JitStatsBox") }
|
||||||
|
fn as_any(&self) -> &dyn Any { self }
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any { self }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NyashBox for JitStatsBox {
|
||||||
|
fn to_string_box(&self) -> StringBox { StringBox::new(self.to_json().to_string_box().value) }
|
||||||
|
fn equals(&self, other: &dyn NyashBox) -> BoolBox { BoolBox::new(other.as_any().is::<JitStatsBox>()) }
|
||||||
|
fn type_name(&self) -> &'static str { "JitStatsBox" }
|
||||||
|
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
||||||
|
fn share_box(&self) -> Box<dyn NyashBox> { self.clone_box() }
|
||||||
|
}
|
||||||
@ -74,6 +74,10 @@ pub mod qr_box;
|
|||||||
pub mod sound_box;
|
pub mod sound_box;
|
||||||
pub mod map_box;
|
pub mod map_box;
|
||||||
pub mod console_box;
|
pub mod console_box;
|
||||||
|
pub mod jit_config_box;
|
||||||
|
pub mod jit_stats_box;
|
||||||
|
pub mod jit_policy_box;
|
||||||
|
pub mod jit_events_box;
|
||||||
|
|
||||||
// Web専用Box群(ブラウザ環境でのみ利用可能)
|
// Web専用Box群(ブラウザ環境でのみ利用可能)
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
@ -104,6 +108,10 @@ pub use qr_box::QRBox;
|
|||||||
pub use sound_box::SoundBox;
|
pub use sound_box::SoundBox;
|
||||||
pub use map_box::MapBox;
|
pub use map_box::MapBox;
|
||||||
pub use console_box::ConsoleBox;
|
pub use console_box::ConsoleBox;
|
||||||
|
pub use jit_config_box::JitConfigBox;
|
||||||
|
pub use jit_stats_box::JitStatsBox;
|
||||||
|
pub use jit_policy_box::JitPolicyBox;
|
||||||
|
pub use jit_events_box::JitEventsBox;
|
||||||
|
|
||||||
// EguiBoxの再エクスポート(非WASM環境のみ)
|
// EguiBoxの再エクスポート(非WASM環境のみ)
|
||||||
#[cfg(all(feature = "gui", not(target_arch = "wasm32")))]
|
#[cfg(all(feature = "gui", not(target_arch = "wasm32")))]
|
||||||
|
|||||||
@ -122,9 +122,34 @@ impl P2PBox {
|
|||||||
last_intent_name: Arc::new(RwLock::new(None)),
|
last_intent_name: Arc::new(RwLock::new(None)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Note: InProcess callback registration is postponed until a unified
|
// Minimal built-in system handler: auto-respond to sys.ping
|
||||||
// Transport subscription API is provided. For now, loopback tracing is
|
// This enables health checks via ping() without requiring user wiring.
|
||||||
// handled in send() when sending to self.
|
if attach_cb {
|
||||||
|
// capture for receive-side traces
|
||||||
|
let last_from = Arc::clone(&p2p.last_from);
|
||||||
|
let last_intent = Arc::clone(&p2p.last_intent_name);
|
||||||
|
// capture transport Arc to use inside handler
|
||||||
|
let transport_arc_outer = Arc::clone(&p2p.transport);
|
||||||
|
{
|
||||||
|
if let Ok(mut t) = transport_arc_outer.write() {
|
||||||
|
let transport_arc_for_cb = Arc::clone(&transport_arc_outer);
|
||||||
|
t.register_intent_handler("sys.ping", Box::new(move |env| {
|
||||||
|
if let Ok(mut lf) = last_from.write() { *lf = Some(env.from.clone()); }
|
||||||
|
if let Ok(mut li) = last_intent.write() { *li = Some(env.intent.get_name().to_string_box().value); }
|
||||||
|
// Reply asynchronously to avoid deep call stacks
|
||||||
|
let to = env.from.clone();
|
||||||
|
let reply = crate::boxes::IntentBox::new("sys.pong".to_string(), serde_json::json!({}));
|
||||||
|
let transport_arc = Arc::clone(&transport_arc_for_cb);
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||||
|
if let Ok(transport) = transport_arc.read() {
|
||||||
|
let _ = transport.send(&to, reply, Default::default());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
p2p
|
p2p
|
||||||
}
|
}
|
||||||
@ -135,6 +160,51 @@ impl P2PBox {
|
|||||||
Box::new(StringBox::new(node_id))
|
Box::new(StringBox::new(node_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Blocking ping: send sys.ping to target and wait for sys.pong
|
||||||
|
/// Returns BoolBox(true) on success within timeout, else false.
|
||||||
|
pub fn ping_with_timeout(&self, to: Box<dyn NyashBox>, timeout_ms: u64) -> Box<dyn NyashBox> {
|
||||||
|
use std::sync::{mpsc, Arc};
|
||||||
|
let to_str = to.to_string_box().value;
|
||||||
|
|
||||||
|
// Create oneshot channel for pong
|
||||||
|
let (tx, rx) = mpsc::channel::<()>();
|
||||||
|
let active = Arc::new(AtomicBool::new(true));
|
||||||
|
let active_cb = Arc::clone(&active);
|
||||||
|
|
||||||
|
// Register temporary transport-level handler for sys.pong
|
||||||
|
if let Ok(mut t) = self.transport.write() {
|
||||||
|
t.register_intent_handler("sys.pong", Box::new(move |env| {
|
||||||
|
if active_cb.load(Ordering::SeqCst) {
|
||||||
|
// record last receive for visibility
|
||||||
|
// Note: we cannot access self here safely; rely on tx notify only
|
||||||
|
let _ = env; // suppress unused
|
||||||
|
let _ = tx.send(());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Send sys.ping
|
||||||
|
let ping = IntentBox::new("sys.ping".to_string(), serde_json::json!({}));
|
||||||
|
match t.send(&to_str, ping, Default::default()) {
|
||||||
|
Ok(()) => { /* proceed to wait */ }
|
||||||
|
Err(_) => {
|
||||||
|
return Box::new(BoolBox::new(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Box::new(BoolBox::new(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for pong with timeout
|
||||||
|
let ok = rx.recv_timeout(std::time::Duration::from_millis(timeout_ms)).is_ok();
|
||||||
|
active.store(false, Ordering::SeqCst);
|
||||||
|
Box::new(BoolBox::new(ok))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience default-timeout ping (200ms)
|
||||||
|
pub fn ping(&self, to: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||||
|
self.ping_with_timeout(to, 200)
|
||||||
|
}
|
||||||
|
|
||||||
/// 特定ノードにメッセージを送信
|
/// 特定ノードにメッセージを送信
|
||||||
pub fn send(&self, to: Box<dyn NyashBox>, intent: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
pub fn send(&self, to: Box<dyn NyashBox>, intent: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||||
let to_str = to.to_string_box().value;
|
let to_str = to.to_string_box().value;
|
||||||
@ -453,4 +523,28 @@ mod tests {
|
|||||||
let c1 = p.debug_active_handler_count(Box::new(StringBox::new("bye")));
|
let c1 = p.debug_active_handler_count(Box::new(StringBox::new("bye")));
|
||||||
assert_eq!(c1.to_string_box().value, "0");
|
assert_eq!(c1.to_string_box().value, "0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ping_success_between_two_nodes() {
|
||||||
|
let alice = P2PBox::new("alice".to_string(), TransportKind::InProcess);
|
||||||
|
let bob = P2PBox::new("bob".to_string(), TransportKind::InProcess);
|
||||||
|
// bob has built-in sys.ping -> sys.pong
|
||||||
|
let ok = alice.ping(Box::new(StringBox::new("bob")));
|
||||||
|
if let Some(b) = ok.as_any().downcast_ref::<BoolBox>() {
|
||||||
|
assert!(b.value);
|
||||||
|
} else {
|
||||||
|
panic!("ping did not return BoolBox");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ping_timeout_on_missing_node() {
|
||||||
|
let alice = P2PBox::new("alice".to_string(), TransportKind::InProcess);
|
||||||
|
let ok = alice.ping_with_timeout(Box::new(StringBox::new("nobody")), 20);
|
||||||
|
if let Some(b) = ok.as_any().downcast_ref::<BoolBox>() {
|
||||||
|
assert!(!b.value);
|
||||||
|
} else {
|
||||||
|
panic!("ping_with_timeout did not return BoolBox");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
116
src/cli.rs
116
src/cli.rs
@ -26,6 +26,21 @@ pub struct CliConfig {
|
|||||||
pub iterations: u32,
|
pub iterations: u32,
|
||||||
pub vm_stats: bool,
|
pub vm_stats: bool,
|
||||||
pub vm_stats_json: bool,
|
pub vm_stats_json: bool,
|
||||||
|
// JIT controls
|
||||||
|
pub jit_exec: bool,
|
||||||
|
pub jit_stats: bool,
|
||||||
|
pub jit_stats_json: bool,
|
||||||
|
pub jit_dump: bool,
|
||||||
|
pub jit_threshold: Option<u32>,
|
||||||
|
pub jit_phi_min: bool,
|
||||||
|
pub jit_hostcall: bool,
|
||||||
|
pub jit_handle_debug: bool,
|
||||||
|
pub jit_native_f64: bool,
|
||||||
|
pub jit_native_bool: bool,
|
||||||
|
pub jit_only: bool,
|
||||||
|
pub jit_direct: bool,
|
||||||
|
// DOT emit helper
|
||||||
|
pub emit_cfg: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CliConfig {
|
impl CliConfig {
|
||||||
@ -147,6 +162,84 @@ impl CliConfig {
|
|||||||
.help("Output VM statistics in JSON format")
|
.help("Output VM statistics in JSON format")
|
||||||
.action(clap::ArgAction::SetTrue)
|
.action(clap::ArgAction::SetTrue)
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("jit-exec")
|
||||||
|
.long("jit-exec")
|
||||||
|
.help("Enable JIT execution where available (NYASH_JIT_EXEC=1)")
|
||||||
|
.action(clap::ArgAction::SetTrue)
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("jit-stats")
|
||||||
|
.long("jit-stats")
|
||||||
|
.help("Print JIT compilation/execution statistics (NYASH_JIT_STATS=1)")
|
||||||
|
.action(clap::ArgAction::SetTrue)
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("jit-stats-json")
|
||||||
|
.long("jit-stats-json")
|
||||||
|
.help("Output JIT statistics in JSON format (NYASH_JIT_STATS_JSON=1)")
|
||||||
|
.action(clap::ArgAction::SetTrue)
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("jit-dump")
|
||||||
|
.long("jit-dump")
|
||||||
|
.help("Dump JIT lowering summary (NYASH_JIT_DUMP=1)")
|
||||||
|
.action(clap::ArgAction::SetTrue)
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("jit-threshold")
|
||||||
|
.long("jit-threshold")
|
||||||
|
.value_name("N")
|
||||||
|
.help("Set hotness threshold for JIT compilation (NYASH_JIT_THRESHOLD)")
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("jit-phi-min")
|
||||||
|
.long("jit-phi-min")
|
||||||
|
.help("Enable minimal PHI path for branches (NYASH_JIT_PHI_MIN=1)")
|
||||||
|
.action(clap::ArgAction::SetTrue)
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("jit-hostcall")
|
||||||
|
.long("jit-hostcall")
|
||||||
|
.help("Enable JIT hostcall bridge for Array/Map (NYASH_JIT_HOSTCALL=1)")
|
||||||
|
.action(clap::ArgAction::SetTrue)
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("jit-handle-debug")
|
||||||
|
.long("jit-handle-debug")
|
||||||
|
.help("Print JIT handle allocation debug logs (NYASH_JIT_HANDLE_DEBUG=1)")
|
||||||
|
.action(clap::ArgAction::SetTrue)
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("jit-native-f64")
|
||||||
|
.long("jit-native-f64")
|
||||||
|
.help("Enable native f64 ABI path in JIT (NYASH_JIT_NATIVE_F64=1)")
|
||||||
|
.action(clap::ArgAction::SetTrue)
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("jit-native-bool")
|
||||||
|
.long("jit-native-bool")
|
||||||
|
.help("Enable native bool ABI path in JIT (NYASH_JIT_NATIVE_BOOL=1)")
|
||||||
|
.action(clap::ArgAction::SetTrue)
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("jit-only")
|
||||||
|
.long("jit-only")
|
||||||
|
.help("Run JIT only (no VM fallback). Fails if JIT is unavailable (NYASH_JIT_ONLY=1)")
|
||||||
|
.action(clap::ArgAction::SetTrue)
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("jit-direct")
|
||||||
|
.long("jit-direct")
|
||||||
|
.help("Run program via independent JIT engine (no VM interpreter/executor). Requires --features cranelift-jit")
|
||||||
|
.action(clap::ArgAction::SetTrue)
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("emit-cfg")
|
||||||
|
.long("emit-cfg")
|
||||||
|
.value_name("DOT_FILE")
|
||||||
|
.help("Emit JIT CFG as DOT to file (equivalent to setting NYASH_JIT_DOT)")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert ArgMatches to CliConfig
|
/// Convert ArgMatches to CliConfig
|
||||||
@ -168,6 +261,19 @@ impl CliConfig {
|
|||||||
iterations: matches.get_one::<String>("iterations").unwrap().parse().unwrap_or(10),
|
iterations: matches.get_one::<String>("iterations").unwrap().parse().unwrap_or(10),
|
||||||
vm_stats: matches.get_flag("vm-stats"),
|
vm_stats: matches.get_flag("vm-stats"),
|
||||||
vm_stats_json: matches.get_flag("vm-stats-json"),
|
vm_stats_json: matches.get_flag("vm-stats-json"),
|
||||||
|
jit_exec: matches.get_flag("jit-exec"),
|
||||||
|
jit_stats: matches.get_flag("jit-stats"),
|
||||||
|
jit_stats_json: matches.get_flag("jit-stats-json"),
|
||||||
|
jit_dump: matches.get_flag("jit-dump"),
|
||||||
|
jit_threshold: matches.get_one::<String>("jit-threshold").and_then(|s| s.parse::<u32>().ok()),
|
||||||
|
jit_phi_min: matches.get_flag("jit-phi-min"),
|
||||||
|
jit_hostcall: matches.get_flag("jit-hostcall"),
|
||||||
|
jit_handle_debug: matches.get_flag("jit-handle-debug"),
|
||||||
|
jit_native_f64: matches.get_flag("jit-native-f64"),
|
||||||
|
jit_native_bool: matches.get_flag("jit-native-bool"),
|
||||||
|
emit_cfg: matches.get_one::<String>("emit-cfg").cloned(),
|
||||||
|
jit_only: matches.get_flag("jit-only"),
|
||||||
|
jit_direct: matches.get_flag("jit-direct"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,6 +319,16 @@ mod tests {
|
|||||||
iterations: 10,
|
iterations: 10,
|
||||||
vm_stats: false,
|
vm_stats: false,
|
||||||
vm_stats_json: false,
|
vm_stats_json: false,
|
||||||
|
jit_exec: false,
|
||||||
|
jit_stats: false,
|
||||||
|
jit_stats_json: false,
|
||||||
|
jit_dump: false,
|
||||||
|
jit_threshold: None,
|
||||||
|
jit_phi_min: false,
|
||||||
|
jit_hostcall: false,
|
||||||
|
jit_handle_debug: false,
|
||||||
|
jit_native_f64: false,
|
||||||
|
jit_native_bool: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(config.backend, "interpreter");
|
assert_eq!(config.backend, "interpreter");
|
||||||
|
|||||||
@ -76,6 +76,21 @@ impl NyashInterpreter {
|
|||||||
Ok(p2p_box.send(to_result, intent_result))
|
Ok(p2p_box.send(to_result, intent_result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ping: health check using sys.ping/sys.pong
|
||||||
|
"ping" => {
|
||||||
|
if arguments.is_empty() {
|
||||||
|
return Err(RuntimeError::InvalidOperation { message: "ping requires (to [, timeout_ms]) arguments".to_string() });
|
||||||
|
}
|
||||||
|
let to_result = self.execute_expression(&arguments[0])?;
|
||||||
|
if arguments.len() >= 2 {
|
||||||
|
let tmo_val = self.execute_expression(&arguments[1])?;
|
||||||
|
let tmo_ms = tmo_val.to_string_box().value.parse::<u64>().unwrap_or(200);
|
||||||
|
Ok(p2p_box.ping_with_timeout(to_result, tmo_ms))
|
||||||
|
} else {
|
||||||
|
Ok(p2p_box.ping(to_result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// on メソッド実装(ResultBox返却)
|
// on メソッド実装(ResultBox返却)
|
||||||
"on" => {
|
"on" => {
|
||||||
if arguments.len() < 2 {
|
if arguments.len() < 2 {
|
||||||
|
|||||||
62
src/jit/abi.rs
Normal file
62
src/jit/abi.rs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
//! JIT minimal ABI types independent from VM internals
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum JitValue {
|
||||||
|
I64(i64),
|
||||||
|
F64(f64),
|
||||||
|
Bool(bool),
|
||||||
|
/// Opaque handle for host objects (future use)
|
||||||
|
Handle(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JitValue {
|
||||||
|
pub fn as_i64(&self) -> Option<i64> { if let JitValue::I64(v) = self { Some(*v) } else { None } }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adapter between VMValue and JitValue — keeps JIT decoupled from VM internals
|
||||||
|
pub mod adapter {
|
||||||
|
use super::JitValue;
|
||||||
|
use crate::backend::vm::VMValue;
|
||||||
|
|
||||||
|
pub fn to_jit_values(args: &[VMValue]) -> Vec<JitValue> {
|
||||||
|
args.iter().map(|v| match v {
|
||||||
|
VMValue::Integer(i) => JitValue::I64(*i),
|
||||||
|
VMValue::Float(f) => JitValue::F64(*f),
|
||||||
|
VMValue::Bool(b) => JitValue::Bool(*b),
|
||||||
|
VMValue::BoxRef(arc) => {
|
||||||
|
let h = crate::jit::rt::handles::to_handle(arc.clone());
|
||||||
|
JitValue::Handle(h)
|
||||||
|
}
|
||||||
|
// For now, map others to handle via boxing where reasonable
|
||||||
|
VMValue::String(s) => {
|
||||||
|
let bx = Box::new(crate::box_trait::StringBox::new(s));
|
||||||
|
let bx_dyn: Box<dyn crate::box_trait::NyashBox> = bx;
|
||||||
|
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(bx_dyn);
|
||||||
|
let h = crate::jit::rt::handles::to_handle(arc);
|
||||||
|
JitValue::Handle(h)
|
||||||
|
}
|
||||||
|
VMValue::Void => JitValue::Handle(0),
|
||||||
|
VMValue::Future(f) => {
|
||||||
|
let bx_dyn: Box<dyn crate::box_trait::NyashBox> = Box::new(f.clone());
|
||||||
|
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(bx_dyn);
|
||||||
|
let h = crate::jit::rt::handles::to_handle(arc);
|
||||||
|
JitValue::Handle(h)
|
||||||
|
}
|
||||||
|
}).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_jit_value(v: JitValue) -> VMValue {
|
||||||
|
match v {
|
||||||
|
JitValue::I64(i) => VMValue::Integer(i),
|
||||||
|
JitValue::F64(f) => VMValue::Float(f),
|
||||||
|
JitValue::Bool(b) => VMValue::Bool(b),
|
||||||
|
JitValue::Handle(h) => {
|
||||||
|
if let Some(arc) = crate::jit::rt::handles::get(h) {
|
||||||
|
VMValue::BoxRef(arc)
|
||||||
|
} else {
|
||||||
|
VMValue::Void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
102
src/jit/config.rs
Normal file
102
src/jit/config.rs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
//! JIT configuration aggregator
|
||||||
|
//!
|
||||||
|
//! Centralizes JIT-related flags so callers and tests can use a single
|
||||||
|
//! source of truth instead of scattering env access across modules.
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct JitConfig {
|
||||||
|
pub exec: bool, // NYASH_JIT_EXEC
|
||||||
|
pub stats: bool, // NYASH_JIT_STATS
|
||||||
|
pub stats_json: bool, // NYASH_JIT_STATS_JSON
|
||||||
|
pub dump: bool, // NYASH_JIT_DUMP
|
||||||
|
pub threshold: Option<u32>,// NYASH_JIT_THRESHOLD
|
||||||
|
pub phi_min: bool, // NYASH_JIT_PHI_MIN
|
||||||
|
pub hostcall: bool, // NYASH_JIT_HOSTCALL
|
||||||
|
pub handle_debug: bool, // NYASH_JIT_HANDLE_DEBUG
|
||||||
|
pub native_f64: bool, // NYASH_JIT_NATIVE_F64
|
||||||
|
pub native_bool: bool, // NYASH_JIT_NATIVE_BOOL
|
||||||
|
pub native_bool_abi: bool, // NYASH_JIT_ABI_B1 (experimental)
|
||||||
|
pub ret_bool_b1: bool, // NYASH_JIT_RET_B1 (footing; currently returns i64 0/1)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JitConfig {
|
||||||
|
pub fn from_env() -> Self {
|
||||||
|
let getb = |k: &str| std::env::var(k).ok().as_deref() == Some("1");
|
||||||
|
let threshold = std::env::var("NYASH_JIT_THRESHOLD").ok().and_then(|s| s.parse::<u32>().ok());
|
||||||
|
Self {
|
||||||
|
exec: getb("NYASH_JIT_EXEC"),
|
||||||
|
stats: getb("NYASH_JIT_STATS"),
|
||||||
|
stats_json: getb("NYASH_JIT_STATS_JSON"),
|
||||||
|
dump: getb("NYASH_JIT_DUMP"),
|
||||||
|
threshold,
|
||||||
|
phi_min: getb("NYASH_JIT_PHI_MIN"),
|
||||||
|
hostcall: getb("NYASH_JIT_HOSTCALL"),
|
||||||
|
handle_debug: getb("NYASH_JIT_HANDLE_DEBUG"),
|
||||||
|
native_f64: getb("NYASH_JIT_NATIVE_F64"),
|
||||||
|
native_bool: getb("NYASH_JIT_NATIVE_BOOL"),
|
||||||
|
native_bool_abi: getb("NYASH_JIT_ABI_B1"),
|
||||||
|
ret_bool_b1: getb("NYASH_JIT_RET_B1"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply current struct values into environment variables.
|
||||||
|
/// This keeps existing env untouched unless the value is explicitly set here.
|
||||||
|
pub fn apply_env(&self) {
|
||||||
|
let setb = |k: &str, v: bool| { if v { std::env::set_var(k, "1"); } };
|
||||||
|
setb("NYASH_JIT_EXEC", self.exec);
|
||||||
|
setb("NYASH_JIT_STATS", self.stats);
|
||||||
|
setb("NYASH_JIT_STATS_JSON", self.stats_json);
|
||||||
|
setb("NYASH_JIT_DUMP", self.dump);
|
||||||
|
if let Some(t) = self.threshold { std::env::set_var("NYASH_JIT_THRESHOLD", t.to_string()); }
|
||||||
|
setb("NYASH_JIT_PHI_MIN", self.phi_min);
|
||||||
|
setb("NYASH_JIT_HOSTCALL", self.hostcall);
|
||||||
|
setb("NYASH_JIT_HANDLE_DEBUG", self.handle_debug);
|
||||||
|
setb("NYASH_JIT_NATIVE_F64", self.native_f64);
|
||||||
|
setb("NYASH_JIT_NATIVE_BOOL", self.native_bool);
|
||||||
|
setb("NYASH_JIT_ABI_B1", self.native_bool_abi);
|
||||||
|
setb("NYASH_JIT_RET_B1", self.ret_bool_b1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global current JIT config (thread-safe), defaults to env when unset
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use std::sync::RwLock;
|
||||||
|
|
||||||
|
static GLOBAL_JIT_CONFIG: OnceCell<RwLock<JitConfig>> = OnceCell::new();
|
||||||
|
|
||||||
|
/// Get current JIT config (falls back to env-derived default if unset)
|
||||||
|
pub fn current() -> JitConfig {
|
||||||
|
if let Some(lock) = GLOBAL_JIT_CONFIG.get() {
|
||||||
|
if let Ok(cfg) = lock.read() { return cfg.clone(); }
|
||||||
|
}
|
||||||
|
JitConfig::from_env()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set current JIT config (overrides env lookups in hot paths)
|
||||||
|
pub fn set_current(cfg: JitConfig) {
|
||||||
|
if let Some(lock) = GLOBAL_JIT_CONFIG.get() {
|
||||||
|
if let Ok(mut w) = lock.write() { *w = cfg; return; }
|
||||||
|
}
|
||||||
|
let _ = GLOBAL_JIT_CONFIG.set(RwLock::new(cfg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Runtime capability probing (minimal, safe defaults) ---
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub struct JitCapabilities {
|
||||||
|
pub supports_b1_sig: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Probe JIT backend capabilities once. Safe default: b1 signatures are unsupported.
|
||||||
|
pub fn probe_capabilities() -> JitCapabilities {
|
||||||
|
// Current toolchain: allow forcing via env for experiments; otherwise false.
|
||||||
|
// When upgrading Cranelift to a version with B1 signature support, set NYASH_JIT_ABI_B1_SUPPORT=1
|
||||||
|
let forced = std::env::var("NYASH_JIT_ABI_B1_SUPPORT").ok().as_deref() == Some("1");
|
||||||
|
JitCapabilities { supports_b1_sig: forced }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply runtime capabilities onto a JitConfig (e.g., disable b1 ABI when unsupported)
|
||||||
|
pub fn apply_runtime_caps(mut cfg: JitConfig, caps: JitCapabilities) -> JitConfig {
|
||||||
|
if cfg.native_bool_abi && !caps.supports_b1_sig { cfg.native_bool_abi = false; }
|
||||||
|
cfg
|
||||||
|
}
|
||||||
@ -12,11 +12,15 @@ pub struct JitEngine {
|
|||||||
initialized: bool,
|
initialized: bool,
|
||||||
next_handle: u64,
|
next_handle: u64,
|
||||||
/// Stub function table: handle -> callable closure
|
/// Stub function table: handle -> callable closure
|
||||||
fntab: HashMap<u64, Arc<dyn Fn(&[crate::backend::vm::VMValue]) -> crate::backend::vm::VMValue + Send + Sync>>,
|
fntab: HashMap<u64, Arc<dyn Fn(&[crate::jit::abi::JitValue]) -> crate::jit::abi::JitValue + Send + Sync>>,
|
||||||
/// Host externs by symbol name (Phase 10_d)
|
/// Host externs by symbol name (Phase 10_d)
|
||||||
externs: HashMap<String, Arc<dyn Fn(&[crate::backend::vm::VMValue]) -> crate::backend::vm::VMValue + Send + Sync>>,
|
externs: HashMap<String, Arc<dyn Fn(&[crate::backend::vm::VMValue]) -> crate::backend::vm::VMValue + Send + Sync>>,
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
isa: Option<cranelift_codegen::isa::OwnedTargetIsa>,
|
isa: Option<cranelift_codegen::isa::OwnedTargetIsa>,
|
||||||
|
// Last lower stats (per function)
|
||||||
|
last_phi_total: u64,
|
||||||
|
last_phi_b1: u64,
|
||||||
|
last_ret_bool_hint: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JitEngine {
|
impl JitEngine {
|
||||||
@ -27,6 +31,9 @@ impl JitEngine {
|
|||||||
fntab: HashMap::new(),
|
fntab: HashMap::new(),
|
||||||
externs: HashMap::new(),
|
externs: HashMap::new(),
|
||||||
#[cfg(feature = "cranelift-jit")] isa: None,
|
#[cfg(feature = "cranelift-jit")] isa: None,
|
||||||
|
last_phi_total: 0,
|
||||||
|
last_phi_b1: 0,
|
||||||
|
last_ret_bool_hint: false,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
{ this.isa = None; }
|
{ this.isa = None; }
|
||||||
@ -47,26 +54,46 @@ impl JitEngine {
|
|||||||
eprintln!("[JIT] lower failed for {}: {}", func_name, e);
|
eprintln!("[JIT] lower failed for {}: {}", func_name, e);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if std::env::var("NYASH_JIT_DUMP").ok().as_deref() == Some("1") {
|
// Capture per-function lower stats for manager to query later
|
||||||
|
let (phi_t, phi_b1, ret_b) = lower.last_stats();
|
||||||
|
self.last_phi_total = phi_t; self.last_phi_b1 = phi_b1; self.last_ret_bool_hint = ret_b;
|
||||||
|
// Record per-function stats into manager via callback if available (handled by caller)
|
||||||
|
let cfg_now = crate::jit::config::current();
|
||||||
|
if cfg_now.dump {
|
||||||
|
let phi_min = cfg_now.phi_min;
|
||||||
|
let native_f64 = cfg_now.native_f64;
|
||||||
|
let native_bool = cfg_now.native_bool;
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
{
|
{
|
||||||
let s = builder.stats;
|
let s = builder.stats;
|
||||||
eprintln!("[JIT] lower {}: covered={} unsupported={} (consts={}, binops={}, cmps={}, branches={}, rets={})",
|
eprintln!("[JIT] lower {}: argc={} phi_min={} f64={} bool={} covered={} unsupported={} (consts={}, binops={}, cmps={}, branches={}, rets={})",
|
||||||
func_name, lower.covered, lower.unsupported,
|
func_name, mir.params.len(), phi_min, native_f64, native_bool,
|
||||||
|
lower.covered, lower.unsupported,
|
||||||
s.0, s.1, s.2, s.3, s.4);
|
s.0, s.1, s.2, s.3, s.4);
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "cranelift-jit"))]
|
#[cfg(not(feature = "cranelift-jit"))]
|
||||||
{
|
{
|
||||||
eprintln!("[JIT] lower {}: covered={} unsupported={} (consts={}, binops={}, cmps={}, branches={}, rets={})",
|
eprintln!("[JIT] lower {}: argc={} phi_min={} f64={} bool={} covered={} unsupported={} (consts={}, binops={}, cmps={}, branches={}, rets={})",
|
||||||
func_name, lower.covered, lower.unsupported,
|
func_name, mir.params.len(), phi_min, native_f64, native_bool,
|
||||||
|
lower.covered, lower.unsupported,
|
||||||
builder.consts, builder.binops, builder.cmps, builder.branches, builder.rets);
|
builder.consts, builder.binops, builder.cmps, builder.branches, builder.rets);
|
||||||
}
|
}
|
||||||
|
// Optional DOT export
|
||||||
|
if let Ok(path) = std::env::var("NYASH_JIT_DOT") {
|
||||||
|
if !path.is_empty() {
|
||||||
|
if let Err(e) = crate::jit::lower::core::dump_cfg_dot(mir, &path, phi_min) {
|
||||||
|
eprintln!("[JIT] DOT export failed: {}", e);
|
||||||
|
} else {
|
||||||
|
eprintln!("[JIT] DOT written to {}", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Create a handle and register an executable closure
|
// Create a handle and register an executable closure if available
|
||||||
let h = self.next_handle;
|
|
||||||
self.next_handle = self.next_handle.saturating_add(1);
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
{
|
{
|
||||||
|
let h = self.next_handle;
|
||||||
|
self.next_handle = self.next_handle.saturating_add(1);
|
||||||
if let Some(closure) = builder.take_compiled_closure() {
|
if let Some(closure) = builder.take_compiled_closure() {
|
||||||
self.fntab.insert(h, closure);
|
self.fntab.insert(h, closure);
|
||||||
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
|
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
|
||||||
@ -75,21 +102,27 @@ impl JitEngine {
|
|||||||
}
|
}
|
||||||
return Some(h);
|
return Some(h);
|
||||||
}
|
}
|
||||||
|
// If Cranelift path did not produce a closure, treat as not compiled
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
// Fallback: insert a stub closure
|
#[cfg(not(feature = "cranelift-jit"))]
|
||||||
self.fntab.insert(h, Arc::new(|_args: &[crate::backend::vm::VMValue]| {
|
{
|
||||||
crate::backend::vm::VMValue::Void
|
// Without Cranelift, do not register a stub that alters program semantics.
|
||||||
}));
|
// Report as not compiled so VM path remains authoritative.
|
||||||
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
|
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
|
||||||
let dt = t0.elapsed();
|
let dt = t0.elapsed();
|
||||||
eprintln!("[JIT] compile_time_ms={} for {} (stub)", dt.as_millis(), func_name);
|
eprintln!("[JIT] compile skipped (no cranelift) for {} after {}ms", func_name, dt.as_millis());
|
||||||
|
}
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
Some(h)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get statistics from the last lowered function
|
||||||
|
pub fn last_lower_stats(&self) -> (u64, u64, bool) { (self.last_phi_total, self.last_phi_b1, self.last_ret_bool_hint) }
|
||||||
|
|
||||||
/// Execute compiled function by handle with trap fallback.
|
/// Execute compiled function by handle with trap fallback.
|
||||||
/// Returns Some(VMValue) if executed successfully; None on missing handle or trap (panic).
|
/// Returns Some(VMValue) if executed successfully; None on missing handle or trap (panic).
|
||||||
pub fn execute_handle(&self, handle: u64, args: &[crate::backend::vm::VMValue]) -> Option<crate::backend::vm::VMValue> {
|
pub fn execute_handle(&self, handle: u64, args: &[crate::jit::abi::JitValue]) -> Option<crate::jit::abi::JitValue> {
|
||||||
let f = match self.fntab.get(&handle) { Some(f) => f, None => return None };
|
let f = match self.fntab.get(&handle) { Some(f) => f, None => return None };
|
||||||
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (f)(args)));
|
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (f)(args)));
|
||||||
match res {
|
match res {
|
||||||
|
|||||||
42
src/jit/events.rs
Normal file
42
src/jit/events.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
//! JIT Events (v0): minimal JSONL appender for compile/execute/fallback/trap
|
||||||
|
//!
|
||||||
|
//! Emission is opt-in via env:
|
||||||
|
//! - NYASH_JIT_EVENTS=1 prints to stdout (one JSON per line)
|
||||||
|
//! - NYASH_JIT_EVENTS_PATH=/path/to/file.jsonl appends to file
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
fn should_emit() -> bool {
|
||||||
|
std::env::var("NYASH_JIT_EVENTS").ok().as_deref() == Some("1")
|
||||||
|
|| std::env::var("NYASH_JIT_EVENTS_PATH").is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_line(s: &str) {
|
||||||
|
if let Ok(path) = std::env::var("NYASH_JIT_EVENTS_PATH") {
|
||||||
|
let _ = std::fs::OpenOptions::new().create(true).append(true).open(path).and_then(|mut f| {
|
||||||
|
use std::io::Write;
|
||||||
|
writeln!(f, "{}", s)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
println!("{}", s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Event<'a, T: Serialize> {
|
||||||
|
kind: &'a str,
|
||||||
|
function: &'a str,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
handle: Option<u64>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
ms: Option<u128>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
extra: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emit<T: Serialize>(kind: &str, function: &str, handle: Option<u64>, ms: Option<u128>, extra: T) {
|
||||||
|
if !should_emit() { return; }
|
||||||
|
let ev = Event { kind, function, handle, ms, extra };
|
||||||
|
if let Ok(s) = serde_json::to_string(&ev) { write_line(&s); }
|
||||||
|
}
|
||||||
|
|
||||||
16
src/jit/extern/collections.rs
vendored
16
src/jit/extern/collections.rs
vendored
@ -13,6 +13,21 @@ pub const SYM_MAP_GET: &str = "nyash.map.get";
|
|||||||
pub const SYM_MAP_SET: &str = "nyash.map.set";
|
pub const SYM_MAP_SET: &str = "nyash.map.set";
|
||||||
pub const SYM_MAP_SIZE: &str = "nyash.map.size";
|
pub const SYM_MAP_SIZE: &str = "nyash.map.size";
|
||||||
|
|
||||||
|
// Handle-based variants for direct JIT bridging
|
||||||
|
pub const SYM_ARRAY_LEN_H: &str = "nyash.array.len_h";
|
||||||
|
pub const SYM_ARRAY_GET_H: &str = "nyash.array.get_h";
|
||||||
|
pub const SYM_ARRAY_SET_H: &str = "nyash.array.set_h";
|
||||||
|
pub const SYM_ARRAY_PUSH_H: &str = "nyash.array.push_h";
|
||||||
|
pub const SYM_ARRAY_LAST_H: &str = "nyash.array.last_h";
|
||||||
|
pub const SYM_MAP_SIZE_H: &str = "nyash.map.size_h";
|
||||||
|
pub const SYM_MAP_GET_H: &str = "nyash.map.get_h";
|
||||||
|
pub const SYM_MAP_SET_H: &str = "nyash.map.set_h";
|
||||||
|
pub const SYM_MAP_HAS_H: &str = "nyash.map.has_h";
|
||||||
|
// Generic read-only helper
|
||||||
|
pub const SYM_ANY_LEN_H: &str = "nyash.any.length_h";
|
||||||
|
pub const SYM_ANY_IS_EMPTY_H: &str = "nyash.any.is_empty_h";
|
||||||
|
pub const SYM_STRING_CHARCODE_AT_H: &str = "nyash.string.charCodeAt_h";
|
||||||
|
|
||||||
fn as_array(args: &[VMValue]) -> Option<&crate::boxes::array::ArrayBox> {
|
fn as_array(args: &[VMValue]) -> Option<&crate::boxes::array::ArrayBox> {
|
||||||
match args.get(0) {
|
match args.get(0) {
|
||||||
Some(VMValue::BoxRef(b)) => b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>(),
|
Some(VMValue::BoxRef(b)) => b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>(),
|
||||||
@ -88,4 +103,3 @@ pub fn map_size(args: &[VMValue]) -> VMValue {
|
|||||||
}
|
}
|
||||||
VMValue::Integer(0)
|
VMValue::Integer(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
18
src/jit/hostcall_registry.rs
Normal file
18
src/jit/hostcall_registry.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
//! Minimal hostcall registry (v0): classify symbols as read-only or mutating
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum HostcallKind { ReadOnly, Mutating }
|
||||||
|
|
||||||
|
pub fn classify(symbol: &str) -> HostcallKind {
|
||||||
|
match symbol {
|
||||||
|
// Read-only (safe under read_only policy)
|
||||||
|
"nyash.array.len_h" | "nyash.any.length_h" | "nyash.any.is_empty_h" |
|
||||||
|
"nyash.map.size_h" | "nyash.map.get_h" | "nyash.string.charCodeAt_h" |
|
||||||
|
"nyash.array.get_h" => HostcallKind::ReadOnly,
|
||||||
|
// Mutating
|
||||||
|
"nyash.array.push_h" | "nyash.array.set_h" | "nyash.map.set_h" => HostcallKind::Mutating,
|
||||||
|
// Default to read-only to be permissive in v0
|
||||||
|
_ => HostcallKind::ReadOnly,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -9,11 +9,16 @@ pub enum BinOpKind { Add, Sub, Mul, Div, Mod }
|
|||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum CmpKind { Eq, Ne, Lt, Le, Gt, Ge }
|
pub enum CmpKind { Eq, Ne, Lt, Le, Gt, Ge }
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ParamKind { I64, F64, B1 }
|
||||||
|
|
||||||
pub trait IRBuilder {
|
pub trait IRBuilder {
|
||||||
fn begin_function(&mut self, name: &str);
|
fn begin_function(&mut self, name: &str);
|
||||||
fn end_function(&mut self);
|
fn end_function(&mut self);
|
||||||
/// Optional: prepare a simple `i64` ABI signature with `argc` params
|
/// Optional: prepare a simple `i64` ABI signature with `argc` params
|
||||||
fn prepare_signature_i64(&mut self, _argc: usize, _has_ret: bool) { }
|
fn prepare_signature_i64(&mut self, _argc: usize, _has_ret: bool) { }
|
||||||
|
/// Optional: prepare typed ABI signature for params and f64 return flag
|
||||||
|
fn prepare_signature_typed(&mut self, _params: &[ParamKind], _ret_is_f64: bool) { }
|
||||||
/// Load i64 parameter at index and push to value stack (Core-1 path)
|
/// Load i64 parameter at index and push to value stack (Core-1 path)
|
||||||
fn emit_param_i64(&mut self, _index: usize) { }
|
fn emit_param_i64(&mut self, _index: usize) { }
|
||||||
fn emit_const_i64(&mut self, _val: i64);
|
fn emit_const_i64(&mut self, _val: i64);
|
||||||
@ -36,10 +41,18 @@ pub trait IRBuilder {
|
|||||||
fn br_if_top_is_true(&mut self, _then_index: usize, _else_index: usize) { }
|
fn br_if_top_is_true(&mut self, _then_index: usize, _else_index: usize) { }
|
||||||
/// Optional: unconditional jump to target block index
|
/// Optional: unconditional jump to target block index
|
||||||
fn jump_to(&mut self, _target_index: usize) { }
|
fn jump_to(&mut self, _target_index: usize) { }
|
||||||
/// Optional: ensure target block has one i64 block param (for minimal PHI)
|
/// Optional: ensure target block has N i64 block params (for PHI)
|
||||||
fn ensure_block_param_i64(&mut self, _index: usize) { }
|
fn ensure_block_params_i64(&mut self, _index: usize, _count: usize) { }
|
||||||
/// Optional: push current block's first param (i64) onto the value stack
|
/// Optional: ensure target block has N b1 block params (for PHI of bool)
|
||||||
fn push_block_param_i64(&mut self) { }
|
fn ensure_block_params_b1(&mut self, index: usize, count: usize) { self.ensure_block_params_i64(index, count); }
|
||||||
|
/// Optional: ensure target block has one i64 block param (backward compat)
|
||||||
|
fn ensure_block_param_i64(&mut self, index: usize) { self.ensure_block_params_i64(index, 1); }
|
||||||
|
/// Optional: push current block's param at position onto the value stack (default=0)
|
||||||
|
fn push_block_param_i64_at(&mut self, _pos: usize) { }
|
||||||
|
/// Optional: push current block's boolean param (b1) at position; default converts i64 0/1 → b1
|
||||||
|
fn push_block_param_b1_at(&mut self, _pos: usize) { self.push_block_param_i64_at(_pos); }
|
||||||
|
/// Optional: push current block's first param (i64) onto the value stack (backward compat)
|
||||||
|
fn push_block_param_i64(&mut self) { self.push_block_param_i64_at(0); }
|
||||||
/// Optional: conditional branch with explicit arg counts for then/else; pops args from stack
|
/// Optional: conditional branch with explicit arg counts for then/else; pops args from stack
|
||||||
fn br_if_with_args(&mut self, _then_index: usize, _else_index: usize, _then_n: usize, _else_n: usize) {
|
fn br_if_with_args(&mut self, _then_index: usize, _else_index: usize, _then_n: usize, _else_n: usize) {
|
||||||
// fallback to no-arg br_if
|
// fallback to no-arg br_if
|
||||||
@ -47,6 +60,16 @@ pub trait IRBuilder {
|
|||||||
}
|
}
|
||||||
/// Optional: jump with explicit arg count; pops args from stack
|
/// Optional: jump with explicit arg count; pops args from stack
|
||||||
fn jump_with_args(&mut self, _target_index: usize, _n: usize) { self.jump_to(_target_index); }
|
fn jump_with_args(&mut self, _target_index: usize, _n: usize) { self.jump_to(_target_index); }
|
||||||
|
/// Optional: hint that function returns a boolean (b1) value (footing only)
|
||||||
|
fn hint_ret_bool(&mut self, _is_b1: bool) { }
|
||||||
|
|
||||||
|
// ==== Minimal local slots for Load/Store (i64 only) ====
|
||||||
|
/// Ensure an i64 local slot exists for the given index
|
||||||
|
fn ensure_local_i64(&mut self, _index: usize) { }
|
||||||
|
/// Store top-of-stack (normalized to i64) into local slot
|
||||||
|
fn store_local_i64(&mut self, _index: usize) { }
|
||||||
|
/// Load i64 from local slot and push to stack
|
||||||
|
fn load_local_i64(&mut self, _index: usize) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NoopBuilder {
|
pub struct NoopBuilder {
|
||||||
@ -72,6 +95,9 @@ impl IRBuilder for NoopBuilder {
|
|||||||
fn emit_jump(&mut self) { self.branches += 1; }
|
fn emit_jump(&mut self) { self.branches += 1; }
|
||||||
fn emit_branch(&mut self) { self.branches += 1; }
|
fn emit_branch(&mut self) { self.branches += 1; }
|
||||||
fn emit_return(&mut self) { self.rets += 1; }
|
fn emit_return(&mut self) { self.rets += 1; }
|
||||||
|
fn ensure_local_i64(&mut self, _index: usize) { /* no-op */ }
|
||||||
|
fn store_local_i64(&mut self, _index: usize) { self.consts += 1; }
|
||||||
|
fn load_local_i64(&mut self, _index: usize) { self.consts += 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
@ -88,11 +114,17 @@ pub struct CraneliftBuilder {
|
|||||||
blocks: Vec<cranelift_codegen::ir::Block>,
|
blocks: Vec<cranelift_codegen::ir::Block>,
|
||||||
current_block_index: Option<usize>,
|
current_block_index: Option<usize>,
|
||||||
block_param_counts: std::collections::HashMap<usize, usize>,
|
block_param_counts: std::collections::HashMap<usize, usize>,
|
||||||
|
// Local stack slots for minimal Load/Store lowering (i64 only)
|
||||||
|
local_slots: std::collections::HashMap<usize, cranelift_codegen::ir::StackSlot>,
|
||||||
// Finalized function pointer (if any)
|
// Finalized function pointer (if any)
|
||||||
compiled_closure: Option<std::sync::Arc<dyn Fn(&[crate::backend::vm::VMValue]) -> crate::backend::vm::VMValue + Send + Sync>>,
|
compiled_closure: Option<std::sync::Arc<dyn Fn(&[crate::jit::abi::JitValue]) -> crate::jit::abi::JitValue + Send + Sync>>,
|
||||||
// Desired simple ABI (Phase 10_c minimal): i64 params count and i64 return
|
// Desired simple ABI (Phase 10_c minimal): i64 params count and i64 return
|
||||||
desired_argc: usize,
|
desired_argc: usize,
|
||||||
desired_has_ret: bool,
|
desired_has_ret: bool,
|
||||||
|
desired_ret_is_f64: bool,
|
||||||
|
typed_sig_prepared: bool,
|
||||||
|
// Return-type hint: function returns boolean (footing only; ABI remains i64 for now)
|
||||||
|
ret_hint_is_b1: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
@ -106,7 +138,7 @@ extern "C" fn nyash_host_stub0() -> i64 { 0 }
|
|||||||
extern "C" fn nyash_array_len(arr_param_index: i64) -> i64 {
|
extern "C" fn nyash_array_len(arr_param_index: i64) -> i64 {
|
||||||
// Interpret first arg as function param index and fetch from thread-local args
|
// Interpret first arg as function param index and fetch from thread-local args
|
||||||
if arr_param_index < 0 { return 0; }
|
if arr_param_index < 0 { return 0; }
|
||||||
crate::jit::rt::with_args(|args| {
|
crate::jit::rt::with_legacy_vm_args(|args| {
|
||||||
let idx = arr_param_index as usize;
|
let idx = arr_param_index as usize;
|
||||||
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(idx) {
|
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(idx) {
|
||||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
@ -121,7 +153,7 @@ extern "C" fn nyash_array_len(arr_param_index: i64) -> i64 {
|
|||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_array_push(arr_param_index: i64, val: i64) -> i64 {
|
extern "C" fn nyash_array_push(arr_param_index: i64, val: i64) -> i64 {
|
||||||
if arr_param_index < 0 { return 0; }
|
if arr_param_index < 0 { return 0; }
|
||||||
crate::jit::rt::with_args(|args| {
|
crate::jit::rt::with_legacy_vm_args(|args| {
|
||||||
let idx = arr_param_index as usize;
|
let idx = arr_param_index as usize;
|
||||||
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(idx) {
|
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(idx) {
|
||||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
@ -137,7 +169,7 @@ extern "C" fn nyash_array_push(arr_param_index: i64, val: i64) -> i64 {
|
|||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_array_get(arr_param_index: i64, idx: i64) -> i64 {
|
extern "C" fn nyash_array_get(arr_param_index: i64, idx: i64) -> i64 {
|
||||||
if arr_param_index < 0 { return 0; }
|
if arr_param_index < 0 { return 0; }
|
||||||
crate::jit::rt::with_args(|args| {
|
crate::jit::rt::with_legacy_vm_args(|args| {
|
||||||
let pidx = arr_param_index as usize;
|
let pidx = arr_param_index as usize;
|
||||||
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(pidx) {
|
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(pidx) {
|
||||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
@ -153,7 +185,7 @@ extern "C" fn nyash_array_get(arr_param_index: i64, idx: i64) -> i64 {
|
|||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_array_set(arr_param_index: i64, idx: i64, val: i64) -> i64 {
|
extern "C" fn nyash_array_set(arr_param_index: i64, idx: i64, val: i64) -> i64 {
|
||||||
if arr_param_index < 0 { return 0; }
|
if arr_param_index < 0 { return 0; }
|
||||||
crate::jit::rt::with_args(|args| {
|
crate::jit::rt::with_legacy_vm_args(|args| {
|
||||||
let pidx = arr_param_index as usize;
|
let pidx = arr_param_index as usize;
|
||||||
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(pidx) {
|
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(pidx) {
|
||||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
@ -174,7 +206,7 @@ extern "C" fn nyash_map_set(_map: u64, _key: i64, _val: i64) -> i64 { 0 }
|
|||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_map_size(map_param_index: i64) -> i64 {
|
extern "C" fn nyash_map_size(map_param_index: i64) -> i64 {
|
||||||
if map_param_index < 0 { return 0; }
|
if map_param_index < 0 { return 0; }
|
||||||
crate::jit::rt::with_args(|args| {
|
crate::jit::rt::with_legacy_vm_args(|args| {
|
||||||
let idx = map_param_index as usize;
|
let idx = map_param_index as usize;
|
||||||
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(idx) {
|
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(idx) {
|
||||||
if let Some(mb) = b.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
if let Some(mb) = b.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
||||||
@ -187,8 +219,234 @@ extern "C" fn nyash_map_size(map_param_index: i64) -> i64 {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === Handle-based externs (10.7c) ===
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
extern "C" fn nyash_array_len_h(handle: u64) -> i64 {
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
|
if let Some(ib) = arr.length().as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return ib.value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
extern "C" fn nyash_array_push_h(handle: u64, val: i64) -> i64 {
|
||||||
|
// Policy/Events: classify and decide
|
||||||
|
use crate::jit::hostcall_registry::{classify, HostcallKind};
|
||||||
|
let sym = crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H;
|
||||||
|
match (classify(sym), crate::jit::policy::current().read_only) {
|
||||||
|
(HostcallKind::Mutating, true) => {
|
||||||
|
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": sym, "decision":"fallback"}));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
|
let ib = crate::box_trait::IntegerBox::new(val);
|
||||||
|
let _ = arr.push(Box::new(ib));
|
||||||
|
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": sym, "decision":"allow"}));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
extern "C" fn nyash_array_get_h(handle: u64, idx: i64) -> i64 {
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
|
let val = arr.get(Box::new(crate::box_trait::IntegerBox::new(idx)));
|
||||||
|
if let Some(ib) = val.as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return ib.value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
extern "C" fn nyash_array_last_h(handle: u64) -> i64 {
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
|
// Return last element as i64 if IntegerBox, else 0
|
||||||
|
if let Ok(items) = arr.items.read() {
|
||||||
|
if let Some(last) = items.last() {
|
||||||
|
if let Some(ib) = last.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
|
||||||
|
return ib.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
extern "C" fn nyash_array_set_h(handle: u64, idx: i64, val: i64) -> i64 {
|
||||||
|
use crate::jit::hostcall_registry::{classify, HostcallKind};
|
||||||
|
let sym = crate::jit::r#extern::collections::SYM_ARRAY_SET_H;
|
||||||
|
if classify(sym) == HostcallKind::Mutating && crate::jit::policy::current().read_only {
|
||||||
|
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": sym, "decision":"fallback"}));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
|
let _ = arr.set(
|
||||||
|
Box::new(crate::box_trait::IntegerBox::new(idx)),
|
||||||
|
Box::new(crate::box_trait::IntegerBox::new(val)),
|
||||||
|
);
|
||||||
|
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": sym, "decision":"allow"}));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
extern "C" fn nyash_map_size_h(handle: u64) -> i64 {
|
||||||
|
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_SIZE_H, "decision":"allow"}));
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
||||||
|
if let Some(ib) = map.size().as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return ib.value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
extern "C" fn nyash_map_get_h(handle: u64, key: i64) -> i64 {
|
||||||
|
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_GET_H, "decision":"allow"}));
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
||||||
|
let key_box = Box::new(crate::box_trait::IntegerBox::new(key));
|
||||||
|
let val = map.get(key_box);
|
||||||
|
if let Some(ib) = val.as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return ib.value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
extern "C" fn nyash_map_set_h(handle: u64, key: i64, val: i64) -> i64 {
|
||||||
|
use crate::jit::hostcall_registry::{classify, HostcallKind};
|
||||||
|
let sym = crate::jit::r#extern::collections::SYM_MAP_SET_H;
|
||||||
|
if classify(sym) == HostcallKind::Mutating && crate::jit::policy::current().read_only {
|
||||||
|
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": sym, "decision":"fallback"}));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
||||||
|
let key_box = Box::new(crate::box_trait::IntegerBox::new(key));
|
||||||
|
let val_box = Box::new(crate::box_trait::IntegerBox::new(val));
|
||||||
|
let _ = map.set(key_box, val_box);
|
||||||
|
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": sym, "decision":"allow"}));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
extern "C" fn nyash_map_has_h(handle: u64, key: i64) -> i64 {
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
||||||
|
let key_box = Box::new(crate::box_trait::IntegerBox::new(key));
|
||||||
|
let val = map.get(key_box);
|
||||||
|
// Treat presence if result is not Void
|
||||||
|
let is_present = !val.as_any().is::<crate::box_trait::VoidBox>();
|
||||||
|
return if is_present { 1 } else { 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
extern "C" fn nyash_any_length_h(handle: u64) -> i64 {
|
||||||
|
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_LEN_H, "decision":"allow"}));
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
// Array length
|
||||||
|
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
|
if let Some(ib) = arr.length().as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return ib.value; }
|
||||||
|
}
|
||||||
|
// String length
|
||||||
|
if let Some(sb) = obj.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||||
|
return sb.value.len() as i64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
extern "C" fn nyash_any_is_empty_h(handle: u64) -> i64 {
|
||||||
|
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, "decision":"allow"}));
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
// Array empty?
|
||||||
|
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
|
if let Ok(items) = arr.items.read() { return if items.is_empty() { 1 } else { 0 }; }
|
||||||
|
}
|
||||||
|
// String empty?
|
||||||
|
if let Some(sb) = obj.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||||
|
return if sb.value.is_empty() { 1 } else { 0 };
|
||||||
|
}
|
||||||
|
// Map empty?
|
||||||
|
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
||||||
|
if let Some(ib) = map.size().as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return if ib.value == 0 { 1 } else { 0 }; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
extern "C" fn nyash_string_charcode_at_h(handle: u64, idx: i64) -> i64 {
|
||||||
|
crate::jit::events::emit("hostcall", "<jit>", None, None, serde_json::json!({"id": crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, "decision":"allow"}));
|
||||||
|
if idx < 0 { return -1; }
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(sb) = obj.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||||
|
let s = &sb.value;
|
||||||
|
let i = idx as usize;
|
||||||
|
if i < s.len() {
|
||||||
|
// Return UTF-8 byte at index (ASCII-friendly PoC)
|
||||||
|
return s.as_bytes()[i] as i64;
|
||||||
|
} else { return -1; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
impl IRBuilder for CraneliftBuilder {
|
impl IRBuilder for CraneliftBuilder {
|
||||||
|
fn prepare_signature_typed(&mut self, params: &[ParamKind], ret_is_f64: bool) {
|
||||||
|
use cranelift_codegen::ir::{AbiParam, Signature, types};
|
||||||
|
fn abi_param_for_kind(k: ParamKind, cfg: &crate::jit::config::JitConfig) -> cranelift_codegen::ir::AbiParam {
|
||||||
|
use cranelift_codegen::ir::types;
|
||||||
|
match k {
|
||||||
|
ParamKind::I64 => cranelift_codegen::ir::AbiParam::new(types::I64),
|
||||||
|
ParamKind::F64 => cranelift_codegen::ir::AbiParam::new(types::F64),
|
||||||
|
ParamKind::B1 => {
|
||||||
|
let _ = cfg.native_bool_abi;
|
||||||
|
#[cfg(feature = "jit-b1-abi")]
|
||||||
|
{
|
||||||
|
if crate::jit::config::probe_capabilities().supports_b1_sig && cfg.native_bool_abi { return cranelift_codegen::ir::AbiParam::new(types::B1); }
|
||||||
|
}
|
||||||
|
cranelift_codegen::ir::AbiParam::new(types::I64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.desired_argc = params.len();
|
||||||
|
self.desired_has_ret = true;
|
||||||
|
self.desired_ret_is_f64 = ret_is_f64;
|
||||||
|
let call_conv = self.module.isa().default_call_conv();
|
||||||
|
let mut sig = Signature::new(call_conv);
|
||||||
|
let cfg_now = crate::jit::config::current();
|
||||||
|
for &k in params { sig.params.push(abi_param_for_kind(k, &cfg_now)); }
|
||||||
|
if self.desired_has_ret {
|
||||||
|
// Decide return ABI: prefer F64 if requested; otherwise Bool may use B1 when supported
|
||||||
|
if self.desired_ret_is_f64 { sig.returns.push(AbiParam::new(types::F64)); }
|
||||||
|
else {
|
||||||
|
let mut used_b1 = false;
|
||||||
|
#[cfg(feature = "jit-b1-abi")]
|
||||||
|
{
|
||||||
|
let cfg_now = crate::jit::config::current();
|
||||||
|
if crate::jit::config::probe_capabilities().supports_b1_sig && cfg_now.native_bool_abi && self.ret_hint_is_b1 {
|
||||||
|
sig.returns.push(AbiParam::new(types::B1));
|
||||||
|
used_b1 = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !used_b1 { sig.returns.push(AbiParam::new(types::I64)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.ctx.func.signature = sig;
|
||||||
|
self.typed_sig_prepared = true;
|
||||||
|
}
|
||||||
fn emit_param_i64(&mut self, index: usize) {
|
fn emit_param_i64(&mut self, index: usize) {
|
||||||
if let Some(v) = self.entry_param(index) {
|
if let Some(v) = self.entry_param(index) {
|
||||||
self.value_stack.push(v);
|
self.value_stack.push(v);
|
||||||
@ -197,6 +455,7 @@ impl IRBuilder for CraneliftBuilder {
|
|||||||
fn prepare_signature_i64(&mut self, argc: usize, has_ret: bool) {
|
fn prepare_signature_i64(&mut self, argc: usize, has_ret: bool) {
|
||||||
self.desired_argc = argc;
|
self.desired_argc = argc;
|
||||||
self.desired_has_ret = has_ret;
|
self.desired_has_ret = has_ret;
|
||||||
|
self.desired_ret_is_f64 = crate::jit::config::current().native_f64;
|
||||||
}
|
}
|
||||||
fn begin_function(&mut self, name: &str) {
|
fn begin_function(&mut self, name: &str) {
|
||||||
use cranelift_codegen::ir::{AbiParam, Signature, types};
|
use cranelift_codegen::ir::{AbiParam, Signature, types};
|
||||||
@ -204,14 +463,31 @@ impl IRBuilder for CraneliftBuilder {
|
|||||||
|
|
||||||
self.current_name = Some(name.to_string());
|
self.current_name = Some(name.to_string());
|
||||||
self.value_stack.clear();
|
self.value_stack.clear();
|
||||||
// Keep any pre-created blocks (from prepare_blocks)
|
// Keep any pre-created blocks (from prepare_blocks or typed signature)
|
||||||
|
|
||||||
// Minimal signature: (i64 x argc) -> i64? (Core-1 integer path)
|
// Build default signature only if a typed one wasn't prepared
|
||||||
let call_conv = self.module.isa().default_call_conv();
|
if !self.typed_sig_prepared {
|
||||||
let mut sig = Signature::new(call_conv);
|
// Minimal signature: (i64 x argc) -> i64? (Core-1 integer path)
|
||||||
for _ in 0..self.desired_argc { sig.params.push(AbiParam::new(types::I64)); }
|
let call_conv = self.module.isa().default_call_conv();
|
||||||
if self.desired_has_ret { sig.returns.push(AbiParam::new(types::I64)); }
|
let mut sig = Signature::new(call_conv);
|
||||||
self.ctx.func.signature = sig;
|
for _ in 0..self.desired_argc { sig.params.push(AbiParam::new(types::I64)); }
|
||||||
|
if self.desired_has_ret {
|
||||||
|
if self.desired_ret_is_f64 { sig.returns.push(AbiParam::new(types::F64)); }
|
||||||
|
else {
|
||||||
|
let mut used_b1 = false;
|
||||||
|
#[cfg(feature = "jit-b1-abi")]
|
||||||
|
{
|
||||||
|
let cfg_now = crate::jit::config::current();
|
||||||
|
if crate::jit::config::probe_capabilities().supports_b1_sig && cfg_now.native_bool_abi && self.ret_hint_is_b1 {
|
||||||
|
sig.returns.push(AbiParam::new(types::B1));
|
||||||
|
used_b1 = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !used_b1 { sig.returns.push(AbiParam::new(types::I64)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.ctx.func.signature = sig;
|
||||||
|
}
|
||||||
self.ctx.func.name = cranelift_codegen::ir::UserFuncName::user(0, 0);
|
self.ctx.func.name = cranelift_codegen::ir::UserFuncName::user(0, 0);
|
||||||
|
|
||||||
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
||||||
@ -253,15 +529,69 @@ impl IRBuilder for CraneliftBuilder {
|
|||||||
// Get finalized code pointer and wrap into a safe closure
|
// Get finalized code pointer and wrap into a safe closure
|
||||||
let code = self.module.get_finalized_function(func_id);
|
let code = self.module.get_finalized_function(func_id);
|
||||||
|
|
||||||
// SAFETY: We compiled a function with simple i64 ABI; we still call without args for now
|
// SAFETY: We compiled a function with simple (i64 x N) -> i64/f64 というABIだよ。
|
||||||
|
// ランタイムでは JitValue から i64 へ正規化して、引数個数に応じた関数型にtransmuteして呼び出すにゃ。
|
||||||
|
let argc = self.desired_argc;
|
||||||
|
let ret_is_f64 = self.desired_has_ret && self.desired_ret_is_f64;
|
||||||
|
// capture code as usize to avoid raw pointer Send/Sync issues in closure
|
||||||
|
let code_usize = code as usize;
|
||||||
unsafe {
|
unsafe {
|
||||||
let f: extern "C" fn() -> i64 = std::mem::transmute(code);
|
let closure = std::sync::Arc::new(move |args: &[crate::jit::abi::JitValue]| -> crate::jit::abi::JitValue {
|
||||||
let closure = std::sync::Arc::new(move |_args: &[crate::backend::vm::VMValue]| -> crate::backend::vm::VMValue {
|
// 正規化: 足りなければ0で埋め、余分は切り捨て
|
||||||
let v = f();
|
let mut a: [i64; 6] = [0; 6];
|
||||||
crate::backend::vm::VMValue::Integer(v)
|
let take = core::cmp::min(core::cmp::min(argc, 6), args.len());
|
||||||
|
for i in 0..take {
|
||||||
|
a[i] = match args[i] { crate::jit::abi::JitValue::I64(v) => v, crate::jit::abi::JitValue::Bool(b) => if b {1} else {0}, crate::jit::abi::JitValue::F64(f) => f as i64, crate::jit::abi::JitValue::Handle(h) => h as i64 };
|
||||||
|
}
|
||||||
|
let ret_i64 = match argc {
|
||||||
|
0 => {
|
||||||
|
let f: extern "C" fn() -> i64 = std::mem::transmute(code_usize);
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
let f: extern "C" fn(i64) -> i64 = std::mem::transmute(code_usize);
|
||||||
|
f(a[0])
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
let f: extern "C" fn(i64, i64) -> i64 = std::mem::transmute(code_usize);
|
||||||
|
f(a[0], a[1])
|
||||||
|
}
|
||||||
|
3 => {
|
||||||
|
let f: extern "C" fn(i64, i64, i64) -> i64 = std::mem::transmute(code_usize);
|
||||||
|
f(a[0], a[1], a[2])
|
||||||
|
}
|
||||||
|
4 => {
|
||||||
|
let f: extern "C" fn(i64, i64, i64, i64) -> i64 = std::mem::transmute(code_usize);
|
||||||
|
f(a[0], a[1], a[2], a[3])
|
||||||
|
}
|
||||||
|
5 => {
|
||||||
|
let f: extern "C" fn(i64, i64, i64, i64, i64) -> i64 = std::mem::transmute(code_usize);
|
||||||
|
f(a[0], a[1], a[2], a[3], a[4])
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// 上限6(十分なPoC)
|
||||||
|
let f: extern "C" fn(i64, i64, i64, i64, i64, i64) -> i64 = std::mem::transmute(code_usize);
|
||||||
|
f(a[0], a[1], a[2], a[3], a[4], a[5])
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if ret_is_f64 {
|
||||||
|
let ret_f64 = match argc {
|
||||||
|
0 => { let f: extern "C" fn() -> f64 = std::mem::transmute(code_usize); f() }
|
||||||
|
1 => { let f: extern "C" fn(i64) -> f64 = std::mem::transmute(code_usize); f(a[0]) }
|
||||||
|
2 => { let f: extern "C" fn(i64,i64) -> f64 = std::mem::transmute(code_usize); f(a[0],a[1]) }
|
||||||
|
3 => { let f: extern "C" fn(i64,i64,i64) -> f64 = std::mem::transmute(code_usize); f(a[0],a[1],a[2]) }
|
||||||
|
4 => { let f: extern "C" fn(i64,i64,i64,i64) -> f64 = std::mem::transmute(code_usize); f(a[0],a[1],a[2],a[3]) }
|
||||||
|
5 => { let f: extern "C" fn(i64,i64,i64,i64,i64) -> f64 = std::mem::transmute(code_usize); f(a[0],a[1],a[2],a[3],a[4]) }
|
||||||
|
_ => { let f: extern "C" fn(i64,i64,i64,i64,i64,i64) -> f64 = std::mem::transmute(code_usize); f(a[0],a[1],a[2],a[3],a[4],a[5]) }
|
||||||
|
};
|
||||||
|
return crate::jit::abi::JitValue::F64(ret_f64);
|
||||||
|
}
|
||||||
|
crate::jit::abi::JitValue::I64(ret_i64)
|
||||||
});
|
});
|
||||||
self.compiled_closure = Some(closure);
|
self.compiled_closure = Some(closure);
|
||||||
}
|
}
|
||||||
|
// Reset typed signature flag for next function
|
||||||
|
self.typed_sig_prepared = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_const_i64(&mut self, val: i64) {
|
fn emit_const_i64(&mut self, val: i64) {
|
||||||
@ -277,22 +607,56 @@ impl IRBuilder for CraneliftBuilder {
|
|||||||
fb.finalize();
|
fb.finalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_const_f64(&mut self, _val: f64) { self.stats.0 += 1; /* not yet supported in Core-1 */ }
|
fn emit_const_f64(&mut self, val: f64) {
|
||||||
|
self.stats.0 += 1;
|
||||||
fn emit_binop(&mut self, op: BinOpKind) {
|
if !crate::jit::config::current().native_f64 { return; }
|
||||||
|
use cranelift_codegen::ir::types;
|
||||||
use cranelift_frontend::FunctionBuilder;
|
use cranelift_frontend::FunctionBuilder;
|
||||||
if self.value_stack.len() < 2 { return; }
|
|
||||||
let rhs = self.value_stack.pop().unwrap();
|
|
||||||
let lhs = self.value_stack.pop().unwrap();
|
|
||||||
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
||||||
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
|
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
|
||||||
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
|
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
|
||||||
let res = match op {
|
let v = fb.ins().f64const(val);
|
||||||
BinOpKind::Add => fb.ins().iadd(lhs, rhs),
|
self.value_stack.push(v);
|
||||||
BinOpKind::Sub => fb.ins().isub(lhs, rhs),
|
fb.finalize();
|
||||||
BinOpKind::Mul => fb.ins().imul(lhs, rhs),
|
}
|
||||||
BinOpKind::Div => fb.ins().sdiv(lhs, rhs),
|
|
||||||
BinOpKind::Mod => fb.ins().srem(lhs, rhs),
|
fn emit_binop(&mut self, op: BinOpKind) {
|
||||||
|
use cranelift_frontend::FunctionBuilder;
|
||||||
|
use cranelift_codegen::ir::types;
|
||||||
|
if self.value_stack.len() < 2 { return; }
|
||||||
|
let mut rhs = self.value_stack.pop().unwrap();
|
||||||
|
let mut lhs = self.value_stack.pop().unwrap();
|
||||||
|
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
||||||
|
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
|
||||||
|
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
|
||||||
|
// Choose op by operand type (I64 vs F64). If mixed and native_f64, promote to F64.
|
||||||
|
let lty = fb.func.dfg.value_type(lhs);
|
||||||
|
let rty = fb.func.dfg.value_type(rhs);
|
||||||
|
let native_f64 = crate::jit::config::current().native_f64;
|
||||||
|
let mut use_f64 = native_f64 && (lty == types::F64 || rty == types::F64);
|
||||||
|
if use_f64 {
|
||||||
|
if lty != types::F64 { lhs = fb.ins().fcvt_from_sint(types::F64, lhs); }
|
||||||
|
if rty != types::F64 { rhs = fb.ins().fcvt_from_sint(types::F64, rhs); }
|
||||||
|
}
|
||||||
|
let res = if use_f64 {
|
||||||
|
match op {
|
||||||
|
BinOpKind::Add => fb.ins().fadd(lhs, rhs),
|
||||||
|
BinOpKind::Sub => fb.ins().fsub(lhs, rhs),
|
||||||
|
BinOpKind::Mul => fb.ins().fmul(lhs, rhs),
|
||||||
|
BinOpKind::Div => fb.ins().fdiv(lhs, rhs),
|
||||||
|
BinOpKind::Mod => {
|
||||||
|
// Minimal path: produce 0.0 (fmod未実装)。将来はホストコール/Libcallに切替
|
||||||
|
fb.ins().f64const(0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match op {
|
||||||
|
BinOpKind::Add => fb.ins().iadd(lhs, rhs),
|
||||||
|
BinOpKind::Sub => fb.ins().isub(lhs, rhs),
|
||||||
|
BinOpKind::Mul => fb.ins().imul(lhs, rhs),
|
||||||
|
BinOpKind::Div => fb.ins().sdiv(lhs, rhs),
|
||||||
|
BinOpKind::Mod => fb.ins().srem(lhs, rhs),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
self.value_stack.push(res);
|
self.value_stack.push(res);
|
||||||
self.stats.1 += 1;
|
self.stats.1 += 1;
|
||||||
@ -300,24 +664,42 @@ impl IRBuilder for CraneliftBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn emit_compare(&mut self, op: CmpKind) {
|
fn emit_compare(&mut self, op: CmpKind) {
|
||||||
use cranelift_codegen::ir::{condcodes::IntCC};
|
use cranelift_codegen::ir::{condcodes::{IntCC, FloatCC}, types};
|
||||||
use cranelift_frontend::FunctionBuilder;
|
use cranelift_frontend::FunctionBuilder;
|
||||||
if self.value_stack.len() < 2 { return; }
|
if self.value_stack.len() < 2 { return; }
|
||||||
let rhs = self.value_stack.pop().unwrap();
|
let mut rhs = self.value_stack.pop().unwrap();
|
||||||
let lhs = self.value_stack.pop().unwrap();
|
let mut lhs = self.value_stack.pop().unwrap();
|
||||||
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
||||||
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
|
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
|
||||||
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
|
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
|
||||||
let cc = match op {
|
let lty = fb.func.dfg.value_type(lhs);
|
||||||
CmpKind::Eq => IntCC::Equal,
|
let rty = fb.func.dfg.value_type(rhs);
|
||||||
CmpKind::Ne => IntCC::NotEqual,
|
let native_f64 = crate::jit::config::current().native_f64;
|
||||||
CmpKind::Lt => IntCC::SignedLessThan,
|
let use_f64 = native_f64 && (lty == types::F64 || rty == types::F64);
|
||||||
CmpKind::Le => IntCC::SignedLessThanOrEqual,
|
let b1 = if use_f64 {
|
||||||
CmpKind::Gt => IntCC::SignedGreaterThan,
|
if lty != types::F64 { lhs = fb.ins().fcvt_from_sint(types::F64, lhs); }
|
||||||
CmpKind::Ge => IntCC::SignedGreaterThanOrEqual,
|
if rty != types::F64 { rhs = fb.ins().fcvt_from_sint(types::F64, rhs); }
|
||||||
|
let cc = match op {
|
||||||
|
CmpKind::Eq => FloatCC::Equal,
|
||||||
|
CmpKind::Ne => FloatCC::NotEqual,
|
||||||
|
CmpKind::Lt => FloatCC::LessThan,
|
||||||
|
CmpKind::Le => FloatCC::LessThanOrEqual,
|
||||||
|
CmpKind::Gt => FloatCC::GreaterThan,
|
||||||
|
CmpKind::Ge => FloatCC::GreaterThanOrEqual,
|
||||||
|
};
|
||||||
|
fb.ins().fcmp(cc, lhs, rhs)
|
||||||
|
} else {
|
||||||
|
let cc = match op {
|
||||||
|
CmpKind::Eq => IntCC::Equal,
|
||||||
|
CmpKind::Ne => IntCC::NotEqual,
|
||||||
|
CmpKind::Lt => IntCC::SignedLessThan,
|
||||||
|
CmpKind::Le => IntCC::SignedLessThanOrEqual,
|
||||||
|
CmpKind::Gt => IntCC::SignedGreaterThan,
|
||||||
|
CmpKind::Ge => IntCC::SignedGreaterThanOrEqual,
|
||||||
|
};
|
||||||
|
fb.ins().icmp(cc, lhs, rhs)
|
||||||
};
|
};
|
||||||
let b1 = fb.ins().icmp(cc, lhs, rhs);
|
// Keep b1 on the stack; users (branch) can consume directly
|
||||||
// Keep b1 on the stack; users (branch) can consume directly, arithmetic should not assume compare value as i64
|
|
||||||
self.value_stack.push(b1);
|
self.value_stack.push(b1);
|
||||||
self.stats.2 += 1;
|
self.stats.2 += 1;
|
||||||
fb.finalize();
|
fb.finalize();
|
||||||
@ -331,13 +713,44 @@ impl IRBuilder for CraneliftBuilder {
|
|||||||
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
||||||
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
|
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
|
||||||
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
|
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
|
||||||
if let Some(v) = self.value_stack.pop() {
|
if let Some(mut v) = self.value_stack.pop() {
|
||||||
|
// Normalize return type if needed
|
||||||
|
let ret_ty = fb.func.signature.returns.get(0).map(|p| p.value_type).unwrap_or(cranelift_codegen::ir::types::I64);
|
||||||
|
let v_ty = fb.func.dfg.value_type(v);
|
||||||
|
if v_ty != ret_ty {
|
||||||
|
use cranelift_codegen::ir::types;
|
||||||
|
if ret_ty == types::F64 && v_ty == types::I64 {
|
||||||
|
v = fb.ins().fcvt_from_sint(types::F64, v);
|
||||||
|
} else if ret_ty == types::I64 && v_ty == types::F64 {
|
||||||
|
v = fb.ins().fcvt_to_sint(types::I64, v);
|
||||||
|
} else if ret_ty == types::I64 {
|
||||||
|
// If returning i64 but we currently have a boolean, normalize via select(b1,1,0)
|
||||||
|
use cranelift_codegen::ir::types;
|
||||||
|
let one = fb.ins().iconst(types::I64, 1);
|
||||||
|
let zero = fb.ins().iconst(types::I64, 0);
|
||||||
|
v = fb.ins().select(v, one, zero);
|
||||||
|
}
|
||||||
|
#[cfg(feature = "jit-b1-abi")]
|
||||||
|
{
|
||||||
|
use cranelift_codegen::ir::types;
|
||||||
|
if ret_ty == types::B1 && v_ty == types::I64 {
|
||||||
|
use cranelift_codegen::ir::condcodes::IntCC;
|
||||||
|
v = fb.ins().icmp_imm(IntCC::NotEqual, v, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
fb.ins().return_(&[v]);
|
fb.ins().return_(&[v]);
|
||||||
} else {
|
} else {
|
||||||
// Return 0 if empty stack (defensive)
|
// Return 0 if empty stack (defensive)
|
||||||
use cranelift_codegen::ir::types;
|
use cranelift_codegen::ir::types;
|
||||||
let zero = fb.ins().iconst(types::I64, 0);
|
let ret_ty = fb.func.signature.returns.get(0).map(|p| p.value_type).unwrap_or(types::I64);
|
||||||
fb.ins().return_(&[zero]);
|
if ret_ty == types::F64 {
|
||||||
|
let z = fb.ins().f64const(0.0);
|
||||||
|
fb.ins().return_(&[z]);
|
||||||
|
} else {
|
||||||
|
let zero = fb.ins().iconst(types::I64, 0);
|
||||||
|
fb.ins().return_(&[zero]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fb.finalize();
|
fb.finalize();
|
||||||
}
|
}
|
||||||
@ -416,14 +829,18 @@ impl IRBuilder for CraneliftBuilder {
|
|||||||
let cond_b1 = if let Some(v) = self.value_stack.pop() {
|
let cond_b1 = if let Some(v) = self.value_stack.pop() {
|
||||||
let ty = fb.func.dfg.value_type(v);
|
let ty = fb.func.dfg.value_type(v);
|
||||||
if ty == types::I64 {
|
if ty == types::I64 {
|
||||||
fb.ins().icmp_imm(IntCC::NotEqual, v, 0)
|
let out = fb.ins().icmp_imm(IntCC::NotEqual, v, 0);
|
||||||
|
crate::jit::rt::b1_norm_inc(1);
|
||||||
|
out
|
||||||
} else {
|
} else {
|
||||||
// assume already b1
|
// assume already b1
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let zero = fb.ins().iconst(types::I64, 0);
|
let zero = fb.ins().iconst(types::I64, 0);
|
||||||
fb.ins().icmp_imm(IntCC::NotEqual, zero, 0)
|
let out = fb.ins().icmp_imm(IntCC::NotEqual, zero, 0);
|
||||||
|
crate::jit::rt::b1_norm_inc(1);
|
||||||
|
out
|
||||||
};
|
};
|
||||||
fb.ins().brif(cond_b1, self.blocks[then_index], &[], self.blocks[else_index], &[]);
|
fb.ins().brif(cond_b1, self.blocks[then_index], &[], self.blocks[else_index], &[]);
|
||||||
self.stats.3 += 1;
|
self.stats.3 += 1;
|
||||||
@ -440,34 +857,60 @@ impl IRBuilder for CraneliftBuilder {
|
|||||||
fb.finalize();
|
fb.finalize();
|
||||||
}
|
}
|
||||||
fn ensure_block_param_i64(&mut self, index: usize) {
|
fn ensure_block_param_i64(&mut self, index: usize) {
|
||||||
|
self.ensure_block_params_i64(index, 1);
|
||||||
|
}
|
||||||
|
fn ensure_block_params_i64(&mut self, index: usize, needed: usize) {
|
||||||
use cranelift_codegen::ir::types;
|
use cranelift_codegen::ir::types;
|
||||||
use cranelift_frontend::FunctionBuilder;
|
use cranelift_frontend::FunctionBuilder;
|
||||||
if index >= self.blocks.len() { return; }
|
if index >= self.blocks.len() { return; }
|
||||||
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
||||||
let count = self.block_param_counts.get(&index).copied().unwrap_or(0);
|
let have = self.block_param_counts.get(&index).copied().unwrap_or(0);
|
||||||
if count == 0 {
|
if needed > have {
|
||||||
let b = self.blocks[index];
|
let b = self.blocks[index];
|
||||||
let _v = fb.append_block_param(b, types::I64);
|
for _ in have..needed {
|
||||||
self.block_param_counts.insert(index, 1);
|
let _v = fb.append_block_param(b, types::I64);
|
||||||
|
}
|
||||||
|
self.block_param_counts.insert(index, needed);
|
||||||
}
|
}
|
||||||
fb.finalize();
|
fb.finalize();
|
||||||
}
|
}
|
||||||
fn push_block_param_i64(&mut self) {
|
fn ensure_block_params_b1(&mut self, index: usize, needed: usize) {
|
||||||
|
// Store as i64 block params for ABI stability; consumers can convert to b1
|
||||||
|
self.ensure_block_params_i64(index, needed);
|
||||||
|
}
|
||||||
|
fn push_block_param_i64_at(&mut self, pos: usize) {
|
||||||
use cranelift_frontend::FunctionBuilder;
|
use cranelift_frontend::FunctionBuilder;
|
||||||
use cranelift_codegen::ir::types;
|
use cranelift_codegen::ir::types;
|
||||||
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
||||||
// Determine current block
|
|
||||||
let b = if let Some(idx) = self.current_block_index { self.blocks[idx] } else if let Some(b) = self.entry_block { b } else { fb.create_block() };
|
let b = if let Some(idx) = self.current_block_index { self.blocks[idx] } else if let Some(b) = self.entry_block { b } else { fb.create_block() };
|
||||||
// Fetch first param if exists
|
// Ensure we have an active insertion point before emitting any instructions
|
||||||
|
fb.switch_to_block(b);
|
||||||
let params = fb.func.dfg.block_params(b).to_vec();
|
let params = fb.func.dfg.block_params(b).to_vec();
|
||||||
if let Some(v) = params.get(0).copied() { self.value_stack.push(v); }
|
if let Some(v) = params.get(pos).copied() { self.value_stack.push(v); }
|
||||||
else {
|
else {
|
||||||
// defensive: push 0
|
// defensive fallback
|
||||||
let zero = fb.ins().iconst(types::I64, 0);
|
let zero = fb.ins().iconst(types::I64, 0);
|
||||||
self.value_stack.push(zero);
|
self.value_stack.push(zero);
|
||||||
}
|
}
|
||||||
fb.finalize();
|
fb.finalize();
|
||||||
}
|
}
|
||||||
|
fn push_block_param_b1_at(&mut self, pos: usize) {
|
||||||
|
use cranelift_frontend::FunctionBuilder;
|
||||||
|
use cranelift_codegen::ir::{types, condcodes::IntCC};
|
||||||
|
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
||||||
|
let b = if let Some(idx) = self.current_block_index { self.blocks[idx] } else if let Some(b) = self.entry_block { b } else { fb.create_block() };
|
||||||
|
let params = fb.func.dfg.block_params(b).to_vec();
|
||||||
|
if let Some(v) = params.get(pos).copied() {
|
||||||
|
let ty = fb.func.dfg.value_type(v);
|
||||||
|
let b1 = if ty == types::I64 { fb.ins().icmp_imm(IntCC::NotEqual, v, 0) } else { v };
|
||||||
|
self.value_stack.push(b1);
|
||||||
|
} else {
|
||||||
|
let zero = fb.ins().iconst(types::I64, 0);
|
||||||
|
let b1 = fb.ins().icmp_imm(IntCC::NotEqual, zero, 0);
|
||||||
|
self.value_stack.push(b1);
|
||||||
|
}
|
||||||
|
fb.finalize();
|
||||||
|
}
|
||||||
fn br_if_with_args(&mut self, then_index: usize, else_index: usize, then_n: usize, else_n: usize) {
|
fn br_if_with_args(&mut self, then_index: usize, else_index: usize, then_n: usize, else_n: usize) {
|
||||||
use cranelift_codegen::ir::{types, condcodes::IntCC};
|
use cranelift_codegen::ir::{types, condcodes::IntCC};
|
||||||
use cranelift_frontend::FunctionBuilder;
|
use cranelift_frontend::FunctionBuilder;
|
||||||
@ -478,10 +921,12 @@ impl IRBuilder for CraneliftBuilder {
|
|||||||
// Condition
|
// Condition
|
||||||
let cond_b1 = if let Some(v) = self.value_stack.pop() {
|
let cond_b1 = if let Some(v) = self.value_stack.pop() {
|
||||||
let ty = fb.func.dfg.value_type(v);
|
let ty = fb.func.dfg.value_type(v);
|
||||||
if ty == types::I64 { fb.ins().icmp_imm(IntCC::NotEqual, v, 0) } else { v }
|
if ty == types::I64 { let out = fb.ins().icmp_imm(IntCC::NotEqual, v, 0); crate::jit::rt::b1_norm_inc(1); out } else { v }
|
||||||
} else {
|
} else {
|
||||||
let zero = fb.ins().iconst(types::I64, 0);
|
let zero = fb.ins().iconst(types::I64, 0);
|
||||||
fb.ins().icmp_imm(IntCC::NotEqual, zero, 0)
|
let out = fb.ins().icmp_imm(IntCC::NotEqual, zero, 0);
|
||||||
|
crate::jit::rt::b1_norm_inc(1);
|
||||||
|
out
|
||||||
};
|
};
|
||||||
// Pop else args then then args (so stack order can be value-friendly)
|
// Pop else args then then args (so stack order can be value-friendly)
|
||||||
let mut else_args: Vec<cranelift_codegen::ir::Value> = Vec::new();
|
let mut else_args: Vec<cranelift_codegen::ir::Value> = Vec::new();
|
||||||
@ -507,6 +952,58 @@ impl IRBuilder for CraneliftBuilder {
|
|||||||
self.stats.3 += 1;
|
self.stats.3 += 1;
|
||||||
fb.finalize();
|
fb.finalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hint_ret_bool(&mut self, is_b1: bool) { self.ret_hint_is_b1 = is_b1; }
|
||||||
|
|
||||||
|
fn ensure_local_i64(&mut self, index: usize) {
|
||||||
|
use cranelift_codegen::ir::{StackSlotData, StackSlotKind};
|
||||||
|
use cranelift_frontend::FunctionBuilder;
|
||||||
|
if self.local_slots.contains_key(&index) { return; }
|
||||||
|
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
||||||
|
let slot = fb.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 8));
|
||||||
|
self.local_slots.insert(index, slot);
|
||||||
|
fb.finalize();
|
||||||
|
}
|
||||||
|
fn store_local_i64(&mut self, index: usize) {
|
||||||
|
use cranelift_codegen::ir::{types, condcodes::IntCC};
|
||||||
|
use cranelift_frontend::FunctionBuilder;
|
||||||
|
if let Some(mut v) = self.value_stack.pop() {
|
||||||
|
// Ensure slot without overlapping FunctionBuilder borrows
|
||||||
|
if !self.local_slots.contains_key(&index) { self.ensure_local_i64(index); }
|
||||||
|
let slot = self.local_slots.get(&index).copied();
|
||||||
|
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
||||||
|
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
|
||||||
|
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
|
||||||
|
let ty = fb.func.dfg.value_type(v);
|
||||||
|
if ty != types::I64 {
|
||||||
|
if ty == types::F64 {
|
||||||
|
v = fb.ins().fcvt_to_sint(types::I64, v);
|
||||||
|
} else {
|
||||||
|
// Convert unknown ints/bools to i64 via (v!=0)?1:0
|
||||||
|
let one = fb.ins().iconst(types::I64, 1);
|
||||||
|
let zero = fb.ins().iconst(types::I64, 0);
|
||||||
|
let b1 = fb.ins().icmp_imm(IntCC::NotEqual, v, 0);
|
||||||
|
v = fb.ins().select(b1, one, zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(slot) = slot { fb.ins().stack_store(v, slot, 0); }
|
||||||
|
fb.finalize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn load_local_i64(&mut self, index: usize) {
|
||||||
|
use cranelift_codegen::ir::types;
|
||||||
|
use cranelift_frontend::FunctionBuilder;
|
||||||
|
if !self.local_slots.contains_key(&index) { self.ensure_local_i64(index); }
|
||||||
|
if let Some(&slot) = self.local_slots.get(&index) {
|
||||||
|
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
|
||||||
|
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
|
||||||
|
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
|
||||||
|
let v = fb.ins().stack_load(types::I64, slot, 0);
|
||||||
|
self.value_stack.push(v);
|
||||||
|
self.stats.0 += 1;
|
||||||
|
fb.finalize();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
@ -542,6 +1039,19 @@ impl CraneliftBuilder {
|
|||||||
builder.symbol(c::SYM_MAP_GET, nyash_map_get as *const u8);
|
builder.symbol(c::SYM_MAP_GET, nyash_map_get as *const u8);
|
||||||
builder.symbol(c::SYM_MAP_SET, nyash_map_set as *const u8);
|
builder.symbol(c::SYM_MAP_SET, nyash_map_set as *const u8);
|
||||||
builder.symbol(c::SYM_MAP_SIZE, nyash_map_size as *const u8);
|
builder.symbol(c::SYM_MAP_SIZE, nyash_map_size as *const u8);
|
||||||
|
// Handle-based symbols
|
||||||
|
builder.symbol(c::SYM_ARRAY_LEN_H, nyash_array_len_h as *const u8);
|
||||||
|
builder.symbol(c::SYM_ARRAY_GET_H, nyash_array_get_h as *const u8);
|
||||||
|
builder.symbol(c::SYM_ARRAY_SET_H, nyash_array_set_h as *const u8);
|
||||||
|
builder.symbol(c::SYM_ARRAY_PUSH_H, nyash_array_push_h as *const u8);
|
||||||
|
builder.symbol(c::SYM_ARRAY_LAST_H, nyash_array_last_h as *const u8);
|
||||||
|
builder.symbol(c::SYM_MAP_SIZE_H, nyash_map_size_h as *const u8);
|
||||||
|
builder.symbol(c::SYM_MAP_GET_H, nyash_map_get_h as *const u8);
|
||||||
|
builder.symbol(c::SYM_MAP_SET_H, nyash_map_set_h as *const u8);
|
||||||
|
builder.symbol(c::SYM_MAP_HAS_H, nyash_map_has_h as *const u8);
|
||||||
|
builder.symbol(c::SYM_ANY_LEN_H, nyash_any_length_h as *const u8);
|
||||||
|
builder.symbol(c::SYM_ANY_IS_EMPTY_H, nyash_any_is_empty_h as *const u8);
|
||||||
|
builder.symbol(c::SYM_STRING_CHARCODE_AT_H, nyash_string_charcode_at_h as *const u8);
|
||||||
}
|
}
|
||||||
let module = cranelift_jit::JITModule::new(builder);
|
let module = cranelift_jit::JITModule::new(builder);
|
||||||
let ctx = cranelift_codegen::Context::new();
|
let ctx = cranelift_codegen::Context::new();
|
||||||
@ -555,14 +1065,18 @@ impl CraneliftBuilder {
|
|||||||
blocks: Vec::new(),
|
blocks: Vec::new(),
|
||||||
current_block_index: None,
|
current_block_index: None,
|
||||||
block_param_counts: std::collections::HashMap::new(),
|
block_param_counts: std::collections::HashMap::new(),
|
||||||
|
local_slots: std::collections::HashMap::new(),
|
||||||
compiled_closure: None,
|
compiled_closure: None,
|
||||||
desired_argc: 0,
|
desired_argc: 0,
|
||||||
desired_has_ret: true,
|
desired_has_ret: true,
|
||||||
|
desired_ret_is_f64: false,
|
||||||
|
typed_sig_prepared: false,
|
||||||
|
ret_hint_is_b1: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Take ownership of compiled closure if available
|
/// Take ownership of compiled closure if available
|
||||||
pub fn take_compiled_closure(&mut self) -> Option<std::sync::Arc<dyn Fn(&[crate::backend::vm::VMValue]) -> crate::backend::vm::VMValue + Send + Sync>> {
|
pub fn take_compiled_closure(&mut self) -> Option<std::sync::Arc<dyn Fn(&[crate::jit::abi::JitValue]) -> crate::jit::abi::JitValue + Send + Sync>> {
|
||||||
self.compiled_closure.take()
|
self.compiled_closure.take()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,15 +12,33 @@ pub struct LowerCore {
|
|||||||
param_index: std::collections::HashMap<ValueId, usize>,
|
param_index: std::collections::HashMap<ValueId, usize>,
|
||||||
/// Track values produced by Phi (for minimal PHI path)
|
/// Track values produced by Phi (for minimal PHI path)
|
||||||
phi_values: std::collections::HashSet<ValueId>,
|
phi_values: std::collections::HashSet<ValueId>,
|
||||||
|
/// Map (block, phi dst) -> param index in that block (for multi-PHI)
|
||||||
|
phi_param_index: std::collections::HashMap<(crate::mir::BasicBlockId, ValueId), usize>,
|
||||||
|
/// Track values that are boolean (b1) results, e.g., Compare destinations
|
||||||
|
bool_values: std::collections::HashSet<ValueId>,
|
||||||
|
/// Track PHI destinations that are boolean (all inputs derived from bool_values)
|
||||||
|
bool_phi_values: std::collections::HashSet<ValueId>,
|
||||||
|
// Per-function statistics (last lowered)
|
||||||
|
last_phi_total: u64,
|
||||||
|
last_phi_b1: u64,
|
||||||
|
last_ret_bool_hint_used: bool,
|
||||||
|
// Minimal local slot mapping for Load/Store (ptr ValueId -> slot index)
|
||||||
|
local_index: std::collections::HashMap<ValueId, usize>,
|
||||||
|
next_local: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LowerCore {
|
impl LowerCore {
|
||||||
pub fn new() -> Self { Self { unsupported: 0, covered: 0, known_i64: std::collections::HashMap::new(), param_index: std::collections::HashMap::new(), phi_values: std::collections::HashSet::new() } }
|
pub fn new() -> Self { Self { unsupported: 0, covered: 0, known_i64: std::collections::HashMap::new(), param_index: std::collections::HashMap::new(), phi_values: std::collections::HashSet::new(), phi_param_index: std::collections::HashMap::new(), bool_values: std::collections::HashSet::new(), bool_phi_values: std::collections::HashSet::new(), last_phi_total: 0, last_phi_b1: 0, last_ret_bool_hint_used: false, local_index: std::collections::HashMap::new(), next_local: 0 } }
|
||||||
|
|
||||||
|
/// Get statistics for the last lowered function
|
||||||
|
pub fn last_stats(&self) -> (u64, u64, bool) { (self.last_phi_total, self.last_phi_b1, self.last_ret_bool_hint_used) }
|
||||||
|
|
||||||
/// Walk the MIR function and count supported/unsupported instructions.
|
/// Walk the MIR function and count supported/unsupported instructions.
|
||||||
/// In the future, this will build CLIF via Cranelift builders.
|
/// In the future, this will build CLIF via Cranelift builders.
|
||||||
pub fn lower_function(&mut self, func: &MirFunction, builder: &mut dyn IRBuilder) -> Result<(), String> {
|
pub fn lower_function(&mut self, func: &MirFunction, builder: &mut dyn IRBuilder) -> Result<(), String> {
|
||||||
// Prepare a simple i64 ABI based on param count; always assume i64 return for now
|
// Prepare a simple i64 ABI based on param count; always assume i64 return for now
|
||||||
|
// Reset per-function stats
|
||||||
|
self.last_phi_total = 0; self.last_phi_b1 = 0; self.last_ret_bool_hint_used = false;
|
||||||
// Build param index map
|
// Build param index map
|
||||||
self.param_index.clear();
|
self.param_index.clear();
|
||||||
for (i, v) in func.params.iter().copied().enumerate() {
|
for (i, v) in func.params.iter().copied().enumerate() {
|
||||||
@ -30,34 +48,221 @@ impl LowerCore {
|
|||||||
let mut bb_ids: Vec<_> = func.blocks.keys().copied().collect();
|
let mut bb_ids: Vec<_> = func.blocks.keys().copied().collect();
|
||||||
bb_ids.sort_by_key(|b| b.0);
|
bb_ids.sort_by_key(|b| b.0);
|
||||||
builder.prepare_blocks(bb_ids.len());
|
builder.prepare_blocks(bb_ids.len());
|
||||||
// Optional: collect single-PHI targets for minimal PHI path
|
// Seed boolean lattice with boolean parameters from MIR signature
|
||||||
let enable_phi_min = std::env::var("NYASH_JIT_PHI_MIN").ok().as_deref() == Some("1");
|
if !func.signature.params.is_empty() {
|
||||||
let mut phi_targets: std::collections::HashMap<crate::mir::BasicBlockId, std::collections::HashMap<crate::mir::BasicBlockId, crate::mir::ValueId>> = std::collections::HashMap::new();
|
for (idx, vid) in func.params.iter().copied().enumerate() {
|
||||||
if enable_phi_min {
|
if let Some(mt) = func.signature.params.get(idx) {
|
||||||
for (bb_id, bb) in func.blocks.iter() {
|
if matches!(mt, crate::mir::MirType::Bool) {
|
||||||
// gather Phi instructions in this block
|
self.bool_values.insert(vid);
|
||||||
let mut phis: Vec<&crate::mir::MirInstruction> = Vec::new();
|
|
||||||
for ins in bb.instructions.iter() { if let crate::mir::MirInstruction::Phi { .. } = ins { phis.push(ins); } }
|
|
||||||
if phis.len() == 1 {
|
|
||||||
if let crate::mir::MirInstruction::Phi { inputs, .. } = phis[0] {
|
|
||||||
let mut map: std::collections::HashMap<crate::mir::BasicBlockId, crate::mir::ValueId> = std::collections::HashMap::new();
|
|
||||||
for (pred, val) in inputs.iter() { map.insert(*pred, *val); }
|
|
||||||
phi_targets.insert(*bb_id, map);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
builder.prepare_signature_i64(func.params.len(), true);
|
// Pre-scan to classify boolean-producing values and propagate via Copy/Phi/Load-Store heuristics.
|
||||||
|
self.bool_values.clear();
|
||||||
|
let mut copy_edges: Vec<(crate::mir::ValueId, crate::mir::ValueId)> = Vec::new();
|
||||||
|
let mut phi_defs: Vec<(crate::mir::ValueId, Vec<crate::mir::ValueId>)> = Vec::new();
|
||||||
|
let mut stores: Vec<(crate::mir::ValueId, crate::mir::ValueId)> = Vec::new(); // (ptr, value)
|
||||||
|
let mut loads: Vec<(crate::mir::ValueId, crate::mir::ValueId)> = Vec::new(); // (dst, ptr)
|
||||||
|
for bb in bb_ids.iter() {
|
||||||
|
if let Some(block) = func.blocks.get(bb) {
|
||||||
|
for ins in block.instructions.iter() {
|
||||||
|
match ins {
|
||||||
|
crate::mir::MirInstruction::Compare { dst, .. } => { self.bool_values.insert(*dst); }
|
||||||
|
crate::mir::MirInstruction::Const { dst, value } => {
|
||||||
|
if let ConstValue::Bool(_) = value { self.bool_values.insert(*dst); }
|
||||||
|
}
|
||||||
|
crate::mir::MirInstruction::Cast { dst, target_type, .. } => {
|
||||||
|
if matches!(target_type, crate::mir::MirType::Bool) { self.bool_values.insert(*dst); }
|
||||||
|
}
|
||||||
|
crate::mir::MirInstruction::TypeOp { dst, op, ty, .. } => {
|
||||||
|
// Check and cast-to-bool produce boolean
|
||||||
|
if matches!(op, crate::mir::TypeOpKind::Check) || matches!(ty, crate::mir::MirType::Bool) { self.bool_values.insert(*dst); }
|
||||||
|
}
|
||||||
|
crate::mir::MirInstruction::Copy { dst, src } => { copy_edges.push((*dst, *src)); }
|
||||||
|
crate::mir::MirInstruction::Phi { dst, inputs } => {
|
||||||
|
let vs: Vec<_> = inputs.iter().map(|(_, v)| *v).collect();
|
||||||
|
phi_defs.push((*dst, vs));
|
||||||
|
}
|
||||||
|
crate::mir::MirInstruction::Store { value, ptr } => { stores.push((*ptr, *value)); }
|
||||||
|
crate::mir::MirInstruction::Load { dst, ptr } => { loads.push((*dst, *ptr)); }
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(term) = &block.terminator {
|
||||||
|
match term {
|
||||||
|
crate::mir::MirInstruction::Compare { dst, .. } => { self.bool_values.insert(*dst); }
|
||||||
|
crate::mir::MirInstruction::Const { dst, value } => {
|
||||||
|
if let ConstValue::Bool(_) = value { self.bool_values.insert(*dst); }
|
||||||
|
}
|
||||||
|
crate::mir::MirInstruction::Cast { dst, target_type, .. } => {
|
||||||
|
if matches!(target_type, crate::mir::MirType::Bool) { self.bool_values.insert(*dst); }
|
||||||
|
}
|
||||||
|
crate::mir::MirInstruction::TypeOp { dst, op, ty, .. } => {
|
||||||
|
if matches!(op, crate::mir::TypeOpKind::Check) || matches!(ty, crate::mir::MirType::Bool) { self.bool_values.insert(*dst); }
|
||||||
|
}
|
||||||
|
crate::mir::MirInstruction::Copy { dst, src } => { copy_edges.push((*dst, *src)); }
|
||||||
|
crate::mir::MirInstruction::Phi { dst, inputs } => {
|
||||||
|
let vs: Vec<_> = inputs.iter().map(|(_, v)| *v).collect();
|
||||||
|
phi_defs.push((*dst, vs));
|
||||||
|
}
|
||||||
|
crate::mir::MirInstruction::Branch { condition, .. } => { self.bool_values.insert(*condition); }
|
||||||
|
crate::mir::MirInstruction::Store { value, ptr } => { stores.push((*ptr, *value)); }
|
||||||
|
crate::mir::MirInstruction::Load { dst, ptr } => { loads.push((*dst, *ptr)); }
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fixed-point boolean lattice propagation
|
||||||
|
let mut changed = true;
|
||||||
|
let mut store_bool_ptrs: std::collections::HashSet<crate::mir::ValueId> = std::collections::HashSet::new();
|
||||||
|
while changed {
|
||||||
|
changed = false;
|
||||||
|
// Copy propagation
|
||||||
|
for (dst, src) in copy_edges.iter().copied() {
|
||||||
|
if self.bool_values.contains(&src) && !self.bool_values.contains(&dst) {
|
||||||
|
self.bool_values.insert(dst);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
// Pointer alias propagation for Store/Load lattice
|
||||||
|
if store_bool_ptrs.contains(&src) && !store_bool_ptrs.contains(&dst) {
|
||||||
|
store_bool_ptrs.insert(dst);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Store marking
|
||||||
|
for (ptr, val) in stores.iter().copied() {
|
||||||
|
if self.bool_values.contains(&val) && !store_bool_ptrs.contains(&ptr) {
|
||||||
|
store_bool_ptrs.insert(ptr);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Load propagation
|
||||||
|
for (dst, ptr) in loads.iter().copied() {
|
||||||
|
if store_bool_ptrs.contains(&ptr) && !self.bool_values.contains(&dst) {
|
||||||
|
self.bool_values.insert(dst);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// PHI closure for value booleans
|
||||||
|
for (dst, inputs) in phi_defs.iter() {
|
||||||
|
if inputs.iter().all(|v| self.bool_values.contains(v)) && !self.bool_values.contains(dst) {
|
||||||
|
self.bool_values.insert(*dst);
|
||||||
|
self.bool_phi_values.insert(*dst);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// PHI closure for pointer aliases: if all inputs are bool-storing pointers, mark dst pointer as such
|
||||||
|
for (dst, inputs) in phi_defs.iter() {
|
||||||
|
if inputs.iter().all(|v| store_bool_ptrs.contains(v)) && !store_bool_ptrs.contains(dst) {
|
||||||
|
store_bool_ptrs.insert(*dst);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Always-on PHI statistics: count total/b1 phi slots using current heuristics
|
||||||
|
{
|
||||||
|
use crate::mir::MirInstruction;
|
||||||
|
let mut total_phi_slots: usize = 0;
|
||||||
|
let mut total_phi_b1_slots: usize = 0;
|
||||||
|
for (dst, inputs) in phi_defs.iter() {
|
||||||
|
total_phi_slots += 1;
|
||||||
|
// Heuristics consistent with dump path
|
||||||
|
let used_as_branch = func.blocks.values().any(|bbx| {
|
||||||
|
if let Some(MirInstruction::Branch { condition, .. }) = &bbx.terminator { condition == dst } else { false }
|
||||||
|
});
|
||||||
|
let is_b1 = self.bool_phi_values.contains(dst)
|
||||||
|
|| inputs.iter().all(|v| {
|
||||||
|
self.bool_values.contains(v) || self.known_i64.get(v).map(|&iv| iv == 0 || iv == 1).unwrap_or(false)
|
||||||
|
})
|
||||||
|
|| used_as_branch;
|
||||||
|
if is_b1 { total_phi_b1_slots += 1; }
|
||||||
|
}
|
||||||
|
if total_phi_slots > 0 {
|
||||||
|
crate::jit::rt::phi_total_inc(total_phi_slots as u64);
|
||||||
|
crate::jit::rt::phi_b1_inc(total_phi_b1_slots as u64);
|
||||||
|
self.last_phi_total = total_phi_slots as u64;
|
||||||
|
self.last_phi_b1 = total_phi_b1_slots as u64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Optional: collect PHI targets and ordering per successor for minimal/multi PHI path
|
||||||
|
let cfg_now = crate::jit::config::current();
|
||||||
|
let enable_phi_min = cfg_now.phi_min;
|
||||||
|
// For each successor block, store ordered list of phi dst and a map pred->input for each phi
|
||||||
|
let mut succ_phi_order: std::collections::HashMap<crate::mir::BasicBlockId, Vec<crate::mir::ValueId>> = std::collections::HashMap::new();
|
||||||
|
let mut succ_phi_inputs: std::collections::HashMap<crate::mir::BasicBlockId, Vec<(crate::mir::BasicBlockId, crate::mir::ValueId)>> = std::collections::HashMap::new();
|
||||||
|
if enable_phi_min {
|
||||||
|
for (bb_id, bb) in func.blocks.iter() {
|
||||||
|
let mut order: Vec<crate::mir::ValueId> = Vec::new();
|
||||||
|
for ins in bb.instructions.iter() {
|
||||||
|
if let crate::mir::MirInstruction::Phi { dst, inputs } = ins {
|
||||||
|
order.push(*dst);
|
||||||
|
// store all (pred,val) pairs in flat vec grouped by succ
|
||||||
|
for (pred, val) in inputs.iter() { succ_phi_inputs.entry(*bb_id).or_default().push((*pred, *val)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !order.is_empty() { succ_phi_order.insert(*bb_id, order); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Decide ABI: typed or i64-only
|
||||||
|
let native_f64 = cfg_now.native_f64;
|
||||||
|
let native_bool = cfg_now.native_bool;
|
||||||
|
let mut use_typed = false;
|
||||||
|
let mut kinds: Vec<super::builder::ParamKind> = Vec::new();
|
||||||
|
for mt in func.signature.params.iter() {
|
||||||
|
let k = match mt {
|
||||||
|
crate::mir::MirType::Float if native_f64 => { use_typed = true; super::builder::ParamKind::F64 }
|
||||||
|
crate::mir::MirType::Bool if native_bool => { use_typed = true; super::builder::ParamKind::B1 }
|
||||||
|
_ => super::builder::ParamKind::I64,
|
||||||
|
};
|
||||||
|
kinds.push(k);
|
||||||
|
}
|
||||||
|
let ret_is_f64 = native_f64 && matches!(func.signature.return_type, crate::mir::MirType::Float);
|
||||||
|
// Hint return bool footing (no-op in current backend; keeps switch point centralized)
|
||||||
|
let ret_is_bool = matches!(func.signature.return_type, crate::mir::MirType::Bool);
|
||||||
|
if ret_is_bool {
|
||||||
|
builder.hint_ret_bool(true);
|
||||||
|
// Track how many functions are lowered with boolean return hint (for stats)
|
||||||
|
crate::jit::rt::ret_bool_hint_inc(1);
|
||||||
|
self.last_ret_bool_hint_used = true;
|
||||||
|
}
|
||||||
|
if use_typed || ret_is_f64 {
|
||||||
|
builder.prepare_signature_typed(&kinds, ret_is_f64);
|
||||||
|
} else {
|
||||||
|
builder.prepare_signature_i64(func.params.len(), true);
|
||||||
|
}
|
||||||
builder.begin_function(&func.signature.name);
|
builder.begin_function(&func.signature.name);
|
||||||
// Iterate blocks in the sorted order to keep indices stable
|
// Iterate blocks in the sorted order to keep indices stable
|
||||||
self.phi_values.clear();
|
self.phi_values.clear();
|
||||||
|
self.phi_param_index.clear();
|
||||||
for (idx, bb_id) in bb_ids.iter().enumerate() {
|
for (idx, bb_id) in bb_ids.iter().enumerate() {
|
||||||
let bb = func.blocks.get(bb_id).unwrap();
|
let bb = func.blocks.get(bb_id).unwrap();
|
||||||
builder.switch_to_block(idx);
|
builder.switch_to_block(idx);
|
||||||
|
// Pre-scan PHIs in this block and ensure block parameters count (multi-PHI)
|
||||||
|
if enable_phi_min {
|
||||||
|
let mut local_phi_order: Vec<ValueId> = Vec::new();
|
||||||
|
// Also detect boolean PHIs: inputs all from boolean-producing values
|
||||||
|
for ins in bb.instructions.iter() {
|
||||||
|
if let crate::mir::MirInstruction::Phi { dst, inputs } = ins {
|
||||||
|
local_phi_order.push(*dst);
|
||||||
|
// decide if this phi is boolean
|
||||||
|
if inputs.iter().all(|(_, v)| self.bool_values.contains(v)) && !inputs.is_empty() {
|
||||||
|
self.bool_phi_values.insert(*dst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !local_phi_order.is_empty() {
|
||||||
|
builder.ensure_block_params_i64(idx, local_phi_order.len());
|
||||||
|
for (i, v) in local_phi_order.into_iter().enumerate() {
|
||||||
|
self.phi_values.insert(v);
|
||||||
|
self.phi_param_index.insert((*bb_id, v), i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for instr in bb.instructions.iter() {
|
for instr in bb.instructions.iter() {
|
||||||
self.cover_if_supported(instr);
|
self.cover_if_supported(instr);
|
||||||
if let MirInstruction::Phi { dst, .. } = instr { self.phi_values.insert(*dst); }
|
self.try_emit(builder, instr, *bb_id);
|
||||||
self.try_emit(builder, instr);
|
|
||||||
}
|
}
|
||||||
if let Some(term) = &bb.terminator {
|
if let Some(term) = &bb.terminator {
|
||||||
self.cover_if_supported(term);
|
self.cover_if_supported(term);
|
||||||
@ -70,14 +275,47 @@ impl LowerCore {
|
|||||||
let then_index = bb_ids.iter().position(|x| x == then_bb).unwrap_or(0);
|
let then_index = bb_ids.iter().position(|x| x == then_bb).unwrap_or(0);
|
||||||
let else_index = bb_ids.iter().position(|x| x == else_bb).unwrap_or(0);
|
let else_index = bb_ids.iter().position(|x| x == else_bb).unwrap_or(0);
|
||||||
if enable_phi_min {
|
if enable_phi_min {
|
||||||
// For minimal PHI, pass one i64 arg if successor defines a single PHI with this block as pred
|
// For multi-PHI, push args in successor's phi order
|
||||||
let mut then_n = 0usize;
|
let mut then_n = 0usize; let mut else_n = 0usize;
|
||||||
let mut else_n = 0usize;
|
if let Some(order) = succ_phi_order.get(then_bb) {
|
||||||
if let Some(pred_map) = phi_targets.get(then_bb) {
|
let mut cnt = 0usize;
|
||||||
if let Some(v) = pred_map.get(bb_id) { self.push_value_if_known_or_param(builder, v); then_n = 1; builder.ensure_block_param_i64(then_index); }
|
for dst in order.iter() {
|
||||||
|
// find input from current block
|
||||||
|
if let Some(bb_succ) = func.blocks.get(then_bb) {
|
||||||
|
// locate the specific phi to read its inputs
|
||||||
|
for ins in bb_succ.instructions.iter() {
|
||||||
|
if let crate::mir::MirInstruction::Phi { dst: d2, inputs } = ins {
|
||||||
|
if d2 == dst {
|
||||||
|
if let Some((_, val)) = inputs.iter().find(|(pred, _)| pred == bb_id) {
|
||||||
|
self.push_value_if_known_or_param(builder, val);
|
||||||
|
cnt += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cnt > 0 { builder.ensure_block_params_i64(then_index, cnt); }
|
||||||
|
then_n = cnt;
|
||||||
}
|
}
|
||||||
if let Some(pred_map) = phi_targets.get(else_bb) {
|
if let Some(order) = succ_phi_order.get(else_bb) {
|
||||||
if let Some(v) = pred_map.get(bb_id) { self.push_value_if_known_or_param(builder, v); else_n = 1; builder.ensure_block_param_i64(else_index); }
|
let mut cnt = 0usize;
|
||||||
|
for dst in order.iter() {
|
||||||
|
if let Some(bb_succ) = func.blocks.get(else_bb) {
|
||||||
|
for ins in bb_succ.instructions.iter() {
|
||||||
|
if let crate::mir::MirInstruction::Phi { dst: d2, inputs } = ins {
|
||||||
|
if d2 == dst {
|
||||||
|
if let Some((_, val)) = inputs.iter().find(|(pred, _)| pred == bb_id) {
|
||||||
|
self.push_value_if_known_or_param(builder, val);
|
||||||
|
cnt += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cnt > 0 { builder.ensure_block_params_i64(else_index, cnt); }
|
||||||
|
else_n = cnt;
|
||||||
}
|
}
|
||||||
builder.br_if_with_args(then_index, else_index, then_n, else_n);
|
builder.br_if_with_args(then_index, else_index, then_n, else_n);
|
||||||
} else {
|
} else {
|
||||||
@ -90,8 +328,24 @@ impl LowerCore {
|
|||||||
let target_index = bb_ids.iter().position(|x| x == target).unwrap_or(0);
|
let target_index = bb_ids.iter().position(|x| x == target).unwrap_or(0);
|
||||||
if enable_phi_min {
|
if enable_phi_min {
|
||||||
let mut n = 0usize;
|
let mut n = 0usize;
|
||||||
if let Some(pred_map) = phi_targets.get(target) {
|
if let Some(order) = succ_phi_order.get(target) {
|
||||||
if let Some(v) = pred_map.get(bb_id) { self.push_value_if_known_or_param(builder, v); n = 1; builder.ensure_block_param_i64(target_index); }
|
let mut cnt = 0usize;
|
||||||
|
if let Some(bb_succ) = func.blocks.get(target) {
|
||||||
|
for dst in order.iter() {
|
||||||
|
for ins in bb_succ.instructions.iter() {
|
||||||
|
if let crate::mir::MirInstruction::Phi { dst: d2, inputs } = ins {
|
||||||
|
if d2 == dst {
|
||||||
|
if let Some((_, val)) = inputs.iter().find(|(pred, _)| pred == bb_id) {
|
||||||
|
self.push_value_if_known_or_param(builder, val);
|
||||||
|
cnt += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cnt > 0 { builder.ensure_block_params_i64(target_index, cnt); }
|
||||||
|
n = cnt;
|
||||||
}
|
}
|
||||||
builder.jump_with_args(target_index, n);
|
builder.jump_with_args(target_index, n);
|
||||||
} else {
|
} else {
|
||||||
@ -100,20 +354,72 @@ impl LowerCore {
|
|||||||
builder.seal_block(target_index);
|
builder.seal_block(target_index);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
self.try_emit(builder, term);
|
self.try_emit(builder, term, *bb_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
builder.end_function();
|
builder.end_function();
|
||||||
|
if std::env::var("NYASH_JIT_DUMP").ok().as_deref() == Some("1") {
|
||||||
|
let succs = succ_phi_order.len();
|
||||||
|
eprintln!("[JIT] cfg: blocks={} phi_succ={} (phi_min={})", bb_ids.len(), succs, enable_phi_min);
|
||||||
|
if enable_phi_min {
|
||||||
|
let mut total_phi_slots: usize = 0;
|
||||||
|
let mut total_phi_b1_slots: usize = 0;
|
||||||
|
for (succ, order) in succ_phi_order.iter() {
|
||||||
|
let mut preds_set: std::collections::BTreeSet<i64> = std::collections::BTreeSet::new();
|
||||||
|
let mut phi_lines: Vec<String> = Vec::new();
|
||||||
|
if let Some(bb_succ) = func.blocks.get(succ) {
|
||||||
|
for ins in bb_succ.instructions.iter() {
|
||||||
|
if let crate::mir::MirInstruction::Phi { dst, inputs } = ins {
|
||||||
|
// collect preds for block-level summary
|
||||||
|
for (pred, _) in inputs.iter() { preds_set.insert(pred.0 as i64); }
|
||||||
|
// build detailed mapping text: dst<-pred:val,...
|
||||||
|
let mut pairs: Vec<String> = Vec::new();
|
||||||
|
for (pred, val) in inputs.iter() {
|
||||||
|
pairs.push(format!("{}:{}", pred.0, val.0));
|
||||||
|
}
|
||||||
|
// Heuristics: boolean PHI if (1) pre-analysis marked it, or
|
||||||
|
// (2) all inputs look boolean-like (from bool producers or 0/1 const), or
|
||||||
|
// (3) used as a branch condition somewhere.
|
||||||
|
let used_as_branch = func.blocks.values().any(|bbx| {
|
||||||
|
if let Some(MirInstruction::Branch { condition, .. }) = &bbx.terminator { condition == dst } else { false }
|
||||||
|
});
|
||||||
|
let is_b1 = self.bool_phi_values.contains(dst)
|
||||||
|
|| inputs.iter().all(|(_, v)| {
|
||||||
|
self.bool_values.contains(v) || self.known_i64.get(v).map(|&iv| iv == 0 || iv == 1).unwrap_or(false)
|
||||||
|
})
|
||||||
|
|| used_as_branch;
|
||||||
|
let tag = if is_b1 { " (b1)" } else { "" };
|
||||||
|
phi_lines.push(format!(" dst v{}{} <- {}", dst.0, tag, pairs.join(", ")));
|
||||||
|
total_phi_slots += 1;
|
||||||
|
if is_b1 { total_phi_b1_slots += 1; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let preds_list: Vec<String> = preds_set.into_iter().map(|p| p.to_string()).collect();
|
||||||
|
eprintln!("[JIT] phi: bb={} slots={} preds={}", succ.0, order.len(), preds_list.join("|"));
|
||||||
|
for ln in phi_lines { eprintln!("[JIT]{}", ln); }
|
||||||
|
}
|
||||||
|
eprintln!("[JIT] phi_summary: total_slots={} b1_slots={}", total_phi_slots, total_phi_b1_slots);
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a value onto the builder stack if it is a known i64 const or a parameter.
|
/// Push a value onto the builder stack if it is a known i64 const or a parameter.
|
||||||
fn push_value_if_known_or_param(&self, b: &mut dyn IRBuilder, id: &ValueId) {
|
fn push_value_if_known_or_param(&self, b: &mut dyn IRBuilder, id: &ValueId) {
|
||||||
if self.phi_values.contains(id) {
|
if self.phi_values.contains(id) {
|
||||||
// Minimal PHI: read current block param
|
// Multi-PHI: find the param index for this phi in the current block
|
||||||
b.push_block_param_i64();
|
// We don't have the current block id here; rely on builder's current block context and our stored index being positional.
|
||||||
|
// As an approximation, prefer position 0 if unknown.
|
||||||
|
let pos = self.phi_param_index.iter().find_map(|((_, vid), idx)| if vid == id { Some(*idx) } else { None }).unwrap_or(0);
|
||||||
|
// Use b1 loader for boolean PHIs when enabled
|
||||||
|
if crate::jit::config::current().native_bool && self.bool_phi_values.contains(id) {
|
||||||
|
b.push_block_param_b1_at(pos);
|
||||||
|
} else {
|
||||||
|
b.push_block_param_i64_at(pos);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if let Some(pidx) = self.param_index.get(id).copied() {
|
if let Some(pidx) = self.param_index.get(id).copied() {
|
||||||
@ -131,6 +437,7 @@ impl LowerCore {
|
|||||||
instr,
|
instr,
|
||||||
I::Const { .. }
|
I::Const { .. }
|
||||||
| I::Copy { .. }
|
| I::Copy { .. }
|
||||||
|
| I::Cast { .. }
|
||||||
| I::BinOp { .. }
|
| I::BinOp { .. }
|
||||||
| I::Compare { .. }
|
| I::Compare { .. }
|
||||||
| I::Jump { .. }
|
| I::Jump { .. }
|
||||||
@ -142,9 +449,16 @@ impl LowerCore {
|
|||||||
if supported { self.covered += 1; } else { self.unsupported += 1; }
|
if supported { self.covered += 1; } else { self.unsupported += 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_emit(&mut self, b: &mut dyn IRBuilder, instr: &MirInstruction) {
|
fn try_emit(&mut self, b: &mut dyn IRBuilder, instr: &MirInstruction, cur_bb: crate::mir::BasicBlockId) {
|
||||||
use crate::mir::MirInstruction as I;
|
use crate::mir::MirInstruction as I;
|
||||||
match instr {
|
match instr {
|
||||||
|
I::Cast { dst, value, target_type: _ } => {
|
||||||
|
// Minimal cast footing: materialize source when param/known
|
||||||
|
// Bool→Int: rely on producers (compare) and branch/b1 loaders; here we just reuse integer path
|
||||||
|
self.push_value_if_known_or_param(b, value);
|
||||||
|
// Track known i64 if source known
|
||||||
|
if let Some(v) = self.known_i64.get(value).copied() { self.known_i64.insert(*dst, v); }
|
||||||
|
}
|
||||||
I::Const { dst, value } => match value {
|
I::Const { dst, value } => match value {
|
||||||
ConstValue::Integer(i) => {
|
ConstValue::Integer(i) => {
|
||||||
b.emit_const_i64(*i);
|
b.emit_const_i64(*i);
|
||||||
@ -155,6 +469,8 @@ impl LowerCore {
|
|||||||
let iv = if *bv { 1 } else { 0 };
|
let iv = if *bv { 1 } else { 0 };
|
||||||
b.emit_const_i64(iv);
|
b.emit_const_i64(iv);
|
||||||
self.known_i64.insert(*dst, iv);
|
self.known_i64.insert(*dst, iv);
|
||||||
|
// Mark this value as boolean producer
|
||||||
|
self.bool_values.insert(*dst);
|
||||||
}
|
}
|
||||||
ConstValue::String(_) | ConstValue::Null | ConstValue::Void => {
|
ConstValue::String(_) | ConstValue::Null | ConstValue::Void => {
|
||||||
// leave unsupported for now
|
// leave unsupported for now
|
||||||
@ -166,6 +482,8 @@ impl LowerCore {
|
|||||||
if let Some(pidx) = self.param_index.get(src).copied() {
|
if let Some(pidx) = self.param_index.get(src).copied() {
|
||||||
b.emit_param_i64(pidx);
|
b.emit_param_i64(pidx);
|
||||||
}
|
}
|
||||||
|
// Propagate boolean classification through Copy
|
||||||
|
if self.bool_values.contains(src) { self.bool_values.insert(*dst); }
|
||||||
// Otherwise no-op for codegen (stack-machine handles sources directly later)
|
// Otherwise no-op for codegen (stack-machine handles sources directly later)
|
||||||
}
|
}
|
||||||
I::BinOp { dst, op, lhs, rhs } => {
|
I::BinOp { dst, op, lhs, rhs } => {
|
||||||
@ -208,6 +526,8 @@ impl LowerCore {
|
|||||||
CompareOp::Ge => CmpKind::Ge,
|
CompareOp::Ge => CmpKind::Ge,
|
||||||
};
|
};
|
||||||
b.emit_compare(kind);
|
b.emit_compare(kind);
|
||||||
|
// Mark the last dst (compare produces a boolean)
|
||||||
|
if let MirInstruction::Compare { dst, .. } = instr { self.bool_values.insert(*dst); }
|
||||||
}
|
}
|
||||||
I::Jump { .. } => b.emit_jump(),
|
I::Jump { .. } => b.emit_jump(),
|
||||||
I::Branch { .. } => b.emit_branch(),
|
I::Branch { .. } => b.emit_branch(),
|
||||||
@ -215,53 +535,146 @@ impl LowerCore {
|
|||||||
if let Some(v) = value { self.push_value_if_known_or_param(b, v); }
|
if let Some(v) = value { self.push_value_if_known_or_param(b, v); }
|
||||||
b.emit_return()
|
b.emit_return()
|
||||||
}
|
}
|
||||||
I::Phi { .. } => {
|
I::Store { value, ptr } => {
|
||||||
// Minimal PHI: load current block param as value (i64)
|
// Minimal lowering: materialize value if known/param and store to a local slot keyed by ptr
|
||||||
b.push_block_param_i64();
|
self.push_value_if_known_or_param(b, value);
|
||||||
|
let slot = *self.local_index.entry(*ptr).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
|
||||||
|
b.ensure_local_i64(slot);
|
||||||
|
b.store_local_i64(slot);
|
||||||
|
}
|
||||||
|
I::Load { dst: _, ptr } => {
|
||||||
|
// Minimal lowering: load from local slot keyed by ptr, default 0 if unset
|
||||||
|
let slot = *self.local_index.entry(*ptr).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
|
||||||
|
b.ensure_local_i64(slot);
|
||||||
|
b.load_local_i64(slot);
|
||||||
|
}
|
||||||
|
I::Phi { dst, .. } => {
|
||||||
|
// Minimal PHI: load current block param; b1 when classified boolean
|
||||||
|
let pos = self.phi_param_index.get(&(cur_bb, *dst)).copied().unwrap_or(0);
|
||||||
|
if self.bool_phi_values.contains(dst) {
|
||||||
|
b.push_block_param_b1_at(pos);
|
||||||
|
} else {
|
||||||
|
b.push_block_param_i64_at(pos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
I::ArrayGet { array, index, .. } => {
|
I::ArrayGet { array, index, .. } => {
|
||||||
if std::env::var("NYASH_JIT_HOSTCALL").ok().as_deref() == Some("1") {
|
if std::env::var("NYASH_JIT_HOSTCALL").ok().as_deref() == Some("1") {
|
||||||
// Push args: array param index (or -1), index (known or 0)
|
|
||||||
let idx = self.known_i64.get(index).copied().unwrap_or(0);
|
let idx = self.known_i64.get(index).copied().unwrap_or(0);
|
||||||
let arr_idx = self.param_index.get(array).copied().map(|x| x as i64).unwrap_or(-1);
|
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||||
b.emit_const_i64(arr_idx);
|
// Handle-based: push handle value from param, then index
|
||||||
b.emit_const_i64(idx);
|
b.emit_param_i64(pidx);
|
||||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_GET, 2, true);
|
b.emit_const_i64(idx);
|
||||||
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_GET_H, 2, true);
|
||||||
|
} else {
|
||||||
|
// Fallback to index-based (param index unknown)
|
||||||
|
let arr_idx = -1;
|
||||||
|
b.emit_const_i64(arr_idx);
|
||||||
|
b.emit_const_i64(idx);
|
||||||
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_GET, 2, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
I::ArraySet { array, index, value } => {
|
I::ArraySet { array, index, value } => {
|
||||||
if std::env::var("NYASH_JIT_HOSTCALL").ok().as_deref() == Some("1") {
|
if std::env::var("NYASH_JIT_HOSTCALL").ok().as_deref() == Some("1") {
|
||||||
let idx = self.known_i64.get(index).copied().unwrap_or(0);
|
let idx = self.known_i64.get(index).copied().unwrap_or(0);
|
||||||
let val = self.known_i64.get(value).copied().unwrap_or(0);
|
let val = self.known_i64.get(value).copied().unwrap_or(0);
|
||||||
let arr_idx = self.param_index.get(array).copied().map(|x| x as i64).unwrap_or(-1);
|
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||||
b.emit_const_i64(arr_idx);
|
b.emit_param_i64(pidx);
|
||||||
b.emit_const_i64(idx);
|
b.emit_const_i64(idx);
|
||||||
b.emit_const_i64(val);
|
b.emit_const_i64(val);
|
||||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_SET, 3, false);
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_SET_H, 3, false);
|
||||||
|
} else {
|
||||||
|
let arr_idx = -1;
|
||||||
|
b.emit_const_i64(arr_idx);
|
||||||
|
b.emit_const_i64(idx);
|
||||||
|
b.emit_const_i64(val);
|
||||||
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_SET, 3, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
I::BoxCall { box_val: array, method, args, dst, .. } => {
|
I::BoxCall { box_val: array, method, args, dst, .. } => {
|
||||||
if std::env::var("NYASH_JIT_HOSTCALL").ok().as_deref() == Some("1") {
|
if std::env::var("NYASH_JIT_HOSTCALL").ok().as_deref() == Some("1") {
|
||||||
match method.as_str() {
|
match method.as_str() {
|
||||||
"len" | "length" => {
|
"len" | "length" => {
|
||||||
// argc=1: (array_param_index)
|
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||||
let arr_idx = self.param_index.get(array).copied().map(|x| x as i64).unwrap_or(-1);
|
// Handle-based generic length: supports ArrayBox and StringBox
|
||||||
b.emit_const_i64(arr_idx);
|
b.emit_param_i64(pidx);
|
||||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_LEN, 1, dst.is_some());
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, dst.is_some());
|
||||||
|
} else {
|
||||||
|
let arr_idx = -1;
|
||||||
|
b.emit_const_i64(arr_idx);
|
||||||
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_LEN, 1, dst.is_some());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"isEmpty" | "empty" => {
|
||||||
|
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||||
|
b.emit_param_i64(pidx);
|
||||||
|
// returns i64 0/1
|
||||||
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, 1, dst.is_some());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"push" => {
|
"push" => {
|
||||||
// argc=2: (array, value)
|
// argc=2: (array_handle, value)
|
||||||
let val = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
let val = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||||
let arr_idx = self.param_index.get(array).copied().map(|x| x as i64).unwrap_or(-1);
|
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||||
b.emit_const_i64(arr_idx);
|
b.emit_param_i64(pidx);
|
||||||
b.emit_const_i64(val);
|
b.emit_const_i64(val);
|
||||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_PUSH, 2, false);
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H, 2, false);
|
||||||
|
} else {
|
||||||
|
let arr_idx = -1;
|
||||||
|
b.emit_const_i64(arr_idx);
|
||||||
|
b.emit_const_i64(val);
|
||||||
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_PUSH, 2, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"size" => {
|
"size" => {
|
||||||
// MapBox.size(): argc=1 (map_param_index)
|
// MapBox.size(): argc=1 (map_handle)
|
||||||
let map_idx = self.param_index.get(array).copied().map(|x| x as i64).unwrap_or(-1);
|
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||||
b.emit_const_i64(map_idx);
|
b.emit_param_i64(pidx);
|
||||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE, 1, dst.is_some());
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE_H, 1, dst.is_some());
|
||||||
|
} else {
|
||||||
|
let map_idx = -1;
|
||||||
|
b.emit_const_i64(map_idx);
|
||||||
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE, 1, dst.is_some());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"get" => {
|
||||||
|
// MapBox.get(key): (map_handle, key_i64)
|
||||||
|
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||||
|
let key = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||||
|
b.emit_param_i64(pidx);
|
||||||
|
b.emit_const_i64(key);
|
||||||
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_GET_H, 2, dst.is_some());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"set" => {
|
||||||
|
// MapBox.set(key, value): (map_handle, key_i64, val_i64) — PoC: integer-only
|
||||||
|
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||||
|
let key = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||||
|
let val = args.get(1).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||||
|
b.emit_param_i64(pidx);
|
||||||
|
b.emit_const_i64(key);
|
||||||
|
b.emit_const_i64(val);
|
||||||
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SET_H, 3, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"charCodeAt" => {
|
||||||
|
// String.charCodeAt(index)
|
||||||
|
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||||
|
let idx = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||||
|
b.emit_param_i64(pidx);
|
||||||
|
b.emit_const_i64(idx);
|
||||||
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, 2, dst.is_some());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"has" => {
|
||||||
|
// MapBox.has(key_i64) -> 0/1
|
||||||
|
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||||
|
let key = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||||
|
b.emit_param_i64(pidx);
|
||||||
|
b.emit_const_i64(key);
|
||||||
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_HAS_H, 2, dst.is_some());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@ -271,3 +684,66 @@ impl LowerCore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emit a simple DOT graph for a MIR function's CFG.
|
||||||
|
/// Includes block ids, successor edges, and PHI counts per block when phi_min is enabled.
|
||||||
|
pub fn dump_cfg_dot(func: &crate::mir::MirFunction, path: &str, phi_min: bool) -> std::io::Result<()> {
|
||||||
|
use std::io::Write;
|
||||||
|
let mut out = String::new();
|
||||||
|
out.push_str(&format!("digraph \"{}\" {{\n", func.signature.name));
|
||||||
|
out.push_str(" node [shape=box, fontsize=10];\n");
|
||||||
|
// Derive simple bool sets: compare dsts are bool; phi of all-bool inputs are bool
|
||||||
|
let mut bool_values: std::collections::HashSet<crate::mir::ValueId> = std::collections::HashSet::new();
|
||||||
|
for (_bb_id, bb) in func.blocks.iter() {
|
||||||
|
for ins in bb.instructions.iter() {
|
||||||
|
if let crate::mir::MirInstruction::Compare { dst, .. } = ins { bool_values.insert(*dst); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut bool_phi: std::collections::HashSet<crate::mir::ValueId> = std::collections::HashSet::new();
|
||||||
|
if phi_min {
|
||||||
|
for (_bb_id, bb) in func.blocks.iter() {
|
||||||
|
for ins in bb.instructions.iter() {
|
||||||
|
if let crate::mir::MirInstruction::Phi { dst, inputs } = ins {
|
||||||
|
if !inputs.is_empty() && inputs.iter().all(|(_, v)| bool_values.contains(v)) {
|
||||||
|
bool_phi.insert(*dst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort blocks for deterministic output
|
||||||
|
let mut bb_ids: Vec<_> = func.blocks.keys().copied().collect();
|
||||||
|
bb_ids.sort_by_key(|b| b.0);
|
||||||
|
// Emit nodes with labels
|
||||||
|
for bb_id in bb_ids.iter() {
|
||||||
|
let bb = func.blocks.get(bb_id).unwrap();
|
||||||
|
let phi_count = bb.instructions.iter().filter(|ins| matches!(ins, crate::mir::MirInstruction::Phi { .. })).count();
|
||||||
|
let phi_b1_count = bb.instructions.iter().filter(|ins| match ins { crate::mir::MirInstruction::Phi { dst, .. } => bool_phi.contains(dst), _ => false }).count();
|
||||||
|
let mut label = format!("bb{}", bb_id.0);
|
||||||
|
if phi_min && phi_count > 0 {
|
||||||
|
if phi_b1_count > 0 { label = format!("{}\\nphi:{} (b1:{})", label, phi_count, phi_b1_count); }
|
||||||
|
else { label = format!("{}\\nphi:{}", label, phi_count); }
|
||||||
|
}
|
||||||
|
if *bb_id == func.entry_block { label = format!("{}\\nENTRY", label); }
|
||||||
|
out.push_str(&format!(" n{} [label=\"{}\"];\n", bb_id.0, label));
|
||||||
|
}
|
||||||
|
// Emit edges based on terminators
|
||||||
|
for bb_id in bb_ids.iter() {
|
||||||
|
let bb = func.blocks.get(bb_id).unwrap();
|
||||||
|
if let Some(term) = &bb.terminator {
|
||||||
|
match term {
|
||||||
|
crate::mir::MirInstruction::Jump { target } => {
|
||||||
|
out.push_str(&format!(" n{} -> n{};\n", bb_id.0, target.0));
|
||||||
|
}
|
||||||
|
crate::mir::MirInstruction::Branch { then_bb, else_bb, .. } => {
|
||||||
|
// Branch condition is boolean (b1)
|
||||||
|
out.push_str(&format!(" n{} -> n{} [label=\"then cond:b1\"];\n", bb_id.0, then_bb.0));
|
||||||
|
out.push_str(&format!(" n{} -> n{} [label=\"else cond:b1\"];\n", bb_id.0, else_bb.0));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.push_str("}\n");
|
||||||
|
std::fs::write(path, out)
|
||||||
|
}
|
||||||
|
|||||||
@ -9,11 +9,17 @@ pub struct JitManager {
|
|||||||
hits: HashMap<String, u32>,
|
hits: HashMap<String, u32>,
|
||||||
compiled: HashMap<String, u64>,
|
compiled: HashMap<String, u64>,
|
||||||
engine: crate::jit::engine::JitEngine,
|
engine: crate::jit::engine::JitEngine,
|
||||||
|
exec_ok: u64,
|
||||||
|
exec_trap: u64,
|
||||||
|
// Per-function lowering stats (accumulated)
|
||||||
|
func_phi_total: HashMap<String, u64>,
|
||||||
|
func_phi_b1: HashMap<String, u64>,
|
||||||
|
func_ret_bool_hint: HashMap<String, u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JitManager {
|
impl JitManager {
|
||||||
pub fn new(threshold: u32) -> Self {
|
pub fn new(threshold: u32) -> Self {
|
||||||
Self { threshold, hits: HashMap::new(), compiled: HashMap::new(), engine: crate::jit::engine::JitEngine::new() }
|
Self { threshold, hits: HashMap::new(), compiled: HashMap::new(), engine: crate::jit::engine::JitEngine::new(), exec_ok: 0, exec_trap: 0, func_phi_total: HashMap::new(), func_phi_b1: HashMap::new(), func_ret_bool_hint: HashMap::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn record_entry(&mut self, func: &str) {
|
pub fn record_entry(&mut self, func: &str) {
|
||||||
@ -35,6 +41,9 @@ impl JitManager {
|
|||||||
if self.should_jit(func) {
|
if self.should_jit(func) {
|
||||||
if let Some(handle) = self.engine.compile_function(func, mir) {
|
if let Some(handle) = self.engine.compile_function(func, mir) {
|
||||||
self.mark_compiled(func, handle);
|
self.mark_compiled(func, handle);
|
||||||
|
// Record per-function lower stats captured by engine
|
||||||
|
let (phi_t, phi_b1, ret_b) = self.engine.last_lower_stats();
|
||||||
|
self.record_lower_stats(func, phi_t, phi_b1, ret_b);
|
||||||
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
|
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
|
||||||
eprintln!("[JIT] compiled {} -> handle={}", func, handle);
|
eprintln!("[JIT] compiled {} -> handle={}", func, handle);
|
||||||
}
|
}
|
||||||
@ -47,12 +56,59 @@ impl JitManager {
|
|||||||
pub fn is_compiled(&self, func: &str) -> bool { self.compiled.contains_key(func) }
|
pub fn is_compiled(&self, func: &str) -> bool { self.compiled.contains_key(func) }
|
||||||
pub fn handle_of(&self, func: &str) -> Option<u64> { self.compiled.get(func).copied() }
|
pub fn handle_of(&self, func: &str) -> Option<u64> { self.compiled.get(func).copied() }
|
||||||
|
|
||||||
|
// --- Stats accessors for unified reporting ---
|
||||||
|
pub fn sites(&self) -> usize { self.hits.len() }
|
||||||
|
pub fn compiled_count(&self) -> usize { self.compiled.len() }
|
||||||
|
pub fn total_hits(&self) -> u64 { self.hits.values().map(|v| *v as u64).sum() }
|
||||||
|
pub fn exec_ok_count(&self) -> u64 { self.exec_ok }
|
||||||
|
pub fn exec_trap_count(&self) -> u64 { self.exec_trap }
|
||||||
|
|
||||||
|
// --- Per-function stats ---
|
||||||
|
pub fn record_lower_stats(&mut self, func: &str, phi_total: u64, phi_b1: u64, ret_bool_hint: bool) {
|
||||||
|
if phi_total > 0 { *self.func_phi_total.entry(func.to_string()).or_insert(0) += phi_total; }
|
||||||
|
if phi_b1 > 0 { *self.func_phi_b1.entry(func.to_string()).or_insert(0) += phi_b1; }
|
||||||
|
if ret_bool_hint { *self.func_ret_bool_hint.entry(func.to_string()).or_insert(0) += 1; }
|
||||||
|
}
|
||||||
|
pub fn per_function_stats(&self) -> Vec<(String, u64, u64, u64, u32, bool, u64)> {
|
||||||
|
// name, phi_total, phi_b1, ret_bool_hint, hits, compiled, handle
|
||||||
|
let mut names: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
|
||||||
|
names.extend(self.hits.keys().cloned());
|
||||||
|
names.extend(self.func_phi_total.keys().cloned());
|
||||||
|
names.extend(self.func_phi_b1.keys().cloned());
|
||||||
|
names.extend(self.func_ret_bool_hint.keys().cloned());
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for name in names {
|
||||||
|
let phi_t = self.func_phi_total.get(&name).copied().unwrap_or(0);
|
||||||
|
let phi_b1 = self.func_phi_b1.get(&name).copied().unwrap_or(0);
|
||||||
|
let rb = self.func_ret_bool_hint.get(&name).copied().unwrap_or(0);
|
||||||
|
let hits = self.hits.get(&name).copied().unwrap_or(0);
|
||||||
|
let compiled = self.compiled.contains_key(&name);
|
||||||
|
let handle = self.compiled.get(&name).copied().unwrap_or(0);
|
||||||
|
out.push((name, phi_t, phi_b1, rb, hits, compiled, handle));
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return top-N hot functions by hits, with compiled flag and handle
|
||||||
|
pub fn top_hits(&self, n: usize) -> Vec<(String, u32, bool, u64)> {
|
||||||
|
let mut v: Vec<(&String, &u32)> = self.hits.iter().collect();
|
||||||
|
v.sort_by(|a, b| b.1.cmp(a.1));
|
||||||
|
v.into_iter()
|
||||||
|
.take(n)
|
||||||
|
.map(|(k, h)| {
|
||||||
|
let compiled = self.compiled.contains_key(k);
|
||||||
|
let handle = self.compiled.get(k).copied().unwrap_or(0);
|
||||||
|
(k.clone(), *h, compiled, handle)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn print_summary(&self) {
|
pub fn print_summary(&self) {
|
||||||
if std::env::var("NYASH_JIT_STATS").ok().as_deref() != Some("1") { return; }
|
if std::env::var("NYASH_JIT_STATS").ok().as_deref() != Some("1") { return; }
|
||||||
let sites = self.hits.len();
|
let sites = self.hits.len();
|
||||||
let total_hits: u64 = self.hits.values().map(|v| *v as u64).sum();
|
let total_hits: u64 = self.hits.values().map(|v| *v as u64).sum();
|
||||||
let compiled = self.compiled.len();
|
let compiled = self.compiled.len();
|
||||||
eprintln!("[JIT] sites={} compiled={} hits_total={}", sites, compiled, total_hits);
|
eprintln!("[JIT] sites={} compiled={} hits_total={} exec_ok={} exec_trap={}", sites, compiled, total_hits, self.exec_ok, self.exec_trap);
|
||||||
// Top 5 hot functions
|
// Top 5 hot functions
|
||||||
let mut v: Vec<(&String, &u32)> = self.hits.iter().collect();
|
let mut v: Vec<(&String, &u32)> = self.hits.iter().collect();
|
||||||
v.sort_by(|a, b| b.1.cmp(a.1));
|
v.sort_by(|a, b| b.1.cmp(a.1));
|
||||||
@ -78,17 +134,27 @@ impl JitManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 10_c: execute compiled function if present (stub: empty args). Returns Some(VMValue) if JIT path was taken.
|
/// 10_c: execute compiled function if present (stub: empty args). Returns Some(VMValue) if JIT path was taken.
|
||||||
pub fn execute_compiled(&self, func: &str, args: &[crate::backend::vm::VMValue]) -> Option<crate::backend::vm::VMValue> {
|
pub fn execute_compiled(&mut self, func: &str, args: &[crate::backend::vm::VMValue]) -> Option<crate::backend::vm::VMValue> {
|
||||||
if let Some(h) = self.handle_of(func) {
|
if let Some(h) = self.handle_of(func) {
|
||||||
// Expose current args to hostcall shims
|
// Expose args to both legacy VM hostcalls and new JIT ABI TLS
|
||||||
crate::jit::rt::set_current_args(args);
|
crate::jit::rt::set_legacy_vm_args(args);
|
||||||
|
let jit_args = crate::jit::abi::adapter::to_jit_values(args);
|
||||||
|
crate::jit::rt::set_current_jit_args(&jit_args);
|
||||||
let t0 = std::time::Instant::now();
|
let t0 = std::time::Instant::now();
|
||||||
let out = self.engine.execute_handle(h, args);
|
// Begin handle scope so temporary handles are reclaimed after the call
|
||||||
|
crate::jit::rt::handles::begin_scope();
|
||||||
|
let out = self.engine.execute_handle(h, &jit_args);
|
||||||
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
|
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
|
||||||
let dt = t0.elapsed();
|
let dt = t0.elapsed();
|
||||||
eprintln!("[JIT] exec_time_ms={} for {}", dt.as_millis(), func);
|
eprintln!("[JIT] exec_time_ms={} for {}", dt.as_millis(), func);
|
||||||
}
|
}
|
||||||
return out;
|
let res = match out {
|
||||||
|
Some(v) => { self.exec_ok = self.exec_ok.saturating_add(1); Some(crate::jit::abi::adapter::from_jit_value(v)) }
|
||||||
|
None => { self.exec_trap = self.exec_trap.saturating_add(1); None }
|
||||||
|
};
|
||||||
|
// Clear handles created during this call
|
||||||
|
crate::jit::rt::handles::end_scope_clear();
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,3 +5,8 @@ pub mod engine;
|
|||||||
pub mod lower;
|
pub mod lower;
|
||||||
pub mod r#extern;
|
pub mod r#extern;
|
||||||
pub mod rt;
|
pub mod rt;
|
||||||
|
pub mod abi;
|
||||||
|
pub mod config;
|
||||||
|
pub mod policy;
|
||||||
|
pub mod events;
|
||||||
|
pub mod hostcall_registry;
|
||||||
|
|||||||
42
src/jit/policy.rs
Normal file
42
src/jit/policy.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
//! JIT Policy (Box-First): centralizes runtime decisions
|
||||||
|
//!
|
||||||
|
//! Minimal v0:
|
||||||
|
//! - read_only: if true, deny write-effects in jit-direct and other independent paths
|
||||||
|
//! - hostcall_whitelist: symbolic names allowed (future use)
|
||||||
|
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use std::sync::RwLock;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct JitPolicy {
|
||||||
|
pub read_only: bool,
|
||||||
|
pub hostcall_whitelist: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JitPolicy {
|
||||||
|
pub fn from_env() -> Self {
|
||||||
|
let ro = std::env::var("NYASH_JIT_READ_ONLY").ok().as_deref() == Some("1");
|
||||||
|
// Comma-separated hostcall names
|
||||||
|
let hc = std::env::var("NYASH_JIT_HOSTCALL_WHITELIST").ok().map(|s| {
|
||||||
|
s.split(',').map(|t| t.trim().to_string()).filter(|s| !s.is_empty()).collect::<Vec<_>>()
|
||||||
|
}).unwrap_or_default();
|
||||||
|
Self { read_only: ro, hostcall_whitelist: hc }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static GLOBAL: OnceCell<RwLock<JitPolicy>> = OnceCell::new();
|
||||||
|
|
||||||
|
pub fn current() -> JitPolicy {
|
||||||
|
if let Some(l) = GLOBAL.get() {
|
||||||
|
if let Ok(g) = l.read() { return g.clone(); }
|
||||||
|
}
|
||||||
|
JitPolicy::from_env()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_current(p: JitPolicy) {
|
||||||
|
if let Some(l) = GLOBAL.get() {
|
||||||
|
if let Ok(mut w) = l.write() { *w = p; return; }
|
||||||
|
}
|
||||||
|
let _ = GLOBAL.set(RwLock::new(p));
|
||||||
|
}
|
||||||
|
|
||||||
125
src/jit/rt.rs
125
src/jit/rt.rs
@ -1,25 +1,140 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
use crate::backend::vm::VMValue;
|
use crate::backend::vm::VMValue;
|
||||||
|
use crate::jit::abi::JitValue;
|
||||||
|
|
||||||
|
// Legacy TLS for hostcalls that still expect VMValue — keep for compatibility
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static CURRENT_ARGS: RefCell<Vec<VMValue>> = RefCell::new(Vec::new());
|
static LEGACY_VM_ARGS: RefCell<Vec<VMValue>> = RefCell::new(Vec::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_current_args(args: &[VMValue]) {
|
pub fn set_legacy_vm_args(args: &[VMValue]) {
|
||||||
CURRENT_ARGS.with(|cell| {
|
LEGACY_VM_ARGS.with(|cell| {
|
||||||
let mut v = cell.borrow_mut();
|
let mut v = cell.borrow_mut();
|
||||||
v.clear();
|
v.clear();
|
||||||
v.extend_from_slice(args);
|
v.extend_from_slice(args);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_args<F, R>(f: F) -> R
|
pub fn with_legacy_vm_args<F, R>(f: F) -> R
|
||||||
where
|
where
|
||||||
F: FnOnce(&[VMValue]) -> R,
|
F: FnOnce(&[VMValue]) -> R,
|
||||||
{
|
{
|
||||||
CURRENT_ARGS.with(|cell| {
|
LEGACY_VM_ARGS.with(|cell| {
|
||||||
let v = cell.borrow();
|
let v = cell.borrow();
|
||||||
f(&v)
|
f(&v)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New TLS for independent JIT ABI values
|
||||||
|
thread_local! {
|
||||||
|
static CURRENT_JIT_ARGS: RefCell<Vec<JitValue>> = RefCell::new(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_current_jit_args(args: &[JitValue]) {
|
||||||
|
CURRENT_JIT_ARGS.with(|cell| {
|
||||||
|
let mut v = cell.borrow_mut();
|
||||||
|
v.clear();
|
||||||
|
v.extend_from_slice(args);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_jit_args<F, R>(f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&[JitValue]) -> R,
|
||||||
|
{
|
||||||
|
CURRENT_JIT_ARGS.with(|cell| {
|
||||||
|
let v = cell.borrow();
|
||||||
|
f(&v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// === JIT runtime counters (minimal) ===
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
static B1_NORM_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||||
|
static RET_BOOL_HINT_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||||
|
static PHI_TOTAL_SLOTS: AtomicU64 = AtomicU64::new(0);
|
||||||
|
static PHI_B1_SLOTS: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
pub fn b1_norm_inc(delta: u64) { B1_NORM_COUNT.fetch_add(delta, Ordering::Relaxed); }
|
||||||
|
pub fn b1_norm_get() -> u64 { B1_NORM_COUNT.load(Ordering::Relaxed) }
|
||||||
|
|
||||||
|
pub fn ret_bool_hint_inc(delta: u64) { RET_BOOL_HINT_COUNT.fetch_add(delta, Ordering::Relaxed); }
|
||||||
|
pub fn ret_bool_hint_get() -> u64 { RET_BOOL_HINT_COUNT.load(Ordering::Relaxed) }
|
||||||
|
|
||||||
|
pub fn phi_total_inc(delta: u64) { PHI_TOTAL_SLOTS.fetch_add(delta, Ordering::Relaxed); }
|
||||||
|
pub fn phi_total_get() -> u64 { PHI_TOTAL_SLOTS.load(Ordering::Relaxed) }
|
||||||
|
pub fn phi_b1_inc(delta: u64) { PHI_B1_SLOTS.fetch_add(delta, Ordering::Relaxed); }
|
||||||
|
pub fn phi_b1_get() -> u64 { PHI_B1_SLOTS.load(Ordering::Relaxed) }
|
||||||
|
|
||||||
|
// === 10.7c PoC: JIT Handle Registry (thread-local) ===
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub mod handles {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static REG: RefCell<HandleRegistry> = RefCell::new(HandleRegistry::new());
|
||||||
|
static CREATED: RefCell<Vec<u64>> = RefCell::new(Vec::new());
|
||||||
|
static SCOPES: RefCell<Vec<usize>> = RefCell::new(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HandleRegistry {
|
||||||
|
next: u64,
|
||||||
|
map: HashMap<u64, Arc<dyn crate::box_trait::NyashBox>>, // BoxRef-compatible
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HandleRegistry {
|
||||||
|
fn new() -> Self { Self { next: 1, map: HashMap::new() } }
|
||||||
|
fn to_handle(&mut self, obj: Arc<dyn crate::box_trait::NyashBox>) -> u64 {
|
||||||
|
// Reuse existing handle if already present (pointer equality check)
|
||||||
|
// For PoC simplicity, always assign new handle
|
||||||
|
let h = self.next;
|
||||||
|
self.next = self.next.saturating_add(1);
|
||||||
|
self.map.insert(h, obj);
|
||||||
|
if std::env::var("NYASH_JIT_HANDLE_DEBUG").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("[JIT][handle] new h={}", h);
|
||||||
|
}
|
||||||
|
h
|
||||||
|
}
|
||||||
|
fn get(&self, h: u64) -> Option<Arc<dyn crate::box_trait::NyashBox>> { self.map.get(&h).cloned() }
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn drop_handle(&mut self, h: u64) { self.map.remove(&h); }
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn clear(&mut self) { self.map.clear(); self.next = 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_handle(obj: Arc<dyn crate::box_trait::NyashBox>) -> u64 {
|
||||||
|
let h = REG.with(|cell| cell.borrow_mut().to_handle(obj));
|
||||||
|
CREATED.with(|c| c.borrow_mut().push(h));
|
||||||
|
h
|
||||||
|
}
|
||||||
|
pub fn get(h: u64) -> Option<Arc<dyn crate::box_trait::NyashBox>> {
|
||||||
|
REG.with(|cell| cell.borrow().get(h))
|
||||||
|
}
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn clear() { REG.with(|cell| cell.borrow_mut().clear()); }
|
||||||
|
pub fn len() -> usize { REG.with(|cell| cell.borrow().map.len()) }
|
||||||
|
|
||||||
|
// Scope management: track and clear handles created within a JIT call
|
||||||
|
pub fn begin_scope() {
|
||||||
|
CREATED.with(|c| {
|
||||||
|
let cur_len = c.borrow().len();
|
||||||
|
SCOPES.with(|s| s.borrow_mut().push(cur_len));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pub fn end_scope_clear() {
|
||||||
|
let start = SCOPES.with(|s| s.borrow_mut().pop()).unwrap_or(0);
|
||||||
|
let to_drop: Vec<u64> = CREATED.with(|c| {
|
||||||
|
let mut v = c.borrow_mut();
|
||||||
|
let slice = v[start..].to_vec();
|
||||||
|
v.truncate(start);
|
||||||
|
slice
|
||||||
|
});
|
||||||
|
REG.with(|cell| {
|
||||||
|
let mut reg = cell.borrow_mut();
|
||||||
|
for h in to_drop { reg.map.remove(&h); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
227
src/runner.rs
227
src/runner.rs
@ -64,6 +64,32 @@ impl NyashRunner {
|
|||||||
// Prefer explicit JSON flag over any default
|
// Prefer explicit JSON flag over any default
|
||||||
std::env::set_var("NYASH_VM_STATS_JSON", "1");
|
std::env::set_var("NYASH_VM_STATS_JSON", "1");
|
||||||
}
|
}
|
||||||
|
// Optional: JIT controls via CLI flags (centralized)
|
||||||
|
{
|
||||||
|
let mut jc = nyash_rust::jit::config::JitConfig::from_env();
|
||||||
|
jc.exec |= self.config.jit_exec;
|
||||||
|
jc.stats |= self.config.jit_stats;
|
||||||
|
jc.stats_json |= self.config.jit_stats_json;
|
||||||
|
jc.dump |= self.config.jit_dump;
|
||||||
|
if self.config.jit_threshold.is_some() { jc.threshold = self.config.jit_threshold; }
|
||||||
|
jc.phi_min |= self.config.jit_phi_min;
|
||||||
|
jc.hostcall |= self.config.jit_hostcall;
|
||||||
|
jc.handle_debug |= self.config.jit_handle_debug;
|
||||||
|
jc.native_f64 |= self.config.jit_native_f64;
|
||||||
|
jc.native_bool |= self.config.jit_native_bool;
|
||||||
|
if self.config.jit_only { std::env::set_var("NYASH_JIT_ONLY", "1"); }
|
||||||
|
// Apply runtime capability probe (e.g., disable b1 ABI if unsupported)
|
||||||
|
let caps = nyash_rust::jit::config::probe_capabilities();
|
||||||
|
jc = nyash_rust::jit::config::apply_runtime_caps(jc, caps);
|
||||||
|
// Optional DOT emit via CLI (ensures dump is on when path specified)
|
||||||
|
if let Some(path) = &self.config.emit_cfg {
|
||||||
|
std::env::set_var("NYASH_JIT_DOT", path);
|
||||||
|
jc.dump = true;
|
||||||
|
}
|
||||||
|
// Persist to env (CLI parity) and set as current
|
||||||
|
jc.apply_env();
|
||||||
|
nyash_rust::jit::config::set_current(jc.clone());
|
||||||
|
}
|
||||||
// Benchmark mode - can run without a file
|
// Benchmark mode - can run without a file
|
||||||
if self.config.benchmark {
|
if self.config.benchmark {
|
||||||
println!("📊 Nyash Performance Benchmark Suite");
|
println!("📊 Nyash Performance Benchmark Suite");
|
||||||
@ -76,6 +102,11 @@ impl NyashRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref filename) = self.config.file {
|
if let Some(ref filename) = self.config.file {
|
||||||
|
// Independent JIT direct mode (no VM execute path)
|
||||||
|
if self.config.jit_direct {
|
||||||
|
self.run_file_jit_direct(filename);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Delegate file-mode execution to modes::common dispatcher
|
// Delegate file-mode execution to modes::common dispatcher
|
||||||
self.run_file(filename);
|
self.run_file(filename);
|
||||||
} else {
|
} else {
|
||||||
@ -501,6 +532,202 @@ impl NyashRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl NyashRunner {
|
||||||
|
/// Run a file through independent JIT engine (no VM execute loop)
|
||||||
|
fn run_file_jit_direct(&self, filename: &str) {
|
||||||
|
use std::fs;
|
||||||
|
use nyash_rust::{parser::NyashParser, mir::MirCompiler};
|
||||||
|
// Small helper for unified error output (text or JSON)
|
||||||
|
let emit_err = |phase: &str, code: &str, msg: &str| {
|
||||||
|
if std::env::var("NYASH_JIT_STATS_JSON").ok().as_deref() == Some("1")
|
||||||
|
|| std::env::var("NYASH_JIT_ERROR_JSON").ok().as_deref() == Some("1") {
|
||||||
|
let payload = serde_json::json!({
|
||||||
|
"kind": "jit_direct_error",
|
||||||
|
"phase": phase,
|
||||||
|
"code": code,
|
||||||
|
"message": msg,
|
||||||
|
"file": filename,
|
||||||
|
});
|
||||||
|
println!("{}", payload.to_string());
|
||||||
|
} else {
|
||||||
|
eprintln!("[JIT-direct][{}][{}] {}", phase, code, msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Require cranelift feature at runtime by attempting compile; if unavailable compile_function returns None
|
||||||
|
let code = match fs::read_to_string(filename) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => { emit_err("read_file", "IO", &format!("{}", e)); std::process::exit(1); }
|
||||||
|
};
|
||||||
|
let ast = match NyashParser::parse_from_string(&code) {
|
||||||
|
Ok(a) => a, Err(e) => { emit_err("parse", "SYNTAX", &format!("{}", e)); std::process::exit(1); }
|
||||||
|
};
|
||||||
|
let mut mc = MirCompiler::new();
|
||||||
|
let cr = match mc.compile(ast) { Ok(m) => m, Err(e) => { emit_err("mir", "MIR_COMPILE", &format!("{}", e)); std::process::exit(1); } };
|
||||||
|
let func = match cr.module.functions.get("main") { Some(f) => f, None => { emit_err("mir", "NO_MAIN", "No main function found"); std::process::exit(1); } };
|
||||||
|
|
||||||
|
// Guard: refuse write-effects in jit-direct when policy.read_only
|
||||||
|
{
|
||||||
|
use nyash_rust::mir::MirInstruction;
|
||||||
|
use nyash_rust::mir::effect::Effect;
|
||||||
|
let policy = nyash_rust::jit::policy::current();
|
||||||
|
let mut writes = 0usize;
|
||||||
|
for (_bbid, bb) in func.blocks.iter() {
|
||||||
|
for inst in bb.instructions.iter() {
|
||||||
|
let mask = inst.effects();
|
||||||
|
if mask.contains(Effect::WriteHeap) {
|
||||||
|
writes += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(term) = &bb.terminator {
|
||||||
|
if term.effects().contains(Effect::WriteHeap) { writes += 1; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if policy.read_only && writes > 0 {
|
||||||
|
emit_err("policy", "WRITE_EFFECTS", &format!("write-effects detected ({} ops). jit-direct is read-only at this stage.", writes));
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut engine = nyash_rust::jit::engine::JitEngine::new();
|
||||||
|
match engine.compile_function("main", func) {
|
||||||
|
Some(h) => {
|
||||||
|
// Optional event: compile
|
||||||
|
nyash_rust::jit::events::emit("compile", &func.signature.name, Some(h), None, serde_json::json!({}));
|
||||||
|
// Parse JIT args from env: NYASH_JIT_ARGS (comma-separated), with optional type prefixes
|
||||||
|
// Formats per arg: i:123, f:3.14, b:true/false, h:42 (handle), or bare numbers (int), true/false (bool)
|
||||||
|
let mut jit_args: Vec<nyash_rust::jit::abi::JitValue> = Vec::new();
|
||||||
|
if let Ok(s) = std::env::var("NYASH_JIT_ARGS") {
|
||||||
|
for raw in s.split(',') {
|
||||||
|
let t = raw.trim();
|
||||||
|
if t.is_empty() { continue; }
|
||||||
|
let v = if let Some(rest) = t.strip_prefix("i:") {
|
||||||
|
rest.parse::<i64>().ok().map(nyash_rust::jit::abi::JitValue::I64)
|
||||||
|
} else if let Some(rest) = t.strip_prefix("f:") {
|
||||||
|
rest.parse::<f64>().ok().map(nyash_rust::jit::abi::JitValue::F64)
|
||||||
|
} else if let Some(rest) = t.strip_prefix("b:") {
|
||||||
|
let b = matches!(rest, "1"|"true"|"True"|"TRUE");
|
||||||
|
Some(nyash_rust::jit::abi::JitValue::Bool(b))
|
||||||
|
} else if let Some(rest) = t.strip_prefix("h:") {
|
||||||
|
rest.parse::<u64>().ok().map(nyash_rust::jit::abi::JitValue::Handle)
|
||||||
|
} else if t.eq_ignore_ascii_case("true") || t == "1" { Some(nyash_rust::jit::abi::JitValue::Bool(true)) }
|
||||||
|
else if t.eq_ignore_ascii_case("false") || t == "0" { Some(nyash_rust::jit::abi::JitValue::Bool(false)) }
|
||||||
|
else if let Ok(iv) = t.parse::<i64>() { Some(nyash_rust::jit::abi::JitValue::I64(iv)) }
|
||||||
|
else if let Ok(fv) = t.parse::<f64>() { Some(nyash_rust::jit::abi::JitValue::F64(fv)) }
|
||||||
|
else { None };
|
||||||
|
if let Some(jv) = v { jit_args.push(jv); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Coerce args to expected MIR types
|
||||||
|
use nyash_rust::mir::MirType;
|
||||||
|
let expected = &func.signature.params;
|
||||||
|
if expected.len() != jit_args.len() {
|
||||||
|
emit_err("args", "COUNT_MISMATCH", &format!("expected={}, passed={}", expected.len(), jit_args.len()));
|
||||||
|
eprintln!("Hint: set NYASH_JIT_ARGS as comma-separated values, e.g., i:42,f:3.14,b:true");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
let mut coerced: Vec<nyash_rust::jit::abi::JitValue> = Vec::with_capacity(jit_args.len());
|
||||||
|
for (i, (exp, got)) in expected.iter().zip(jit_args.iter()).enumerate() {
|
||||||
|
let cv = match exp {
|
||||||
|
MirType::Integer => match got {
|
||||||
|
nyash_rust::jit::abi::JitValue::I64(v) => nyash_rust::jit::abi::JitValue::I64(*v),
|
||||||
|
nyash_rust::jit::abi::JitValue::F64(f) => nyash_rust::jit::abi::JitValue::I64(*f as i64),
|
||||||
|
nyash_rust::jit::abi::JitValue::Bool(b) => nyash_rust::jit::abi::JitValue::I64(if *b {1} else {0}),
|
||||||
|
_ => { emit_err("args", "TYPE_MISMATCH", &format!("param#{} expects Integer", i)); std::process::exit(1); }
|
||||||
|
},
|
||||||
|
MirType::Float => match got {
|
||||||
|
nyash_rust::jit::abi::JitValue::F64(f) => nyash_rust::jit::abi::JitValue::F64(*f),
|
||||||
|
nyash_rust::jit::abi::JitValue::I64(v) => nyash_rust::jit::abi::JitValue::F64(*v as f64),
|
||||||
|
nyash_rust::jit::abi::JitValue::Bool(b) => nyash_rust::jit::abi::JitValue::F64(if *b {1.0} else {0.0}),
|
||||||
|
_ => { emit_err("args", "TYPE_MISMATCH", &format!("param#{} expects Float", i)); std::process::exit(1); }
|
||||||
|
},
|
||||||
|
MirType::Bool => match got {
|
||||||
|
nyash_rust::jit::abi::JitValue::Bool(b) => nyash_rust::jit::abi::JitValue::Bool(*b),
|
||||||
|
nyash_rust::jit::abi::JitValue::I64(v) => nyash_rust::jit::abi::JitValue::Bool(*v != 0),
|
||||||
|
nyash_rust::jit::abi::JitValue::F64(f) => nyash_rust::jit::abi::JitValue::Bool(*f != 0.0),
|
||||||
|
_ => { emit_err("args", "TYPE_MISMATCH", &format!("param#{} expects Bool", i)); std::process::exit(1); }
|
||||||
|
},
|
||||||
|
MirType::String | MirType::Box(_) | MirType::Array(_) | MirType::Future(_) => match got {
|
||||||
|
nyash_rust::jit::abi::JitValue::Handle(h) => nyash_rust::jit::abi::JitValue::Handle(*h),
|
||||||
|
_ => { emit_err("args", "TYPE_MISMATCH", &format!("param#{} expects handle (h:<id>)", i)); std::process::exit(1); }
|
||||||
|
},
|
||||||
|
MirType::Void | MirType::Unknown => {
|
||||||
|
// Keep as-is
|
||||||
|
*got
|
||||||
|
}
|
||||||
|
};
|
||||||
|
coerced.push(cv);
|
||||||
|
}
|
||||||
|
nyash_rust::jit::rt::set_current_jit_args(&coerced);
|
||||||
|
let t0 = std::time::Instant::now();
|
||||||
|
let out = engine.execute_handle(h, &coerced);
|
||||||
|
match out {
|
||||||
|
Some(v) => {
|
||||||
|
let ms = t0.elapsed().as_millis();
|
||||||
|
nyash_rust::jit::events::emit("execute", &func.signature.name, Some(h), Some(ms), serde_json::json!({}));
|
||||||
|
// Normalize result according to MIR return type for friendly output
|
||||||
|
use nyash_rust::mir::MirType;
|
||||||
|
let ret_ty = &func.signature.return_type;
|
||||||
|
let vmv = match (ret_ty, v) {
|
||||||
|
(MirType::Bool, nyash_rust::jit::abi::JitValue::I64(i)) => nyash_rust::backend::vm::VMValue::Bool(i != 0),
|
||||||
|
(MirType::Bool, nyash_rust::jit::abi::JitValue::Bool(b)) => nyash_rust::backend::vm::VMValue::Bool(b),
|
||||||
|
(MirType::Float, nyash_rust::jit::abi::JitValue::F64(f)) => nyash_rust::backend::vm::VMValue::Float(f),
|
||||||
|
(MirType::Float, nyash_rust::jit::abi::JitValue::I64(i)) => nyash_rust::backend::vm::VMValue::Float(i as f64),
|
||||||
|
// Default adapter for other combos
|
||||||
|
_ => nyash_rust::jit::abi::adapter::from_jit_value(v),
|
||||||
|
};
|
||||||
|
println!("✅ JIT-direct execution completed successfully!");
|
||||||
|
// Pretty print with expected type tag
|
||||||
|
let (ety, sval) = match (ret_ty, &vmv) {
|
||||||
|
(MirType::Bool, nyash_rust::backend::vm::VMValue::Bool(b)) => ("Bool", b.to_string()),
|
||||||
|
(MirType::Float, nyash_rust::backend::vm::VMValue::Float(f)) => ("Float", format!("{}", f)),
|
||||||
|
(MirType::Integer, nyash_rust::backend::vm::VMValue::Integer(i)) => ("Integer", i.to_string()),
|
||||||
|
// Fallbacks
|
||||||
|
(_, nyash_rust::backend::vm::VMValue::Integer(i)) => ("Integer", i.to_string()),
|
||||||
|
(_, nyash_rust::backend::vm::VMValue::Float(f)) => ("Float", format!("{}", f)),
|
||||||
|
(_, nyash_rust::backend::vm::VMValue::Bool(b)) => ("Bool", b.to_string()),
|
||||||
|
(_, nyash_rust::backend::vm::VMValue::String(s)) => ("String", s.clone()),
|
||||||
|
(_, nyash_rust::backend::vm::VMValue::BoxRef(arc)) => ("BoxRef", arc.type_name().to_string()),
|
||||||
|
(_, nyash_rust::backend::vm::VMValue::Future(_)) => ("Future", "<future>".to_string()),
|
||||||
|
(_, nyash_rust::backend::vm::VMValue::Void) => ("Void", "void".to_string()),
|
||||||
|
};
|
||||||
|
println!("ResultType(MIR): {}", ety);
|
||||||
|
println!("Result: {}", sval);
|
||||||
|
// Optional JSON stats
|
||||||
|
if std::env::var("NYASH_JIT_STATS_JSON").ok().as_deref() == Some("1") {
|
||||||
|
let cfg = nyash_rust::jit::config::current();
|
||||||
|
let caps = nyash_rust::jit::config::probe_capabilities();
|
||||||
|
let (phi_t, phi_b1, ret_b) = engine.last_lower_stats();
|
||||||
|
let abi_mode = if cfg.native_bool_abi && caps.supports_b1_sig { "b1_bool" } else { "i64_bool" };
|
||||||
|
let payload = serde_json::json!({
|
||||||
|
"version": 1,
|
||||||
|
"function": func.signature.name,
|
||||||
|
"abi_mode": abi_mode,
|
||||||
|
"abi_b1_enabled": cfg.native_bool_abi,
|
||||||
|
"abi_b1_supported": caps.supports_b1_sig,
|
||||||
|
"b1_norm_count": nyash_rust::jit::rt::b1_norm_get(),
|
||||||
|
"ret_bool_hint_count": nyash_rust::jit::rt::ret_bool_hint_get(),
|
||||||
|
"phi_total_slots": phi_t,
|
||||||
|
"phi_b1_slots": phi_b1,
|
||||||
|
"ret_bool_hint_used": ret_b,
|
||||||
|
});
|
||||||
|
println!("{}", payload.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
nyash_rust::jit::events::emit("fallback", &func.signature.name, Some(h), None, serde_json::json!({"reason":"trap_or_missing"}));
|
||||||
|
emit_err("execute", "TRAP_OR_MISSING", "execution failed (trap or missing handle)");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
emit_err("compile", "UNAVAILABLE", "Build with --features cranelift-jit");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Demo functions (moved from main.rs)
|
// Demo functions (moved from main.rs)
|
||||||
fn demo_basic_boxes() {
|
fn demo_basic_boxes() {
|
||||||
println!("\n📦 1. Basic Box Creation:");
|
println!("\n📦 1. Basic Box Creation:");
|
||||||
|
|||||||
@ -5,8 +5,9 @@ impl NyashRunner {
|
|||||||
/// Execute benchmark mode (split)
|
/// Execute benchmark mode (split)
|
||||||
pub(crate) fn execute_benchmark_mode(&self) {
|
pub(crate) fn execute_benchmark_mode(&self) {
|
||||||
println!("🏁 Running benchmark mode with {} iterations", self.config.iterations);
|
println!("🏁 Running benchmark mode with {} iterations", self.config.iterations);
|
||||||
// Two tests: simple add, arithmetic loop
|
// Tests: some run on all backends, some are JIT+f64 only
|
||||||
let tests: Vec<(&str, &str)> = vec![
|
// Third element indicates JIT+f64 only (skip VM/Interpreter)
|
||||||
|
let tests: Vec<(&str, &str, bool)> = vec![
|
||||||
(
|
(
|
||||||
"simple_add",
|
"simple_add",
|
||||||
r#"
|
r#"
|
||||||
@ -16,6 +17,7 @@ impl NyashRunner {
|
|||||||
y = x + 58
|
y = x + 58
|
||||||
return y
|
return y
|
||||||
"#,
|
"#,
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"arith_loop_100k",
|
"arith_loop_100k",
|
||||||
@ -29,6 +31,7 @@ impl NyashRunner {
|
|||||||
}
|
}
|
||||||
return sum
|
return sum
|
||||||
"#,
|
"#,
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"branch_return",
|
"branch_return",
|
||||||
@ -42,33 +45,63 @@ impl NyashRunner {
|
|||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"f64_add_jit",
|
||||||
|
r#"
|
||||||
|
local x, y
|
||||||
|
x = 1.5
|
||||||
|
y = 2.25
|
||||||
|
return x + y
|
||||||
|
"#,
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (name, code) in tests {
|
for (name, code, jit_f64_only) in tests {
|
||||||
println!("\n====================================");
|
println!("\n====================================");
|
||||||
println!("🧪 Test: {}", name);
|
println!("🧪 Test: {}", name);
|
||||||
// Warmup (not measured)
|
if jit_f64_only {
|
||||||
let warmup = (self.config.iterations / 10).max(1);
|
println!("(JIT+f64 only) Skipping VM/Interpreter; requires --features cranelift-jit");
|
||||||
self.bench_interpreter(code, warmup);
|
// Warmup JIT
|
||||||
self.bench_vm(code, warmup);
|
let warmup = (self.config.iterations / 10).max(1);
|
||||||
self.bench_jit(code, warmup);
|
self.bench_jit(code, warmup);
|
||||||
|
// Measured
|
||||||
|
let jit_time = self.bench_jit(code, self.config.iterations);
|
||||||
|
println!("\n📊 Performance Summary [{}]:", name);
|
||||||
|
println!(" JIT f64 ops: {} iters in {:?} ({:.2} ops/sec)", self.config.iterations, jit_time, self.config.iterations as f64 / jit_time.as_secs_f64());
|
||||||
|
} else {
|
||||||
|
// Quick correctness check across modes (golden): Interpreter vs VM vs VM+JIT
|
||||||
|
if let Err(e) = self.verify_outputs_match(code) {
|
||||||
|
println!("❌ Output mismatch: {}", e);
|
||||||
|
} else {
|
||||||
|
println!("✅ Outputs match across Interpreter/VM/JIT");
|
||||||
|
}
|
||||||
|
// Warmup (not measured)
|
||||||
|
let warmup = (self.config.iterations / 10).max(1);
|
||||||
|
self.bench_interpreter(code, warmup);
|
||||||
|
self.bench_vm(code, warmup);
|
||||||
|
self.bench_jit(code, warmup);
|
||||||
|
|
||||||
// Measured runs
|
// Measured runs
|
||||||
let interpreter_time = self.bench_interpreter(code, self.config.iterations);
|
let interpreter_time = self.bench_interpreter(code, self.config.iterations);
|
||||||
let vm_time = self.bench_vm(code, self.config.iterations);
|
let vm_time = self.bench_vm(code, self.config.iterations);
|
||||||
let jit_time = self.bench_jit(code, self.config.iterations);
|
let jit_time = self.bench_jit(code, self.config.iterations);
|
||||||
|
|
||||||
// Summary
|
// Summary
|
||||||
let vm_vs_interp = interpreter_time.as_secs_f64() / vm_time.as_secs_f64();
|
let vm_vs_interp = interpreter_time.as_secs_f64() / vm_time.as_secs_f64();
|
||||||
let jit_vs_vm = vm_time.as_secs_f64() / jit_time.as_secs_f64();
|
let jit_vs_vm = vm_time.as_secs_f64() / jit_time.as_secs_f64();
|
||||||
println!("\n📊 Performance Summary [{}]:", name);
|
println!("\n📊 Performance Summary [{}]:", name);
|
||||||
println!(" VM is {:.2}x {} than Interpreter", if vm_vs_interp > 1.0 { vm_vs_interp } else { 1.0 / vm_vs_interp }, if vm_vs_interp > 1.0 { "faster" } else { "slower" });
|
println!(" VM is {:.2}x {} than Interpreter", if vm_vs_interp > 1.0 { vm_vs_interp } else { 1.0 / vm_vs_interp }, if vm_vs_interp > 1.0 { "faster" } else { "slower" });
|
||||||
println!(" JIT is {:.2}x {} than VM (note: compile cost included)", if jit_vs_vm > 1.0 { jit_vs_vm } else { 1.0 / jit_vs_vm }, if jit_vs_vm > 1.0 { "faster" } else { "slower" });
|
println!(" JIT is {:.2}x {} than VM (note: compile cost included)", if jit_vs_vm > 1.0 { jit_vs_vm } else { 1.0 / jit_vs_vm }, if jit_vs_vm > 1.0 { "faster" } else { "slower" });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_interpreter(&self, code: &str, iters: u32) -> std::time::Duration {
|
fn bench_interpreter(&self, code: &str, iters: u32) -> std::time::Duration {
|
||||||
|
// Enable native f64 when available to exercise widened ABI
|
||||||
|
std::env::set_var("NYASH_JIT_NATIVE_F64", "1");
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
for _ in 0..iters {
|
for _ in 0..iters {
|
||||||
if let Ok(ast) = NyashParser::parse_from_string(code) {
|
if let Ok(ast) = NyashParser::parse_from_string(code) {
|
||||||
@ -101,6 +134,8 @@ impl NyashRunner {
|
|||||||
// Force JIT mode for this run
|
// Force JIT mode for this run
|
||||||
std::env::set_var("NYASH_JIT_EXEC", "1");
|
std::env::set_var("NYASH_JIT_EXEC", "1");
|
||||||
std::env::set_var("NYASH_JIT_THRESHOLD", "1");
|
std::env::set_var("NYASH_JIT_THRESHOLD", "1");
|
||||||
|
if self.config.jit_stats { std::env::set_var("NYASH_JIT_STATS", "1"); }
|
||||||
|
if self.config.jit_stats_json { std::env::set_var("NYASH_JIT_STATS_JSON", "1"); }
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
for _ in 0..iters {
|
for _ in 0..iters {
|
||||||
if let Ok(ast) = NyashParser::parse_from_string(code) {
|
if let Ok(ast) = NyashParser::parse_from_string(code) {
|
||||||
@ -115,4 +150,32 @@ impl NyashRunner {
|
|||||||
println!(" 🔥 JIT: {} iters in {:?} ({:.2} ops/sec)", iters, elapsed, iters as f64 / elapsed.as_secs_f64());
|
println!(" 🔥 JIT: {} iters in {:?} ({:.2} ops/sec)", iters, elapsed, iters as f64 / elapsed.as_secs_f64());
|
||||||
elapsed
|
elapsed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verify that outputs match across VM and JIT-enabled VM (golden)
|
||||||
|
fn verify_outputs_match(&self, code: &str) -> Result<(), String> {
|
||||||
|
// VM
|
||||||
|
let vm_out = {
|
||||||
|
let ast = NyashParser::parse_from_string(code).map_err(|e| format!("vm parse: {}", e))?;
|
||||||
|
let mut mc = MirCompiler::new();
|
||||||
|
let cr = mc.compile(ast).map_err(|e| format!("vm compile: {}", e))?;
|
||||||
|
let mut vm = VM::new();
|
||||||
|
let out = vm.execute_module(&cr.module).map_err(|e| format!("vm exec: {}", e))?;
|
||||||
|
out.to_string_box().value
|
||||||
|
};
|
||||||
|
// VM+JIT
|
||||||
|
let jit_out = {
|
||||||
|
std::env::set_var("NYASH_JIT_EXEC", "1");
|
||||||
|
std::env::set_var("NYASH_JIT_THRESHOLD", "1");
|
||||||
|
let ast = NyashParser::parse_from_string(code).map_err(|e| format!("jit parse: {}", e))?;
|
||||||
|
let mut mc = MirCompiler::new();
|
||||||
|
let cr = mc.compile(ast).map_err(|e| format!("jit compile: {}", e))?;
|
||||||
|
let mut vm = VM::new();
|
||||||
|
let out = vm.execute_module(&cr.module).map_err(|e| format!("jit exec: {}", e))?;
|
||||||
|
out.to_string_box().value
|
||||||
|
};
|
||||||
|
if vm_out != jit_out {
|
||||||
|
return Err(format!("vm='{}' jit='{}'", vm_out, jit_out));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user