feat: Phase 10 reorganization + GC switchable runtime + VM improvements
## 📚 Documentation Updates - Phase 10 reorganized with comprehensive README - Cranelift JIT as main implementation - NEW: Phase 10.4 GC Switchable Runtime (world's first\!) - Phase 10.5 Self-hosting (parallel) - Application migration tests - Phase 11 created for LLVM AOT research (deferred) - Moved phase10_aot_scaffolding.md → Phase 11 - Moved phase_10_x_llvm_backend_skeleton.md → Phase 11 - Master roadmap updated with GC runtime feature - Ideas: GC switchable language concept documented ## 🚀 VM Implementation Progress (by ChatGPT5) - src/backend/vm.rs: Enhanced VM execution - src/backend/vm_instructions.rs: Instruction improvements - src/runtime/type_meta.rs: NEW - Type metadata system - src/boxes/buffer/mod.rs: Buffer optimizations - src/runtime/mod.rs & plugin_ffi_common.rs: Runtime enhancements ## 🌟 Revolutionary Feature: GC Switchable Runtime - Development mode: GC on (convenience) - Production mode: GC off (performance) - Technical feasibility confirmed by Codex GPT-5 - Implementation plan: After Cranelift JIT ## 📋 Phase 10 Structure Phase 10.0: Cranelift JIT foundation Phase 10.1-10.3: JIT implementation & optimization Phase 10.4: GC Switchable Runtime ← NEW\! Phase 10.5: Self-hosting (String/Array/Map in Nyash) Phase 10.9: Application migration tests 🤖 ChatGPT5 says: Ready for Phase 10\! どきどきにゃ!
This commit is contained in:
@ -111,6 +111,7 @@ nyash bid gen --target llvm bid.yaml # AOT用declare生成(LLVM実装時)
|
|||||||
- MIR→VMを維持しつつ、ホットパスをCraneliftでJIT化
|
- MIR→VMを維持しつつ、ホットパスをCraneliftでJIT化
|
||||||
- 目標: VM比2倍以上の高速化
|
- 目標: VM比2倍以上の高速化
|
||||||
- LLVM AOTは設計資産は維持しつつ、Phase 11以降に検討
|
- LLVM AOTは設計資産は維持しつつ、Phase 11以降に検討
|
||||||
|
- **🌟 NEW: GC切り替え可能ランタイム(世界初の柔軟なメモリ管理)**
|
||||||
|
|
||||||
**Start Gate(着手前の必須完了)**:
|
**Start Gate(着手前の必須完了)**:
|
||||||
- ✅ MIRダイエット(26命令)整合完了
|
- ✅ MIRダイエット(26命令)整合完了
|
||||||
@ -122,6 +123,8 @@ nyash bid gen --target llvm bid.yaml # AOT用declare生成(LLVM実装時)
|
|||||||
1. **Phase 10.1**: Proof of Concept(2週間)
|
1. **Phase 10.1**: Proof of Concept(2週間)
|
||||||
2. **Phase 10.2**: 基本実装(4週間)
|
2. **Phase 10.2**: 基本実装(4週間)
|
||||||
3. **Phase 10.3**: 非同期の扱い(最小)
|
3. **Phase 10.3**: 非同期の扱い(最小)
|
||||||
|
4. **Phase 10.4**: GC切り替え可能ランタイム(2-3ヶ月)
|
||||||
|
5. **Phase 10.5**: セルフホスティング(並行実装)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
61
docs/development/roadmap/phases/phase-10/README.md
Normal file
61
docs/development/roadmap/phases/phase-10/README.md
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# Phase 10: JIT実装とセルフホスティング
|
||||||
|
|
||||||
|
## 🎯 Phase 10の全体像
|
||||||
|
|
||||||
|
Phase 10は、Nyashの実行性能を大幅に向上させるJIT実装と、言語の成熟度を示すセルフホスティングを実現します。
|
||||||
|
|
||||||
|
## 📊 実装優先順位
|
||||||
|
|
||||||
|
### 1️⃣ **メイン実装: Cranelift JIT**
|
||||||
|
→ [phase_10_cranelift_jit_backend.md](phase_10_cranelift_jit_backend.md)
|
||||||
|
- VMとのハイブリッド実行(ホットパス検出→JIT化)
|
||||||
|
- 実装期間: 2-3ヶ月
|
||||||
|
- 目標: ホットパスで2倍以上の高速化
|
||||||
|
|
||||||
|
### 🌟 **革新的機能: GC切り替え可能ランタイム**
|
||||||
|
→ [phase_10_4_gc_switchable_runtime.md](phase_10_4_gc_switchable_runtime.md)
|
||||||
|
- 世界初:実行時にGCモード切り替え可能
|
||||||
|
- 開発時はGCオンで快適、本番はGCオフで高速
|
||||||
|
- 実装期間: 2-3ヶ月(Cranelift JIT後)
|
||||||
|
- 技術的にCodex GPT-5が実現可能性を確認済み
|
||||||
|
|
||||||
|
### 2️⃣ **並行プロジェクト: セルフホスティング**
|
||||||
|
→ [phase_10_5_core_std_nyash_impl.md](phase_10_5_core_std_nyash_impl.md)
|
||||||
|
- String/Array/MapをNyash自身で実装
|
||||||
|
- Rust依存の段階的削減
|
||||||
|
- 実装期間: 1-2ヶ月
|
||||||
|
|
||||||
|
### 3️⃣ **実戦テスト: アプリケーション移植**
|
||||||
|
→ [phase_10_app_migration.md](phase_10_app_migration.md)
|
||||||
|
- Tinyproxy: ゼロコピー判定機能の検証
|
||||||
|
- Chip-8エミュレータ: fini伝播とweak参照の実戦テスト
|
||||||
|
- kiloエディタ: メモリ効率の「うっかり全体コピー」検出
|
||||||
|
|
||||||
|
### 🚫 **延期プロジェクト**
|
||||||
|
→ [Phase 11: LLVM AOT Backend](../phase-11/) - 将来の研究開発として分離
|
||||||
|
|
||||||
|
## 🛤️ 実装ロードマップ
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 9.79b (現在)
|
||||||
|
↓
|
||||||
|
Phase 10.0: Cranelift JIT基盤構築
|
||||||
|
├→ Phase 10.1-10.3: JIT実装・最適化
|
||||||
|
├→ Phase 10.4: GC切り替え可能ランタイム ← NEW!
|
||||||
|
└→ Phase 10.5: セルフホスティング(並行)
|
||||||
|
↓
|
||||||
|
Phase 10.9: アプリケーション移植で実戦検証
|
||||||
|
↓
|
||||||
|
Phase 11: LLVM AOT研究(将来)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📈 期待される成果
|
||||||
|
|
||||||
|
1. **実行性能**: インタープリタ比100倍、VM比2-3倍の高速化
|
||||||
|
2. **言語成熟度**: 基本コンテナのセルフホスティング達成
|
||||||
|
3. **実用性検証**: 実アプリケーションの移植による実戦テスト
|
||||||
|
|
||||||
|
## 🔗 関連ドキュメント
|
||||||
|
- [00_MASTER_ROADMAP.md](../00_MASTER_ROADMAP.md) - 全体計画
|
||||||
|
- [Phase 9.79b](../phase-9/) - 統一Box設計(前提)
|
||||||
|
- [MIR仕様](../../../../reference/mir/) - 中間表現
|
||||||
@ -0,0 +1,173 @@
|
|||||||
|
# Phase 10.4: GC Switchable Runtime - 世界初の柔軟なメモリ管理
|
||||||
|
|
||||||
|
Status: Planned
|
||||||
|
Owner: core-runtime
|
||||||
|
Target: After Cranelift JIT (Phase 10.0)
|
||||||
|
Last Updated: 2025-08-26
|
||||||
|
Dependencies: Phase 10.0 (Cranelift JIT), Phase 9.79b (Unified Box)
|
||||||
|
|
||||||
|
## 🎯 概要
|
||||||
|
|
||||||
|
Nyashを**世界初のGC切り替え可能プログラミング言語**にする革新的機能。開発時はGCで快適に、本番ではGCなしで最高性能を実現。
|
||||||
|
|
||||||
|
## 📊 技術的背景
|
||||||
|
|
||||||
|
### 現状のメモリ管理
|
||||||
|
- Everything is Box哲学(すべてのデータがBoxオブジェクト)
|
||||||
|
- 明示的メモリ管理(スコープベースfini)
|
||||||
|
- Arc<Mutex>によるスレッドセーフ設計
|
||||||
|
|
||||||
|
### 提案する2つのモード
|
||||||
|
1. **Explicit Mode(現在のデフォルト)**
|
||||||
|
- スコープを抜けたら即座にfini()呼び出し
|
||||||
|
- 予測可能な性能(リアルタイムアプリ向け)
|
||||||
|
- メモリ使用量が最小
|
||||||
|
|
||||||
|
2. **Reference Counting Mode(新規)**
|
||||||
|
- 参照カウントが0になったらfini()呼び出し
|
||||||
|
- 循環参照はweak参照で解決
|
||||||
|
- 開発効率重視(一般アプリ向け)
|
||||||
|
|
||||||
|
## 🏗️ アーキテクチャ設計
|
||||||
|
|
||||||
|
### MIR層:所有権イベントの抽象化
|
||||||
|
```rust
|
||||||
|
// GCモードに依存しない所有権表現
|
||||||
|
enum MirOwnership {
|
||||||
|
Move(temp_id), // 所有権移動
|
||||||
|
Copy(temp_id), // 複製
|
||||||
|
Drop(temp_id), // 破棄
|
||||||
|
StorageLive(id), // 生存開始
|
||||||
|
StorageDead(id), // 生存終了
|
||||||
|
Escape(target), // エスケープ解析
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ランタイム層:モード別実装
|
||||||
|
```rust
|
||||||
|
// 統一APIでモード切り替え
|
||||||
|
trait MemoryManager {
|
||||||
|
fn retain_ref(&self, ptr: *const BoxHeader);
|
||||||
|
fn release_ref(&self, ptr: *const BoxHeader);
|
||||||
|
fn destroy(&self, ptr: *const BoxHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ExplicitManager; // 即座に破棄
|
||||||
|
struct RefCountManager; // 参照カウント管理
|
||||||
|
```
|
||||||
|
|
||||||
|
### JIT層:関数マルチバージョン化
|
||||||
|
```
|
||||||
|
関数テーブル:
|
||||||
|
┌─────────────┬──────────────┬──────────────┐
|
||||||
|
│ Function │ Explicit Ver │ RefCount Ver │
|
||||||
|
├─────────────┼──────────────┼──────────────┤
|
||||||
|
│ array_push │ 0x1000_0000 │ 0x2000_0000 │
|
||||||
|
│ map_get │ 0x1000_1000 │ 0x2000_1000 │
|
||||||
|
└─────────────┴──────────────┴──────────────┘
|
||||||
|
|
||||||
|
トランポリン → 現在のモードに応じてジャンプ
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📋 実装計画
|
||||||
|
|
||||||
|
### Phase 10.4.1: 基盤構築(2週間)
|
||||||
|
- [ ] BoxHeaderに参照カウントフィールド追加
|
||||||
|
- [ ] MemoryManagerトレイト定義
|
||||||
|
- [ ] インタープリターでの基本実装
|
||||||
|
|
||||||
|
### Phase 10.4.2: MIR対応(1ヶ月)
|
||||||
|
- [ ] 所有権イベント(Move/Copy/Drop等)の導入
|
||||||
|
- [ ] retain_ref/release_ref命令の追加
|
||||||
|
- [ ] エスケープ解析の基礎実装
|
||||||
|
|
||||||
|
### Phase 10.4.3: 最適化(3週間)
|
||||||
|
- [ ] 近接ペア消去(retain直後のrelease削除)
|
||||||
|
- [ ] ループ不変式の移動
|
||||||
|
- [ ] φノードでの差分管理
|
||||||
|
|
||||||
|
### Phase 10.4.4: JIT統合(1ヶ月)
|
||||||
|
- [ ] 関数マルチバージョン生成
|
||||||
|
- [ ] トランポリン機構実装
|
||||||
|
- [ ] fast path/slow path分離
|
||||||
|
|
||||||
|
### Phase 10.4.5: 実戦投入(2週間)
|
||||||
|
- [ ] モード切り替えCLI実装
|
||||||
|
- [ ] メモリリーク検出ツール
|
||||||
|
- [ ] ベンチマーク・性能評価
|
||||||
|
|
||||||
|
## 🎯 使用例
|
||||||
|
|
||||||
|
### 開発フロー
|
||||||
|
```bash
|
||||||
|
# 1. 開発中:GCオンで快適に開発
|
||||||
|
nyash --gc-mode=ref-counting --detect-leaks dev.nyash
|
||||||
|
|
||||||
|
# 2. テスト:メモリリークがないことを確認
|
||||||
|
nyash --gc-mode=ref-counting --memory-report test.nyash
|
||||||
|
# => No memory leaks detected!
|
||||||
|
|
||||||
|
# 3. 本番:GCオフで最高性能
|
||||||
|
nyash --gc-mode=explicit --optimize prod.nyash
|
||||||
|
```
|
||||||
|
|
||||||
|
### コード例
|
||||||
|
```nyash
|
||||||
|
// 同じコードが両モードで動作
|
||||||
|
box DataProcessor {
|
||||||
|
init { buffer, cache }
|
||||||
|
|
||||||
|
process(data) {
|
||||||
|
me.buffer = data.transform() // GCありなら参照カウント管理
|
||||||
|
me.cache.put(data.id, data) // GCなしなら即座に古い値を破棄
|
||||||
|
return me.buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
fini() {
|
||||||
|
print("Cleanup!") // タイミングはモード次第
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚠️ 技術的課題と解決策
|
||||||
|
|
||||||
|
### 1. Arc<Mutex>の重さ
|
||||||
|
**課題**: 現在すべてのBoxがArc<Mutex>で重い
|
||||||
|
**解決**: 必要な場所のみ同期、基本型は非同期に
|
||||||
|
|
||||||
|
### 2. 実行時オーバーヘッド
|
||||||
|
**課題**: モードチェックのコスト
|
||||||
|
**解決**: JITでの関数マルチバージョン化(間接ジャンプ1回のみ)
|
||||||
|
|
||||||
|
### 3. 循環参照
|
||||||
|
**課題**: RefCountingモードでの循環参照
|
||||||
|
**解決**: 既存のWeakBox活用+明示的切断
|
||||||
|
|
||||||
|
### 4. セマンティクスの違い
|
||||||
|
**課題**: デストラクタ呼び出しタイミングの差
|
||||||
|
**解決**: ドキュメント化+移行ガイド作成
|
||||||
|
|
||||||
|
## 📊 期待される効果
|
||||||
|
|
||||||
|
1. **開発効率**: 30%向上(メモリ管理の負担軽減)
|
||||||
|
2. **実行性能**: GCオフ時は現状維持、GCオン時は5-10%低下
|
||||||
|
3. **メモリ効率**: モード次第で最適化可能
|
||||||
|
4. **教育価値**: メモリ管理の学習に最適なツール
|
||||||
|
|
||||||
|
## 🔗 関連ドキュメント
|
||||||
|
- [Phase 10.0: Cranelift JIT](phase_10_cranelift_jit_backend.md)
|
||||||
|
- [Phase 9.79b: Unified Box Design](../phase-9/phase_9_79b_1_unified_registry_ids_and_builder_slotting.md)
|
||||||
|
- [GC Switchable Language Idea](../../../ideas/other/2025-08-26-gc-switchable-language.md)
|
||||||
|
|
||||||
|
## ✅ 受け入れ基準
|
||||||
|
- [ ] インタープリター/VM/JITすべてで両モード動作
|
||||||
|
- [ ] モード切り替えが実行時に可能(再コンパイル不要)
|
||||||
|
- [ ] 既存コードが無修正で動作(後方互換性)
|
||||||
|
- [ ] パフォーマンス劣化が許容範囲(GCオン時10%以内)
|
||||||
|
- [ ] メモリリーク検出ツールの提供
|
||||||
|
|
||||||
|
## 🚀 将来の拡張
|
||||||
|
- Mark & Sweep GCモードの追加
|
||||||
|
- 世代別GC
|
||||||
|
- リージョンベースメモリ管理
|
||||||
|
- プロファイルベース自動モード選択
|
||||||
@ -60,3 +60,93 @@ AST → MIR → Optimizer → VM Dispatcher
|
|||||||
---
|
---
|
||||||
備考: LLVM AOTはPhase 11以降の研究路線に移行(設計ドキュメントは維持)。
|
備考: LLVM AOTはPhase 11以降の研究路線に移行(設計ドキュメントは維持)。
|
||||||
|
|
||||||
|
## 🔬 Sub-Phases (10_a .. 10_h)
|
||||||
|
|
||||||
|
各サブフェーズは「小さく立ち上げ→検証→次へ」。既存のVM/Thunk/PICを活用し、JITは同じ経路に自然合流させる。
|
||||||
|
|
||||||
|
### 10_a: JITブートストラップ(基盤+プロファイラ)
|
||||||
|
- 目的: Cranelift JITの骨組みとホット関数検出の導線を用意。
|
||||||
|
- 具体:
|
||||||
|
- `JitManager`(関数プロファイル、しきい値、キャッシュ)
|
||||||
|
- CLIF環境初期化(`Module`, `Context`, `ISA`)
|
||||||
|
- VM統合: 関数入口でホット度チェック→JITコンパイル/呼び出し
|
||||||
|
- 診断: `NYASH_JIT_STATS=1`(JIT件数/時間/キャッシュヒット)
|
||||||
|
- 受入: ダミー関数をJIT登録してVMから呼出可能、ログが出る
|
||||||
|
|
||||||
|
### 10_b: Lower(Core-1) – Const/Move/BinOp/Cmp/Branch/Ret
|
||||||
|
- 目的: ループや条件分岐を含む純粋関数をJIT実行可能に。
|
||||||
|
- 具体:
|
||||||
|
- MIR値/型→CLIF型(i64/f64/i1/void)
|
||||||
|
- Const/Copy/算術/比較/ジャンプ/分岐/return のLower
|
||||||
|
- フレームレイアウト(VMValue最小表現)
|
||||||
|
- 受入: 算術/比較/分岐のみの関数がJITでVMより速い(小ベンチ)
|
||||||
|
|
||||||
|
### 10_c: ABI/呼出し – 関数呼び出しとフォールバック
|
||||||
|
- 目的: JIT化関数から他関数を呼ぶ/VMへ戻る道を用意。
|
||||||
|
- 具体:
|
||||||
|
- 同一モジュール関数呼び出し(JIT→JIT/JIT→VM)
|
||||||
|
- 引数/戻り値の受け渡し(整数/浮動/void)
|
||||||
|
- 例外/TypeErrorはVMへバイアウト(trap→VM)
|
||||||
|
- 受入: 再帰/多段呼び出しが安定動作
|
||||||
|
|
||||||
|
### 10_d: コレクション基礎 – Array/Map ホット操作(外部呼び出し)
|
||||||
|
- 目的: 実用的ホットパス(length/get/set/push/pop)をJIT側から呼べるように。
|
||||||
|
- 具体:
|
||||||
|
- ホスト関数テーブル(外部シンボル)で既存Rust実装を呼ぶ
|
||||||
|
- 境界チェック/エラーはRust側に委譲、JITは薄い橋渡し
|
||||||
|
- 受入: Array操作がVM経由より高速(目安1.5–2.0×)
|
||||||
|
|
||||||
|
### 10_e: BoxCall高速化 – Thunk/PICの直結
|
||||||
|
- 目的: Phase 9.79bの `TypeMeta{slot->thunk}` と Poly-PIC をJITにインライン。
|
||||||
|
- 具体:
|
||||||
|
- `slot -> thunk -> target` 解決をJITで再現(ユニバーサル0..3含む)
|
||||||
|
- `(type_id, version)` チェック(Poly-PIC 2–4件)→ヒット直呼び、ミスVM
|
||||||
|
- バージョンミスマッチで安全にフォールバック
|
||||||
|
- 受入: BoxCallホットサイトで2×以上の高速化+正しい無効化挙動
|
||||||
|
|
||||||
|
### 10_f: TypeOp/Ref/Weak/Barrier(最小)
|
||||||
|
- 目的: 実アプリで必要な最小限のタイプ/参照操作を埋める。
|
||||||
|
- 具体:
|
||||||
|
- `as_bool()` 等の基本型変換
|
||||||
|
- 参照/弱参照/バリアの最小パス(重い経路はVMへ)
|
||||||
|
- 受入: 代表的コードパスでJIT有効のままE2E成功
|
||||||
|
|
||||||
|
### 10_g: 診断/ベンチ/回帰
|
||||||
|
- 目的: 可視化と安定化。
|
||||||
|
- 具体:
|
||||||
|
- `--vm-stats` にJIT統計統合(compile/ms, sites, cache率)
|
||||||
|
- ベンチ更新(JIT有/無比較)とスナップショット
|
||||||
|
- 受入: CIで回帰検知可能/ドキュメント更新
|
||||||
|
|
||||||
|
### 10_h: 硬化・パフォーマンス調整
|
||||||
|
- 目的: ホットスポットの最適化とノイズ除去。
|
||||||
|
- 具体:
|
||||||
|
- ガード配置最適化(分岐予測/ICヒット優先)
|
||||||
|
- 不要コピー削減、ホスト呼出回数の削減
|
||||||
|
- 受入: 代表ベンチで安定して目標達成(2×以上)
|
||||||
|
|
||||||
|
## 📦 成果物(各サブフェーズ)
|
||||||
|
- 10_a: `jit/manager.rs` スケルトン、VM連携、統計ログ
|
||||||
|
- 10_b: `jit/lower/core.rs`(Const/BinOp/Cmp/Branch/Ret)+単体テスト
|
||||||
|
- 10_c: `jit/abi.rs`(call/ret/fallback)+再帰テスト
|
||||||
|
- 10_d: `jit/extern/collections.rs`(Array/Mapブリッジ)+マイクロベンチ
|
||||||
|
- 10_e: `jit/inline_cache.rs`(PIC/VT連携)+無効化テスト
|
||||||
|
- 10_f: `jit/lower/typeop_ref.rs`(最小)
|
||||||
|
- 10_g: ベンチ/統計/README更新
|
||||||
|
- 10_h: 最適化コミットと測定レポート
|
||||||
|
|
||||||
|
## 🧩 既存資産との連携(重要)
|
||||||
|
- Thunk: Phase 9.79b.3の `TypeMeta{thunks}` をJIT直呼びターゲットとして使用
|
||||||
|
- Poly-PIC: VMの構造をJITに投影(同じキー `(label, version)` を使用)
|
||||||
|
- Versioning: `cache_versions` のイベントに同期してIC無効化
|
||||||
|
|
||||||
|
## 🎯 マイルストーン再定義
|
||||||
|
- M1: 10_a + 10_b 合格(Core関数のJIT実行)
|
||||||
|
- M2: 10_c + 10_d 合格(関数呼出/Arrayホット操作)
|
||||||
|
- M3: 10_e 合格(BoxCallホットパス2×)
|
||||||
|
- M4: 10_g + 10_h 合格(ベンチ/統計/硬化)
|
||||||
|
|
||||||
|
## ⚠️ リスクと緩和
|
||||||
|
- ABI複雑化: まず整数/浮動/voidに限定し、BoxRefはホスト関数へブリッジ
|
||||||
|
- 最適化過剰: 常にVMフォールバックを保持、ガード失敗で安全に戻す
|
||||||
|
- デバッグ困難: CLIFダンプ/CFG表示/`NYASH_JIT_STATS`で観測
|
||||||
|
|||||||
56
docs/development/roadmap/phases/phase-11/README.md
Normal file
56
docs/development/roadmap/phases/phase-11/README.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Phase 11: LLVM AOT Backend(将来研究)
|
||||||
|
|
||||||
|
## 🎯 概要
|
||||||
|
|
||||||
|
Phase 11は、LLVM を使用した Ahead-of-Time(AOT)コンパイル機能の研究・実装フェーズです。
|
||||||
|
Phase 10のCranelift JITで実用的な性能を達成した後、さらなる最適化を追求します。
|
||||||
|
|
||||||
|
## 📊 位置づけ
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 10: Cranelift JIT(実用的な高速化)← 現在の主経路
|
||||||
|
↓
|
||||||
|
Phase 11: LLVM AOT(最高性能への挑戦)← 将来の研究開発
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📁 ドキュメント
|
||||||
|
|
||||||
|
### 🔬 研究・設計ドキュメント
|
||||||
|
- [phase10_aot_scaffolding.md](phase10_aot_scaffolding.md) - LLVM Direct AOT実装計画
|
||||||
|
- MIR→LLVM IR直接変換
|
||||||
|
- Everything is Box最適化(エスケープ解析)
|
||||||
|
- LTO/PGO統合
|
||||||
|
- 目標: 13,500倍高速化(対インタープリタ)
|
||||||
|
|
||||||
|
- [phase_10_x_llvm_backend_skeleton.md](phase_10_x_llvm_backend_skeleton.md) - LLVM Backend最小実装
|
||||||
|
- 具体的な実装ステップ
|
||||||
|
- ExternCall対応
|
||||||
|
- オブジェクトファイル生成
|
||||||
|
|
||||||
|
## ⏰ タイムライン
|
||||||
|
|
||||||
|
- **Status**: Deferred(延期)
|
||||||
|
- **前提条件**: Phase 10(Cranelift JIT)の完了
|
||||||
|
- **想定期間**: 4-6ヶ月
|
||||||
|
- **開始時期**: 未定(Phase 10の成果を見て判断)
|
||||||
|
|
||||||
|
## 🎯 期待される成果
|
||||||
|
|
||||||
|
1. **最高性能**: インタープリタ比13,500倍の実行速度
|
||||||
|
2. **メモリ効率**: Box割当80%削減
|
||||||
|
3. **起動時間**: 1ms以下
|
||||||
|
4. **配布形式**: スタンドアロン実行ファイル
|
||||||
|
|
||||||
|
## ⚠️ 注意事項
|
||||||
|
|
||||||
|
このフェーズは研究的な性質が強く、以下の理由で延期されています:
|
||||||
|
|
||||||
|
1. **複雑性**: LLVM統合は開発・保守コストが高い
|
||||||
|
2. **実用性**: Cranelift JITで十分な性能が得られる可能性
|
||||||
|
3. **優先度**: まずは安定した実装を優先
|
||||||
|
|
||||||
|
## 🔗 関連フェーズ
|
||||||
|
|
||||||
|
- [Phase 10](../phase-10/) - Cranelift JIT(前提)
|
||||||
|
- [Phase 9](../phase-9/) - 統一Box設計(基盤)
|
||||||
|
- [00_MASTER_ROADMAP.md](../00_MASTER_ROADMAP.md) - 全体計画
|
||||||
158
docs/ideas/other/2025-08-26-gc-switchable-language.md
Normal file
158
docs/ideas/other/2025-08-26-gc-switchable-language.md
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
# GC切り替え可能言語 - 史上初の柔軟なメモリ管理
|
||||||
|
|
||||||
|
Status: Research
|
||||||
|
Created: 2025-08-26
|
||||||
|
Priority: Medium
|
||||||
|
Related: メモリ管理、参照カウント、開発効率
|
||||||
|
|
||||||
|
## 🌟 コンセプト
|
||||||
|
|
||||||
|
Nyashを**世界初のGC切り替え可能言語**にする革新的アイデア。開発時はGCオンで快適に、本番ではGCオフで高性能に。
|
||||||
|
|
||||||
|
## 🎯 基本アイデア
|
||||||
|
|
||||||
|
```nyash
|
||||||
|
// 開発時: メモリリークを気にせず開発
|
||||||
|
nyash --gc-mode=ref-counting app.nyash
|
||||||
|
|
||||||
|
// 本番: 最高性能で実行
|
||||||
|
nyash --gc-mode=explicit app.nyash
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 メモリ管理モード
|
||||||
|
|
||||||
|
### 1. Explicit Mode(デフォルト)
|
||||||
|
- 現在のNyashの動作
|
||||||
|
- スコープ抜けたら即fini()
|
||||||
|
- 予測可能な性能
|
||||||
|
- リアルタイムアプリ向け
|
||||||
|
|
||||||
|
### 2. Reference Counting Mode(新規)
|
||||||
|
- 参照カウントが0になったらfini()
|
||||||
|
- share_box()で参照カウント++
|
||||||
|
- 循環参照はweak参照で解決
|
||||||
|
- 一般アプリ向け
|
||||||
|
|
||||||
|
## 🔧 実装戦略
|
||||||
|
|
||||||
|
### インタープリター(簡単)
|
||||||
|
```rust
|
||||||
|
// 実行時のモード切り替えで対応
|
||||||
|
match memory_mode {
|
||||||
|
RefCounting => {
|
||||||
|
// 代入時に参照カウント操作
|
||||||
|
old_value.dec_ref();
|
||||||
|
new_value.inc_ref();
|
||||||
|
}
|
||||||
|
Explicit => {
|
||||||
|
// 現在の動作
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### MIR(中程度)
|
||||||
|
```rust
|
||||||
|
// 新しい抽象命令を追加
|
||||||
|
enum MirInstruction {
|
||||||
|
Acquire(temp_id), // 値の取得(GCならref++)
|
||||||
|
Release(temp_id), // 値の解放(GCならref--/fini)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MIRは同じ、実行時に挙動を変える
|
||||||
|
```
|
||||||
|
|
||||||
|
### VM(やや複雑)
|
||||||
|
- Acquire/Release命令の実装
|
||||||
|
- モードフラグによる分岐
|
||||||
|
- 参照カウント0検出時のfini呼び出し
|
||||||
|
|
||||||
|
### Cranelift JIT(複雑)
|
||||||
|
```rust
|
||||||
|
// モード別のコード生成
|
||||||
|
if gc_mode == Explicit {
|
||||||
|
// Acquire/Release命令をスキップ
|
||||||
|
// 直接fini呼び出し
|
||||||
|
} else {
|
||||||
|
// 参照カウント操作のインライン展開
|
||||||
|
// 条件付きfini呼び出し
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 実装フェーズ
|
||||||
|
|
||||||
|
### Phase 1: 基礎実装(1-2週間)
|
||||||
|
- [ ] BoxBaseに参照カウント追加
|
||||||
|
- [ ] インタープリターでのモード切り替え
|
||||||
|
- [ ] 基本的なテスト
|
||||||
|
|
||||||
|
### Phase 2: MIR/VM対応(1ヶ月)
|
||||||
|
- [ ] Acquire/Release命令の設計
|
||||||
|
- [ ] MIRビルダーの対応
|
||||||
|
- [ ] VM実行時の最適化
|
||||||
|
|
||||||
|
### Phase 3: JIT最適化(Cranelift後)
|
||||||
|
- [ ] モード別コード生成
|
||||||
|
- [ ] デッドコード除去
|
||||||
|
- [ ] インライン最適化
|
||||||
|
|
||||||
|
## 💡 革新的な使い方
|
||||||
|
|
||||||
|
### 開発フロー
|
||||||
|
```bash
|
||||||
|
# 1. 開発中: GCオンで快適開発
|
||||||
|
nyash --gc-mode=ref-counting --detect-leaks dev.nyash
|
||||||
|
|
||||||
|
# 2. テスト: メモリリーク検出
|
||||||
|
nyash --gc-mode=ref-counting --memory-report test.nyash
|
||||||
|
|
||||||
|
# 3. 本番: GCオフで最高性能
|
||||||
|
nyash --gc-mode=explicit --optimize prod.nyash
|
||||||
|
```
|
||||||
|
|
||||||
|
### ハイブリッドモード
|
||||||
|
```nyash
|
||||||
|
// ファイル単位での制御
|
||||||
|
// @gc-mode: explicit
|
||||||
|
box RealtimeAudioProcessor { }
|
||||||
|
|
||||||
|
// @gc-mode: ref-counting
|
||||||
|
box UIController { }
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🤔 技術的課題
|
||||||
|
|
||||||
|
### 1. 統一的なMIR表現
|
||||||
|
- GCモードに依存しないMIR設計
|
||||||
|
- 実行時の最適化余地を残す
|
||||||
|
|
||||||
|
### 2. JITコード生成の複雑化
|
||||||
|
- モード別の最適化パス
|
||||||
|
- コードキャッシュの管理
|
||||||
|
|
||||||
|
### 3. デバッグ情報の保持
|
||||||
|
- どのモードで実行されているか
|
||||||
|
- 参照カウントの可視化
|
||||||
|
|
||||||
|
## 🎉 期待される効果
|
||||||
|
|
||||||
|
1. **開発効率**: GCありで快適開発
|
||||||
|
2. **実行性能**: GCなしで最高速
|
||||||
|
3. **教育価値**: メモリ管理の学習に最適
|
||||||
|
4. **柔軟性**: アプリケーションに応じた選択
|
||||||
|
|
||||||
|
## 📚 参考資料
|
||||||
|
- Rust: 所有権システム(コンパイル時)
|
||||||
|
- Swift: ARC(自動参照カウント)
|
||||||
|
- Go/Java: 強制GC
|
||||||
|
- C/C++: 手動管理
|
||||||
|
|
||||||
|
## 🔮 将来の拡張
|
||||||
|
- Mark & Sweep GCモードの追加
|
||||||
|
- 世代別GC
|
||||||
|
- リージョンベースメモリ管理
|
||||||
|
- プロファイルベース自動選択
|
||||||
|
|
||||||
|
## 実装タイミング
|
||||||
|
- Cranelift JIT実装後が理想的
|
||||||
|
- まずはインタープリターで実験
|
||||||
|
- 実用性を確認してから本格実装
|
||||||
@ -212,6 +212,8 @@ pub struct VM {
|
|||||||
pub(super) boxcall_pic_hits: std::collections::HashMap<String, u32>,
|
pub(super) boxcall_pic_hits: std::collections::HashMap<String, u32>,
|
||||||
/// Mono-PIC: cached direct targets (currently InstanceBox function name)
|
/// Mono-PIC: cached direct targets (currently InstanceBox function name)
|
||||||
pub(super) boxcall_pic_funcname: std::collections::HashMap<String, String>,
|
pub(super) boxcall_pic_funcname: std::collections::HashMap<String, String>,
|
||||||
|
/// Poly-PIC: per call-site up to 4 entries of (label, version, func_name)
|
||||||
|
pub(super) boxcall_poly_pic: std::collections::HashMap<String, Vec<(String, u32, String)>>,
|
||||||
/// VTable-like cache: (type, method_id, arity) → direct target (InstanceBox method)
|
/// VTable-like cache: (type, method_id, arity) → direct target (InstanceBox method)
|
||||||
pub(super) boxcall_vtable_funcname: std::collections::HashMap<String, String>,
|
pub(super) boxcall_vtable_funcname: std::collections::HashMap<String, String>,
|
||||||
/// Version map for cache invalidation: label -> version
|
/// Version map for cache invalidation: label -> version
|
||||||
@ -278,6 +280,7 @@ impl VM {
|
|||||||
exec_start: None,
|
exec_start: None,
|
||||||
boxcall_pic_hits: std::collections::HashMap::new(),
|
boxcall_pic_hits: std::collections::HashMap::new(),
|
||||||
boxcall_pic_funcname: std::collections::HashMap::new(),
|
boxcall_pic_funcname: std::collections::HashMap::new(),
|
||||||
|
boxcall_poly_pic: std::collections::HashMap::new(),
|
||||||
boxcall_vtable_funcname: std::collections::HashMap::new(),
|
boxcall_vtable_funcname: std::collections::HashMap::new(),
|
||||||
type_versions: std::collections::HashMap::new(),
|
type_versions: std::collections::HashMap::new(),
|
||||||
// TODO: Re-enable when interpreter refactoring is complete
|
// TODO: Re-enable when interpreter refactoring is complete
|
||||||
@ -307,6 +310,7 @@ impl VM {
|
|||||||
exec_start: None,
|
exec_start: None,
|
||||||
boxcall_pic_hits: std::collections::HashMap::new(),
|
boxcall_pic_hits: std::collections::HashMap::new(),
|
||||||
boxcall_pic_funcname: std::collections::HashMap::new(),
|
boxcall_pic_funcname: std::collections::HashMap::new(),
|
||||||
|
boxcall_poly_pic: std::collections::HashMap::new(),
|
||||||
boxcall_vtable_funcname: std::collections::HashMap::new(),
|
boxcall_vtable_funcname: std::collections::HashMap::new(),
|
||||||
type_versions: std::collections::HashMap::new(),
|
type_versions: std::collections::HashMap::new(),
|
||||||
}
|
}
|
||||||
@ -339,6 +343,10 @@ impl VM {
|
|||||||
pub fn execute_module(&mut self, module: &MirModule) -> Result<Box<dyn NyashBox>, VMError> {
|
pub fn execute_module(&mut self, module: &MirModule) -> Result<Box<dyn NyashBox>, VMError> {
|
||||||
// Store module for nested calls
|
// Store module for nested calls
|
||||||
self.module = Some(module.clone());
|
self.module = Some(module.clone());
|
||||||
|
// Optional: dump registry for debugging
|
||||||
|
if std::env::var("NYASH_REG_DUMP").ok().as_deref() == Some("1") {
|
||||||
|
crate::runtime::type_meta::dump_registry();
|
||||||
|
}
|
||||||
// Reset stats
|
// Reset stats
|
||||||
self.instr_counter.clear();
|
self.instr_counter.clear();
|
||||||
self.exec_start = Some(Instant::now());
|
self.exec_start = Some(Instant::now());
|
||||||
@ -352,10 +360,34 @@ impl VM {
|
|||||||
// Optional: print VM stats
|
// Optional: print VM stats
|
||||||
self.maybe_print_stats();
|
self.maybe_print_stats();
|
||||||
|
|
||||||
|
// Optional: print cache stats summary
|
||||||
|
if std::env::var("NYASH_VM_PIC_STATS").ok().as_deref() == Some("1") {
|
||||||
|
self.print_cache_stats_summary();
|
||||||
|
}
|
||||||
|
|
||||||
// Convert result to NyashBox
|
// Convert result to NyashBox
|
||||||
Ok(result.to_nyash_box())
|
Ok(result.to_nyash_box())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn print_cache_stats_summary(&self) {
|
||||||
|
let sites_poly = self.boxcall_poly_pic.len();
|
||||||
|
let entries_poly: usize = self.boxcall_poly_pic.values().map(|v| v.len()).sum();
|
||||||
|
let avg_entries = if sites_poly > 0 { (entries_poly as f64) / (sites_poly as f64) } else { 0.0 };
|
||||||
|
let sites_mono = self.boxcall_pic_funcname.len();
|
||||||
|
let hits_total: u64 = self.boxcall_pic_hits.values().map(|v| *v as u64).sum();
|
||||||
|
let vt_entries = self.boxcall_vtable_funcname.len();
|
||||||
|
eprintln!(
|
||||||
|
"[VM] PIC/VT summary: poly_sites={} avg_entries={:.2} mono_sites={} hits_total={} vt_entries={}",
|
||||||
|
sites_poly, avg_entries, sites_mono, hits_total, vt_entries
|
||||||
|
);
|
||||||
|
// Top sites by hits (up to 5)
|
||||||
|
let mut hits: Vec<(&String, &u32)> = self.boxcall_pic_hits.iter().collect();
|
||||||
|
hits.sort_by(|a, b| b.1.cmp(a.1));
|
||||||
|
for (i, (k, v)) in hits.into_iter().take(5).enumerate() {
|
||||||
|
eprintln!(" #{} {} hits={}", i+1, k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Call a MIR function by name with VMValue arguments
|
/// Call a MIR function by name with VMValue arguments
|
||||||
pub(super) fn call_function_by_name(&mut self, func_name: &str, args: Vec<VMValue>) -> Result<VMValue, VMError> {
|
pub(super) fn call_function_by_name(&mut self, func_name: &str, args: Vec<VMValue>) -> Result<VMValue, VMError> {
|
||||||
let module_ref = self.module.as_ref().ok_or_else(|| VMError::InvalidInstruction("No active module".to_string()))?;
|
let module_ref = self.module.as_ref().ok_or_else(|| VMError::InvalidInstruction("No active module".to_string()))?;
|
||||||
|
|||||||
@ -58,6 +58,48 @@ impl VM {
|
|||||||
format!("VT@v{}:{}#{}{}", ver, class_name, method_id, format!("/{}", arity))
|
format!("VT@v{}:{}#{}{}", ver, class_name, method_id, format!("/{}", arity))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Poly-PIC probe: try up to 4 cached entries for this call-site
|
||||||
|
fn try_poly_pic(&mut self, pic_site_key: &str, recv: &VMValue) -> Option<String> {
|
||||||
|
let label = self.cache_label_for_recv(recv);
|
||||||
|
let ver = self.cache_version_for_label(&label);
|
||||||
|
if let Some(entries) = self.boxcall_poly_pic.get_mut(pic_site_key) {
|
||||||
|
// find match and move to end for naive LRU behavior
|
||||||
|
if let Some(idx) = entries.iter().position(|(l, v, _)| *l == label && *v == ver) {
|
||||||
|
let entry = entries.remove(idx);
|
||||||
|
entries.push(entry.clone());
|
||||||
|
return Some(entry.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Poly-PIC record: insert or refresh an entry for this call-site
|
||||||
|
fn record_poly_pic(&mut self, pic_site_key: &str, recv: &VMValue, func_name: &str) {
|
||||||
|
let label = self.cache_label_for_recv(recv);
|
||||||
|
let ver = self.cache_version_for_label(&label);
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
|
match self.boxcall_poly_pic.entry(pic_site_key.to_string()) {
|
||||||
|
Entry::Occupied(mut e) => {
|
||||||
|
let v = e.get_mut();
|
||||||
|
if let Some(idx) = v.iter().position(|(l, vv, _)| *l == label && *vv == ver) {
|
||||||
|
v.remove(idx);
|
||||||
|
}
|
||||||
|
if v.len() >= 4 { v.remove(0); }
|
||||||
|
v.push((label.clone(), ver, func_name.to_string()));
|
||||||
|
}
|
||||||
|
Entry::Vacant(v) => {
|
||||||
|
v.insert(vec![(label.clone(), ver, func_name.to_string())]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if std::env::var("NYASH_VM_PIC_STATS").ok().as_deref() == Some("1") {
|
||||||
|
// minimal per-site size log
|
||||||
|
if let Some(v) = self.boxcall_poly_pic.get(pic_site_key) {
|
||||||
|
eprintln!("[PIC] site={} size={} last=({}, v{}) -> {}",
|
||||||
|
pic_site_key, v.len(), label, ver, func_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Compute cache label for a receiver
|
/// Compute cache label for a receiver
|
||||||
fn cache_label_for_recv(&self, recv: &VMValue) -> String {
|
fn cache_label_for_recv(&self, recv: &VMValue) -> String {
|
||||||
match recv {
|
match recv {
|
||||||
@ -579,11 +621,136 @@ impl VM {
|
|||||||
let pic_key = self.build_pic_key(&recv, method, method_id);
|
let pic_key = self.build_pic_key(&recv, method, method_id);
|
||||||
self.pic_record_hit(&pic_key);
|
self.pic_record_hit(&pic_key);
|
||||||
|
|
||||||
// VTable-like direct call using method_id for InstanceBox (no threshold)
|
// VTable-like direct call using method_id via TypeMeta thunk table (InstanceBox/PluginBoxV2/Builtin)
|
||||||
if let (Some(mid), VMValue::BoxRef(arc_box)) = (method_id, &recv) {
|
if let (Some(mid), VMValue::BoxRef(arc_box)) = (method_id, &recv) {
|
||||||
|
// Determine class label for TypeMeta
|
||||||
|
let mut class_label: Option<String> = None;
|
||||||
|
let mut is_instance = false;
|
||||||
|
let mut is_plugin = false;
|
||||||
|
let mut is_builtin = false;
|
||||||
if let Some(inst) = arc_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
if let Some(inst) = arc_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||||
let vkey = self.build_vtable_key(&inst.class_name, mid, args.len());
|
class_label = Some(inst.class_name.clone());
|
||||||
if let Some(func_name) = self.boxcall_vtable_funcname.get(&vkey).cloned() {
|
is_instance = true;
|
||||||
|
} else if let Some(p) = arc_box.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||||
|
class_label = Some(p.box_type.clone());
|
||||||
|
is_plugin = true;
|
||||||
|
} else {
|
||||||
|
class_label = Some(arc_box.type_name().to_string());
|
||||||
|
is_builtin = true;
|
||||||
|
}
|
||||||
|
if let Some(label) = class_label {
|
||||||
|
let tm = crate::runtime::type_meta::get_or_create_type_meta(&label);
|
||||||
|
if let Some(th) = tm.get_thunk(mid as usize) {
|
||||||
|
if let Some(target) = th.get_target() {
|
||||||
|
match target {
|
||||||
|
crate::runtime::type_meta::ThunkTarget::MirFunction(func_name) => {
|
||||||
|
if std::env::var("NYASH_VM_VT_STATS").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("[VT] hit class={} slot={} -> {}", label, mid, func_name);
|
||||||
|
}
|
||||||
|
let mut vm_args = Vec::with_capacity(1 + args.len());
|
||||||
|
vm_args.push(recv.clone());
|
||||||
|
for a in args { vm_args.push(self.get_value(*a)?); }
|
||||||
|
let res = self.call_function_by_name(&func_name, vm_args)?;
|
||||||
|
if let Some(dst_id) = dst { self.set_value(dst_id, res); }
|
||||||
|
return Ok(ControlFlow::Continue);
|
||||||
|
}
|
||||||
|
crate::runtime::type_meta::ThunkTarget::PluginInvoke { method_id: mid2 } => {
|
||||||
|
if is_plugin {
|
||||||
|
if let Some(p) = arc_box.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||||
|
// Convert args prepared earlier (we need NyashBox args)
|
||||||
|
let nyash_args: Vec<Box<dyn NyashBox>> = args.iter()
|
||||||
|
.map(|arg| {
|
||||||
|
let val = self.get_value(*arg)?;
|
||||||
|
Ok(val.to_nyash_box())
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, VMError>>()?;
|
||||||
|
// Encode minimal TLV (int/string/handle) same as fast-path
|
||||||
|
let mut tlv = crate::runtime::plugin_ffi_common::encode_tlv_header(nyash_args.len() as u16);
|
||||||
|
let mut enc_failed = false;
|
||||||
|
for a in &nyash_args {
|
||||||
|
if let Some(s) = a.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||||
|
crate::runtime::plugin_ffi_common::encode::string(&mut tlv, &s.value);
|
||||||
|
} else if let Some(i) = a.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
|
||||||
|
crate::runtime::plugin_ffi_common::encode::i32(&mut tlv, i.value as i32);
|
||||||
|
} else if let Some(h) = a.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||||
|
crate::runtime::plugin_ffi_common::encode::plugin_handle(&mut tlv, h.inner.type_id, h.inner.instance_id);
|
||||||
|
} else {
|
||||||
|
enc_failed = true; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !enc_failed {
|
||||||
|
let mut out = vec![0u8; 4096];
|
||||||
|
let mut out_len: usize = out.len();
|
||||||
|
let code = unsafe {
|
||||||
|
(p.inner.invoke_fn)(
|
||||||
|
p.inner.type_id,
|
||||||
|
mid2 as u32,
|
||||||
|
p.inner.instance_id,
|
||||||
|
tlv.as_ptr(),
|
||||||
|
tlv.len(),
|
||||||
|
out.as_mut_ptr(),
|
||||||
|
&mut out_len,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if code == 0 {
|
||||||
|
let vm_out = if let Some((_tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) {
|
||||||
|
let s = crate::runtime::plugin_ffi_common::decode::string(payload);
|
||||||
|
if !s.is_empty() {
|
||||||
|
VMValue::String(s)
|
||||||
|
} else if let Some(v) = crate::runtime::plugin_ffi_common::decode::i32(payload) {
|
||||||
|
VMValue::Integer(v as i64)
|
||||||
|
} else {
|
||||||
|
VMValue::Void
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
VMValue::Void
|
||||||
|
};
|
||||||
|
if let Some(dst_id) = dst { self.set_value(dst_id, vm_out); }
|
||||||
|
return Ok(ControlFlow::Continue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crate::runtime::type_meta::ThunkTarget::BuiltinCall { method: ref m } => {
|
||||||
|
if is_builtin {
|
||||||
|
// Prepare NyashBox args and call by name
|
||||||
|
let nyash_args: Vec<Box<dyn NyashBox>> = args.iter()
|
||||||
|
.map(|arg| {
|
||||||
|
let val = self.get_value(*arg)?;
|
||||||
|
Ok(val.to_nyash_box())
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, VMError>>()?;
|
||||||
|
let cloned_box = arc_box.share_box();
|
||||||
|
let out = self.call_box_method(cloned_box, m, nyash_args)?;
|
||||||
|
let vm_out = VMValue::from_nyash_box(out);
|
||||||
|
if let Some(dst_id) = dst { self.set_value(dst_id, vm_out); }
|
||||||
|
return Ok(ControlFlow::Continue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Backward-compat: consult legacy vtable cache for InstanceBox if TypeMeta empty
|
||||||
|
if is_instance {
|
||||||
|
let inst = arc_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>().unwrap();
|
||||||
|
let vkey = self.build_vtable_key(&inst.class_name, mid, args.len());
|
||||||
|
if let Some(func_name) = self.boxcall_vtable_funcname.get(&vkey).cloned() {
|
||||||
|
let mut vm_args = Vec::with_capacity(1 + args.len());
|
||||||
|
vm_args.push(recv.clone());
|
||||||
|
for a in args { vm_args.push(self.get_value(*a)?); }
|
||||||
|
let res = self.call_function_by_name(&func_name, vm_args)?;
|
||||||
|
if let Some(dst_id) = dst { self.set_value(dst_id, res); }
|
||||||
|
return Ok(ControlFlow::Continue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poly-PIC direct call: if we cached a target for this site and receiver is InstanceBox, use it
|
||||||
|
if let VMValue::BoxRef(arc_box) = &recv {
|
||||||
|
if arc_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>().is_some() {
|
||||||
|
if let Some(func_name) = self.try_poly_pic(&pic_key, &recv) {
|
||||||
let mut vm_args = Vec::with_capacity(1 + args.len());
|
let mut vm_args = Vec::with_capacity(1 + args.len());
|
||||||
vm_args.push(recv.clone());
|
vm_args.push(recv.clone());
|
||||||
for a in args { vm_args.push(self.get_value(*a)?); }
|
for a in args { vm_args.push(self.get_value(*a)?); }
|
||||||
@ -591,12 +758,7 @@ impl VM {
|
|||||||
if let Some(dst_id) = dst { self.set_value(dst_id, res); }
|
if let Some(dst_id) = dst { self.set_value(dst_id, res); }
|
||||||
return Ok(ControlFlow::Continue);
|
return Ok(ControlFlow::Continue);
|
||||||
}
|
}
|
||||||
}
|
// Fallback to Mono-PIC (legacy) if present
|
||||||
}
|
|
||||||
|
|
||||||
// Mono-PIC direct call: if we cached a target for this site and receiver is InstanceBox, use it
|
|
||||||
if let VMValue::BoxRef(arc_box) = &recv {
|
|
||||||
if arc_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>().is_some() {
|
|
||||||
if let Some(func_name) = self.boxcall_pic_funcname.get(&pic_key).cloned() {
|
if let Some(func_name) = self.boxcall_pic_funcname.get(&pic_key).cloned() {
|
||||||
// Build VM args: receiver first, then original args
|
// Build VM args: receiver first, then original args
|
||||||
let mut vm_args = Vec::with_capacity(1 + args.len());
|
let mut vm_args = Vec::with_capacity(1 + args.len());
|
||||||
@ -632,10 +794,39 @@ impl VM {
|
|||||||
let mut tlv = crate::runtime::plugin_ffi_common::encode_tlv_header(nyash_args.len() as u16);
|
let mut tlv = crate::runtime::plugin_ffi_common::encode_tlv_header(nyash_args.len() as u16);
|
||||||
let mut enc_failed = false;
|
let mut enc_failed = false;
|
||||||
for a in &nyash_args {
|
for a in &nyash_args {
|
||||||
|
// Prefer BufferBox → bytes
|
||||||
|
if let Some(buf) = a.as_any().downcast_ref::<crate::boxes::buffer::BufferBox>() {
|
||||||
|
let snapshot = buf.to_vec();
|
||||||
|
crate::runtime::plugin_ffi_common::encode::bytes(&mut tlv, &snapshot);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if let Some(s) = a.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
if let Some(s) = a.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||||
crate::runtime::plugin_ffi_common::encode::string(&mut tlv, &s.value);
|
crate::runtime::plugin_ffi_common::encode::string(&mut tlv, &s.value);
|
||||||
} else if let Some(i) = a.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
|
} else if let Some(i) = a.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
|
||||||
crate::runtime::plugin_ffi_common::encode::i32(&mut tlv, i.value as i32);
|
// Prefer 32-bit if fits, else i64
|
||||||
|
if i.value >= i32::MIN as i64 && i.value <= i32::MAX as i64 {
|
||||||
|
crate::runtime::plugin_ffi_common::encode::i32(&mut tlv, i.value as i32);
|
||||||
|
} else {
|
||||||
|
crate::runtime::plugin_ffi_common::encode::i64(&mut tlv, i.value as i64);
|
||||||
|
}
|
||||||
|
} else if let Some(b) = a.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
|
||||||
|
crate::runtime::plugin_ffi_common::encode::bool(&mut tlv, b.value);
|
||||||
|
} else if let Some(f) = a.as_any().downcast_ref::<crate::boxes::math_box::FloatBox>() {
|
||||||
|
crate::runtime::plugin_ffi_common::encode::f64(&mut tlv, f.value);
|
||||||
|
} else if let Some(arr) = a.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
|
// Try encode as bytes if all elements are 0..255 integers
|
||||||
|
let items = arr.items.read().unwrap();
|
||||||
|
let mut tmp = Vec::with_capacity(items.len());
|
||||||
|
let mut ok = true;
|
||||||
|
for item in items.iter() {
|
||||||
|
if let Some(intb) = item.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
|
||||||
|
if intb.value >= 0 && intb.value <= 255 { tmp.push(intb.value as u8); } else { ok = false; break; }
|
||||||
|
} else {
|
||||||
|
ok = false; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ok { crate::runtime::plugin_ffi_common::encode::bytes(&mut tlv, &tmp); }
|
||||||
|
else { enc_failed = true; break; }
|
||||||
} else if let Some(h) = a.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
} else if let Some(h) = a.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
||||||
crate::runtime::plugin_ffi_common::encode::plugin_handle(&mut tlv, h.inner.type_id, h.inner.instance_id);
|
crate::runtime::plugin_ffi_common::encode::plugin_handle(&mut tlv, h.inner.type_id, h.inner.instance_id);
|
||||||
} else {
|
} else {
|
||||||
@ -657,16 +848,17 @@ impl VM {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
if code == 0 {
|
if code == 0 {
|
||||||
|
// Record TypeMeta thunk for plugin invoke so next time VT path can hit
|
||||||
|
let tm = crate::runtime::type_meta::get_or_create_type_meta(&p.box_type);
|
||||||
|
tm.set_thunk_plugin_invoke(mid as usize, mid as u16);
|
||||||
// Try decode TLV first entry (string/i32); else return void
|
// Try decode TLV first entry (string/i32); else return void
|
||||||
let vm_out = if let Some((_tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) {
|
let vm_out = if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) {
|
||||||
// naive: try string, then i32
|
match tag {
|
||||||
let s = crate::runtime::plugin_ffi_common::decode::string(payload);
|
1 => crate::runtime::plugin_ffi_common::decode::bool(payload).map(VMValue::Bool).unwrap_or(VMValue::Void),
|
||||||
if !s.is_empty() {
|
2 => crate::runtime::plugin_ffi_common::decode::i32(payload).map(|v| VMValue::Integer(v as i64)).unwrap_or(VMValue::Void),
|
||||||
VMValue::String(s)
|
5 => crate::runtime::plugin_ffi_common::decode::f64(payload).map(VMValue::Float).unwrap_or(VMValue::Void),
|
||||||
} else if let Some(v) = crate::runtime::plugin_ffi_common::decode::i32(payload) {
|
6 => VMValue::String(crate::runtime::plugin_ffi_common::decode::string(payload)),
|
||||||
VMValue::Integer(v as i64)
|
_ => VMValue::Void,
|
||||||
} else {
|
|
||||||
VMValue::Void
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
VMValue::Void
|
VMValue::Void
|
||||||
@ -688,12 +880,18 @@ impl VM {
|
|||||||
// If this is a user InstanceBox, redirect to lowered function: Class.method/arity
|
// If this is a user InstanceBox, redirect to lowered function: Class.method/arity
|
||||||
if let Some(inst) = arc_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
if let Some(inst) = arc_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||||
let func_name = format!("{}.{}{}", inst.class_name, method, format!("/{}", args.len()));
|
let func_name = format!("{}.{}{}", inst.class_name, method, format!("/{}", args.len()));
|
||||||
// Populate vtable cache if method_id is known
|
// Populate TypeMeta thunk table and legacy vtable cache if method_id is known
|
||||||
if let Some(mid) = method_id {
|
if let Some(mid) = method_id {
|
||||||
|
// TypeMeta preferred store (MIR target)
|
||||||
|
let tm = crate::runtime::type_meta::get_or_create_type_meta(&inst.class_name);
|
||||||
|
tm.set_thunk_mir_target(mid as usize, func_name.clone());
|
||||||
|
// Legacy cache retained for compatibility
|
||||||
let vkey = self.build_vtable_key(&inst.class_name, mid, args.len());
|
let vkey = self.build_vtable_key(&inst.class_name, mid, args.len());
|
||||||
self.boxcall_vtable_funcname.entry(vkey).or_insert(func_name.clone());
|
self.boxcall_vtable_funcname.entry(vkey).or_insert(func_name.clone());
|
||||||
}
|
}
|
||||||
// If this call-site is hot, cache the function name for direct calls next time
|
// Record in Poly-PIC immediately (size <= 4)
|
||||||
|
self.record_poly_pic(&pic_key, &recv, &func_name);
|
||||||
|
// If this call-site is hot, also cache in legacy Mono-PIC for backward behavior
|
||||||
const PIC_THRESHOLD: u32 = 8;
|
const PIC_THRESHOLD: u32 = 8;
|
||||||
if self.pic_hits(&pic_key) >= PIC_THRESHOLD {
|
if self.pic_hits(&pic_key) >= PIC_THRESHOLD {
|
||||||
self.boxcall_pic_funcname.insert(pic_key.clone(), func_name.clone());
|
self.boxcall_pic_funcname.insert(pic_key.clone(), func_name.clone());
|
||||||
@ -711,6 +909,12 @@ impl VM {
|
|||||||
}
|
}
|
||||||
// Otherwise, direct box method call
|
// Otherwise, direct box method call
|
||||||
if debug_boxcall { eprintln!("[BoxCall] Taking BoxRef path for method '{}'", method); }
|
if debug_boxcall { eprintln!("[BoxCall] Taking BoxRef path for method '{}'", method); }
|
||||||
|
// Populate TypeMeta for builtin path if method_id present (BuiltinCall thunk)
|
||||||
|
if let Some(mid) = method_id {
|
||||||
|
let label = arc_box.type_name().to_string();
|
||||||
|
let tm = crate::runtime::type_meta::get_or_create_type_meta(&label);
|
||||||
|
tm.set_thunk_builtin(mid as usize, method.to_string());
|
||||||
|
}
|
||||||
let cloned_box = arc_box.share_box();
|
let cloned_box = arc_box.share_box();
|
||||||
self.call_box_method(cloned_box, method, nyash_args)?
|
self.call_box_method(cloned_box, method, nyash_args)?
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,6 +47,11 @@ impl BufferBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// バッファ内容をコピーしてVec<u8>として取得(FFIやエンコード向け)
|
||||||
|
pub fn to_vec(&self) -> Vec<u8> {
|
||||||
|
self.data.read().unwrap().clone()
|
||||||
|
}
|
||||||
|
|
||||||
/// Rust向けヘルパー: バッファ長をusizeで取得(テスト用)
|
/// Rust向けヘルパー: バッファ長をusizeで取得(テスト用)
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.data.read().unwrap().len()
|
self.data.read().unwrap().len()
|
||||||
|
|||||||
@ -12,6 +12,7 @@ pub mod unified_registry;
|
|||||||
pub mod nyash_runtime;
|
pub mod nyash_runtime;
|
||||||
// pub mod plugin_box; // legacy - 古いPluginBox
|
// pub mod plugin_box; // legacy - 古いPluginBox
|
||||||
// pub mod plugin_loader; // legacy - Host VTable使用
|
// pub mod plugin_loader; // legacy - Host VTable使用
|
||||||
|
pub mod type_meta;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|||||||
@ -28,27 +28,73 @@ pub mod decode {
|
|||||||
if buf.len() < 8 + size { return None; }
|
if buf.len() < 8 + size { return None; }
|
||||||
Some((tag, size, &buf[8..8 + size]))
|
Some((tag, size, &buf[8..8 + size]))
|
||||||
}
|
}
|
||||||
|
/// Decode bool payload (size must be 1; nonzero => true)
|
||||||
|
pub fn bool(payload: &[u8]) -> Option<bool> {
|
||||||
|
if payload.len() != 1 { return None; }
|
||||||
|
Some(payload[0] != 0)
|
||||||
|
}
|
||||||
/// Decode i32 payload (size must be 4)
|
/// Decode i32 payload (size must be 4)
|
||||||
pub fn i32(payload: &[u8]) -> Option<i32> {
|
pub fn i32(payload: &[u8]) -> Option<i32> {
|
||||||
if payload.len() != 4 { return None; }
|
if payload.len() != 4 { return None; }
|
||||||
let mut b = [0u8;4]; b.copy_from_slice(payload);
|
let mut b = [0u8;4]; b.copy_from_slice(payload);
|
||||||
Some(i32::from_le_bytes(b))
|
Some(i32::from_le_bytes(b))
|
||||||
}
|
}
|
||||||
|
/// Decode f64 payload (size must be 8)
|
||||||
|
pub fn f64(payload: &[u8]) -> Option<f64> {
|
||||||
|
if payload.len() != 8 { return None; }
|
||||||
|
let mut b = [0u8;8]; b.copy_from_slice(payload);
|
||||||
|
Some(f64::from_le_bytes(b))
|
||||||
|
}
|
||||||
/// Decode UTF-8 string/bytes
|
/// Decode UTF-8 string/bytes
|
||||||
pub fn string(payload: &[u8]) -> String {
|
pub fn string(payload: &[u8]) -> String {
|
||||||
String::from_utf8_lossy(payload).to_string()
|
String::from_utf8_lossy(payload).to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get nth TLV entry from a buffer with header
|
||||||
|
pub fn tlv_nth(buf: &[u8], n: usize) -> Option<(u8, usize, &[u8])> {
|
||||||
|
if buf.len() < 4 { return None; }
|
||||||
|
let mut off = 4usize;
|
||||||
|
for i in 0..=n {
|
||||||
|
if buf.len() < off + 4 { return None; }
|
||||||
|
let tag = buf[off];
|
||||||
|
let _rsv = buf[off+1];
|
||||||
|
let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize;
|
||||||
|
if buf.len() < off + 4 + size { return None; }
|
||||||
|
if i == n {
|
||||||
|
return Some((tag, size, &buf[off+4..off+4+size]));
|
||||||
|
}
|
||||||
|
off += 4 + size;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TLV encode helpers for primitive Nyash values
|
/// TLV encode helpers for primitive Nyash values
|
||||||
pub mod encode {
|
pub mod encode {
|
||||||
|
/// tag for Bool
|
||||||
|
const TAG_BOOL: u8 = 1;
|
||||||
/// tag for I32
|
/// tag for I32
|
||||||
const TAG_I32: u8 = 2;
|
const TAG_I32: u8 = 2;
|
||||||
|
/// tag for I64
|
||||||
|
const TAG_I64: u8 = 3;
|
||||||
|
/// tag for F32
|
||||||
|
const TAG_F32: u8 = 4;
|
||||||
|
/// tag for F64
|
||||||
|
const TAG_F64: u8 = 5;
|
||||||
/// tag for UTF-8 string
|
/// tag for UTF-8 string
|
||||||
const TAG_STRING: u8 = 6;
|
const TAG_STRING: u8 = 6;
|
||||||
|
/// tag for raw bytes
|
||||||
|
const TAG_BYTES: u8 = 7;
|
||||||
/// tag for Plugin Handle (type_id + instance_id)
|
/// tag for Plugin Handle (type_id + instance_id)
|
||||||
const TAG_HANDLE: u8 = 8;
|
const TAG_HANDLE: u8 = 8;
|
||||||
|
|
||||||
|
/// Append a bool TLV entry (tag=1, size=1)
|
||||||
|
pub fn bool(buf: &mut Vec<u8>, v: bool) {
|
||||||
|
buf.push(TAG_BOOL);
|
||||||
|
buf.push(0u8);
|
||||||
|
buf.extend_from_slice(&(1u16).to_le_bytes());
|
||||||
|
buf.push(if v { 1u8 } else { 0u8 });
|
||||||
|
}
|
||||||
/// Append an i32 TLV entry (tag=2, size=4, little-endian)
|
/// Append an i32 TLV entry (tag=2, size=4, little-endian)
|
||||||
pub fn i32(buf: &mut Vec<u8>, v: i32) {
|
pub fn i32(buf: &mut Vec<u8>, v: i32) {
|
||||||
buf.push(TAG_I32);
|
buf.push(TAG_I32);
|
||||||
@ -56,6 +102,27 @@ pub mod encode {
|
|||||||
buf.extend_from_slice(&(4u16).to_le_bytes());
|
buf.extend_from_slice(&(4u16).to_le_bytes());
|
||||||
buf.extend_from_slice(&v.to_le_bytes());
|
buf.extend_from_slice(&v.to_le_bytes());
|
||||||
}
|
}
|
||||||
|
/// Append an i64 TLV entry (tag=3, size=8)
|
||||||
|
pub fn i64(buf: &mut Vec<u8>, v: i64) {
|
||||||
|
buf.push(TAG_I64);
|
||||||
|
buf.push(0u8);
|
||||||
|
buf.extend_from_slice(&(8u16).to_le_bytes());
|
||||||
|
buf.extend_from_slice(&v.to_le_bytes());
|
||||||
|
}
|
||||||
|
/// Append an f32 TLV entry (tag=4, size=4)
|
||||||
|
pub fn f32(buf: &mut Vec<u8>, v: f32) {
|
||||||
|
buf.push(TAG_F32);
|
||||||
|
buf.push(0u8);
|
||||||
|
buf.extend_from_slice(&(4u16).to_le_bytes());
|
||||||
|
buf.extend_from_slice(&v.to_le_bytes());
|
||||||
|
}
|
||||||
|
/// Append an f64 TLV entry (tag=5, size=8)
|
||||||
|
pub fn f64(buf: &mut Vec<u8>, v: f64) {
|
||||||
|
buf.push(TAG_F64);
|
||||||
|
buf.push(0u8);
|
||||||
|
buf.extend_from_slice(&(8u16).to_le_bytes());
|
||||||
|
buf.extend_from_slice(&v.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
/// Append a string TLV entry (tag=6, size=u16 trunc, UTF-8)
|
/// Append a string TLV entry (tag=6, size=u16 trunc, UTF-8)
|
||||||
pub fn string(buf: &mut Vec<u8>, s: &str) {
|
pub fn string(buf: &mut Vec<u8>, s: &str) {
|
||||||
@ -66,6 +133,14 @@ pub mod encode {
|
|||||||
buf.extend_from_slice(&((len as u16).to_le_bytes()));
|
buf.extend_from_slice(&((len as u16).to_le_bytes()));
|
||||||
buf.extend_from_slice(&bytes[..len]);
|
buf.extend_from_slice(&bytes[..len]);
|
||||||
}
|
}
|
||||||
|
/// Append bytes TLV (tag=7)
|
||||||
|
pub fn bytes(buf: &mut Vec<u8>, data: &[u8]) {
|
||||||
|
let len = core::cmp::min(data.len(), u16::MAX as usize);
|
||||||
|
buf.push(TAG_BYTES);
|
||||||
|
buf.push(0u8);
|
||||||
|
buf.extend_from_slice(&(len as u16).to_le_bytes());
|
||||||
|
buf.extend_from_slice(&data[..len]);
|
||||||
|
}
|
||||||
|
|
||||||
/// Append a plugin handle TLV entry (tag=8, size=8, type_id:u32 + instance_id:u32)
|
/// Append a plugin handle TLV entry (tag=8, size=8, type_id:u32 + instance_id:u32)
|
||||||
pub fn plugin_handle(buf: &mut Vec<u8>, type_id: u32, instance_id: u32) {
|
pub fn plugin_handle(buf: &mut Vec<u8>, type_id: u32, instance_id: u32) {
|
||||||
@ -76,3 +151,55 @@ pub mod encode {
|
|||||||
buf.extend_from_slice(&instance_id.to_le_bytes());
|
buf.extend_from_slice(&instance_id.to_le_bytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{encode, decode};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode_decode_bool() {
|
||||||
|
let mut buf = super::encode_tlv_header(1);
|
||||||
|
encode::bool(&mut buf, true);
|
||||||
|
let (tag, sz, payload) = decode::tlv_first(&buf).unwrap();
|
||||||
|
assert_eq!(tag, 1);
|
||||||
|
assert_eq!(sz, 1);
|
||||||
|
assert_eq!(decode::bool(payload), Some(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode_decode_i32() {
|
||||||
|
let mut buf = super::encode_tlv_header(1);
|
||||||
|
encode::i32(&mut buf, 123456);
|
||||||
|
let (tag, sz, payload) = decode::tlv_first(&buf).unwrap();
|
||||||
|
assert_eq!(tag, 2);
|
||||||
|
assert_eq!(sz, 4);
|
||||||
|
assert_eq!(decode::i32(payload), Some(123456));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode_decode_f64() {
|
||||||
|
let mut buf = super::encode_tlv_header(1);
|
||||||
|
encode::f64(&mut buf, 3.14159);
|
||||||
|
let (tag, sz, payload) = decode::tlv_first(&buf).unwrap();
|
||||||
|
assert_eq!(tag, 5);
|
||||||
|
assert_eq!(sz, 8);
|
||||||
|
let v = decode::f64(payload).unwrap();
|
||||||
|
assert!((v - 3.14159).abs() < 1e-9);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode_decode_string_and_bytes() {
|
||||||
|
let mut buf = super::encode_tlv_header(2);
|
||||||
|
encode::string(&mut buf, "hello");
|
||||||
|
encode::bytes(&mut buf, &[1,2,3,4]);
|
||||||
|
// First entry string
|
||||||
|
let (tag, _sz, payload) = decode::tlv_nth(&buf, 0).unwrap();
|
||||||
|
assert_eq!(tag, 6);
|
||||||
|
assert_eq!(decode::string(payload), "hello");
|
||||||
|
// Second entry bytes
|
||||||
|
let (tag2, sz2, payload2) = decode::tlv_nth(&buf, 1).unwrap();
|
||||||
|
assert_eq!(tag2, 7);
|
||||||
|
assert_eq!(sz2, 4);
|
||||||
|
assert_eq!(payload2, &[1,2,3,4]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
121
src/runtime/type_meta.rs
Normal file
121
src/runtime/type_meta.rs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
//! TypeMeta and Thunk table for unified Box dispatch
|
||||||
|
//!
|
||||||
|
//! Phase 9.79b.3 scaffolding:
|
||||||
|
//! - Provides per-type metadata with a vector of method thunks (by slot index)
|
||||||
|
//! - Each thunk currently holds a target function name (MIR function),
|
||||||
|
//! which VM can call via `call_function_by_name`.
|
||||||
|
//! - Versioning is sourced from `cache_versions` using label `BoxRef:{class}`.
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
|
||||||
|
/// Target of a method thunk
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum ThunkTarget {
|
||||||
|
/// Call a lowered MIR function by name
|
||||||
|
MirFunction(String),
|
||||||
|
/// Call plugin invoke function using receiver's plugin handle
|
||||||
|
PluginInvoke { method_id: u16 },
|
||||||
|
/// Call builtin NyashBox method by name (name-based dispatch)
|
||||||
|
BuiltinCall { method: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A single method thunk entry (scaffolding)
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct MethodThunk {
|
||||||
|
/// Target info
|
||||||
|
target: RwLock<Option<ThunkTarget>>,
|
||||||
|
/// Flags placeholder (e.g., universal, builtin, plugin etc.)
|
||||||
|
_flags: RwLock<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MethodThunk {
|
||||||
|
pub fn new() -> Self { Self { target: RwLock::new(None), _flags: RwLock::new(0) } }
|
||||||
|
pub fn get_target(&self) -> Option<ThunkTarget> { self.target.read().ok().and_then(|g| g.clone()) }
|
||||||
|
pub fn set_mir_target(&self, name: String) { if let Ok(mut g) = self.target.write() { *g = Some(ThunkTarget::MirFunction(name)); } }
|
||||||
|
pub fn set_plugin_invoke(&self, method_id: u16) { if let Ok(mut g) = self.target.write() { *g = Some(ThunkTarget::PluginInvoke { method_id }); } }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Per-type metadata including thunk table
|
||||||
|
pub struct TypeMeta {
|
||||||
|
class_name: String,
|
||||||
|
/// Thunk table indexed by method slot (method_id)
|
||||||
|
thunks: RwLock<Vec<Arc<MethodThunk>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeMeta {
|
||||||
|
fn new(class_name: String) -> Self {
|
||||||
|
Self { class_name, thunks: RwLock::new(Vec::new()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure that the thunk table length is at least `len`.
|
||||||
|
pub fn ensure_len(&self, len: usize) {
|
||||||
|
if let Ok(mut tbl) = self.thunks.write() {
|
||||||
|
if tbl.len() < len {
|
||||||
|
let to_add = len - tbl.len();
|
||||||
|
for _ in 0..to_add { tbl.push(Arc::new(MethodThunk::new())); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get thunk for slot, if present
|
||||||
|
pub fn get_thunk(&self, slot: usize) -> Option<Arc<MethodThunk>> {
|
||||||
|
let tbl = self.thunks.read().ok()?;
|
||||||
|
tbl.get(slot).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set thunk target name for slot
|
||||||
|
pub fn set_thunk_mir_target(&self, slot: usize, target_name: String) {
|
||||||
|
self.ensure_len(slot + 1);
|
||||||
|
if let Some(th) = self.get_thunk(slot) { th.set_mir_target(target_name); }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_thunk_plugin_invoke(&self, slot: usize, method_id: u16) {
|
||||||
|
self.ensure_len(slot + 1);
|
||||||
|
if let Some(th) = self.get_thunk(slot) { th.set_plugin_invoke(method_id); }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_thunk_builtin(&self, slot: usize, method: String) {
|
||||||
|
self.ensure_len(slot + 1);
|
||||||
|
if let Some(th) = self.get_thunk(slot) {
|
||||||
|
if let Ok(mut g) = th.target.write() {
|
||||||
|
*g = Some(ThunkTarget::BuiltinCall { method });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Current version from global cache (for diagnostics / PIC keys)
|
||||||
|
pub fn current_version(&self) -> u32 {
|
||||||
|
crate::runtime::cache_versions::get_version(&format!("BoxRef:{}", self.class_name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static TYPE_META_REGISTRY: Lazy<Mutex<HashMap<String, Arc<TypeMeta>>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
|
/// Get or create TypeMeta for a given class name
|
||||||
|
pub fn get_or_create_type_meta(class_name: &str) -> Arc<TypeMeta> {
|
||||||
|
let mut map = TYPE_META_REGISTRY.lock().unwrap();
|
||||||
|
if let Some(m) = map.get(class_name) { return m.clone(); }
|
||||||
|
let m = Arc::new(TypeMeta::new(class_name.to_string()));
|
||||||
|
map.insert(class_name.to_string(), m.clone());
|
||||||
|
m
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dump registry contents for diagnostics
|
||||||
|
pub fn dump_registry() {
|
||||||
|
let map = TYPE_META_REGISTRY.lock().unwrap();
|
||||||
|
eprintln!("[REG] TypeMeta registry dump ({} types)", map.len());
|
||||||
|
for (name, meta) in map.iter() {
|
||||||
|
let tbl = meta.thunks.read().ok();
|
||||||
|
let len = tbl.as_ref().map(|t| t.len()).unwrap_or(0);
|
||||||
|
eprintln!(" - {}: thunks={} v{}", name, len, meta.current_version());
|
||||||
|
if let Some(t) = tbl {
|
||||||
|
for (i, th) in t.iter().enumerate() {
|
||||||
|
if let Some(target) = th.get_target() {
|
||||||
|
eprintln!(" slot {} -> {:?}", i, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user