From 6488b0542e953eb18bbbeee5573319b07c0e5ef3 Mon Sep 17 00:00:00 2001 From: Moe Charm Date: Thu, 4 Sep 2025 03:41:02 +0900 Subject: [PATCH] =?UTF-8?q?Phase=2012.7=E5=AE=8C=E4=BA=86=20+=20ChatGPT5?= =?UTF-8?q?=E3=81=AB=E3=82=88=E3=82=8BVM=E3=83=AA=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=AF=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📚 Phase 12.7 ドキュメント整理 - ChatGPT5作成のANCP Token仕様書v1を整備 - フォルダ構造を機能別に再編成: - ancp-specs/ : ANCP圧縮技法仕様 - grammar-specs/ : 文法改革仕様 - implementation/ : 実装計画 - ai-feedback/ : AIアドバイザーフィードバック - 各フォルダにREADME.md作成で導線改善 ## 🔧 ChatGPT5によるVMリファクタリング - vm_instructions.rs (1927行) をモジュール分割: - boxcall.rs : Box呼び出し処理 - call.rs : 関数呼び出し処理 - extern_call.rs : 外部関数処理 - function_new.rs : FunctionBox生成 - newbox.rs : Box生成処理 - plugin_invoke.rs : プラグイン呼び出し - VM実行をファイル分割で整理: - vm_state.rs : 状態管理 - vm_exec.rs : 実行エンジン - vm_control_flow.rs : 制御フロー - vm_gc.rs : GC処理 - plugin_loader_v2もモジュール化 ## ✨ 新機能実装 - FunctionBox呼び出しのVM/MIR統一進捗 - ラムダ式のFunctionBox変換テスト追加 - 関数値の直接呼び出し基盤整備 次ステップ: ANCPプロトタイプ実装開始(Week 1) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CURRENT_TASK.md | 158 +- Cargo.toml | 2 + .../current/function_values_and_captures.md | 7 + .../roadmap/phases/phase-12.7/README.md | 26 +- .../chatgpt5-ancp-implementation-advice.md | 111 + .../ancp-specs/ANCP-Token-Specification-v1.md | 230 ++ .../phases/phase-12.7/ancp-specs/README.md | 57 + .../ULTIMATE-AI-CODING-GUIDE.md | 0 .../compression-reference-libraries.md | 0 .../extreme-sugar-proposals.txt | 0 .../{ => ancp-specs}/sugar-formatter-tool.txt | 0 .../phases/phase-12.7/grammar-specs/README.md | 72 + .../grammar-reform-final-decision.txt | 0 .../grammar-technical-spec.txt | 0 .../ANCP-IMPLEMENTATION-PLAN.md | 153 ++ .../phase-12.7/implementation/README.md | 68 + .../implementation-final-checklist.txt | 0 src/ast.rs | 6 +- src/backend/dispatch.rs | 1 + src/backend/llvm/compiler.rs | 45 + src/backend/mod.rs | 4 + src/backend/vm.rs | 290 +-- src/backend/vm_control_flow.rs | 9 + src/backend/vm_exec.rs | 317 +++ src/backend/vm_gc.rs | 99 + src/backend/vm_instructions.rs | 1927 ----------------- src/backend/vm_instructions/boxcall.rs | 559 +++++ src/backend/vm_instructions/call.rs | 41 + src/backend/vm_instructions/core.rs | 365 ++++ src/backend/vm_instructions/extern_call.rs | 111 + src/backend/vm_instructions/function_new.rs | 34 + src/backend/vm_instructions/mod.rs | 15 + src/backend/vm_instructions/newbox.rs | 47 + src/backend/vm_instructions/plugin_invoke.rs | 154 ++ src/backend/vm_state.rs | 193 ++ src/box_factory/builtin.rs | 74 + src/box_factory/mod.rs | 1 + src/boxes/array/mod.rs | 4 + src/boxes/p2p_box.rs | 41 +- src/interpreter/core.rs | 48 + src/jit/lower/core.rs | 28 + src/jit/lower/extern_thunks.rs | 47 + src/mir/builder.rs | 84 +- src/mir/instruction.rs | 22 + src/mir/printer.rs | 7 + src/runtime/nyash_runtime.rs | 11 +- src/runtime/plugin_loader_v2.rs | 1639 -------------- .../plugin_loader_v2/enabled/globals.rs | 27 + .../plugin_loader_v2/enabled/loader.rs | 183 ++ src/runtime/plugin_loader_v2/enabled/mod.rs | 8 + src/runtime/plugin_loader_v2/enabled/types.rs | 157 ++ src/runtime/plugin_loader_v2/mod.rs | 12 + src/runtime/plugin_loader_v2/stub.rs | 35 + src/runtime/unified_registry.rs | 6 + src/tests/functionbox_call_tests.rs | 65 + src/tests/mir_lambda_functionbox.rs | 25 + src/tests/vm_functionbox_call.rs | 49 + 57 files changed, 3803 insertions(+), 3871 deletions(-) create mode 100644 docs/development/roadmap/phases/phase-12.7/ai-feedback/chatgpt5-ancp-implementation-advice.md create mode 100644 docs/development/roadmap/phases/phase-12.7/ancp-specs/ANCP-Token-Specification-v1.md create mode 100644 docs/development/roadmap/phases/phase-12.7/ancp-specs/README.md rename docs/development/roadmap/phases/phase-12.7/{ => ancp-specs}/ULTIMATE-AI-CODING-GUIDE.md (100%) rename docs/development/roadmap/phases/phase-12.7/{ => ancp-specs}/compression-reference-libraries.md (100%) rename docs/development/roadmap/phases/phase-12.7/{ => ancp-specs}/extreme-sugar-proposals.txt (100%) rename docs/development/roadmap/phases/phase-12.7/{ => ancp-specs}/sugar-formatter-tool.txt (100%) create mode 100644 docs/development/roadmap/phases/phase-12.7/grammar-specs/README.md rename docs/development/roadmap/phases/phase-12.7/{ => grammar-specs}/grammar-reform-final-decision.txt (100%) rename docs/development/roadmap/phases/phase-12.7/{ => grammar-specs}/grammar-technical-spec.txt (100%) create mode 100644 docs/development/roadmap/phases/phase-12.7/implementation/ANCP-IMPLEMENTATION-PLAN.md create mode 100644 docs/development/roadmap/phases/phase-12.7/implementation/README.md rename docs/development/roadmap/phases/phase-12.7/{ => implementation}/implementation-final-checklist.txt (100%) create mode 100644 src/backend/vm_control_flow.rs create mode 100644 src/backend/vm_exec.rs create mode 100644 src/backend/vm_gc.rs delete mode 100644 src/backend/vm_instructions.rs create mode 100644 src/backend/vm_instructions/boxcall.rs create mode 100644 src/backend/vm_instructions/call.rs create mode 100644 src/backend/vm_instructions/core.rs create mode 100644 src/backend/vm_instructions/extern_call.rs create mode 100644 src/backend/vm_instructions/function_new.rs create mode 100644 src/backend/vm_instructions/mod.rs create mode 100644 src/backend/vm_instructions/newbox.rs create mode 100644 src/backend/vm_instructions/plugin_invoke.rs create mode 100644 src/backend/vm_state.rs create mode 100644 src/box_factory/builtin.rs delete mode 100644 src/runtime/plugin_loader_v2.rs create mode 100644 src/runtime/plugin_loader_v2/enabled/globals.rs create mode 100644 src/runtime/plugin_loader_v2/enabled/loader.rs create mode 100644 src/runtime/plugin_loader_v2/enabled/mod.rs create mode 100644 src/runtime/plugin_loader_v2/enabled/types.rs create mode 100644 src/runtime/plugin_loader_v2/mod.rs create mode 100644 src/runtime/plugin_loader_v2/stub.rs create mode 100644 src/tests/functionbox_call_tests.rs create mode 100644 src/tests/mir_lambda_functionbox.rs create mode 100644 src/tests/vm_functionbox_call.rs diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 366846e2..7521b36f 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -5,9 +5,59 @@ - ドキュメント: `docs/development/roadmap/phases/phase-12/{README.md, PLAN.md, TASKS.md}` - ABI 最小コア: `docs/reference/abi/NYASH_ABI_MIN_CORE.md` -## 概要(Executive Summary) +## 概要(Executive Summary / 圧縮版) - 目的: ユーザー/プラグイン/内蔵を TypeBox+VTable で統一し、VM/JIT/WASM の同一実行を実現。 -- 現状: Tier‑0/Tier‑1 相当の配線完了。VM vtable→Plugin 経路も接続済み。WASM v2の最小ディスパッチ実装も導入。 +- 現状: Phase 12 完了(JIT/VM FunctionBox 呼び出し統一、Lambda→FunctionBox 値化、最小Builtin方針)。WASM v2 最小ディスパッチ導入。 + +次フェーズ: リファクタリング(Phase 13)開始 +- 目標: 1ファイル1000行以内を目安に分割・整理。他AI/将来タスクが読みやすい構造へ。 +- 制約: 挙動不変・公開API維持・段階的分割+逐次ビルド/テスト。 + +Compact Snapshot(2025‑09‑03/Finalize) +- Builtin(最小/条件付き): 既定は plugins-only。wasm32 と cargo test では自動でBuiltin登録。任意で `--features builtin-core` で明示ON。 +- P2P 安定化: `sys.pong` 返信3ms遅延・`ping()`既定300ms。`on_once` 自己送信は deliver直後のflag無効化+flag配列クリア+`debug_active_handler_count` の最短確定で安定化。 +- FunctionBox 呼び出しの MIR/VM 統一: + - C1: `ASTNode::Call` 正規化(Lambda以外は `MirInstruction::Call(func,args)`)。 + - C2: VM `execute_call` が 文字列(関数名)/ `FunctionBox`(BoxRef)両対応。`FunctionBox` 実行は interpreter ヘルパで return を正しく伝播。 + - テスト: `src/tests/functionbox_call_tests.rs`(インタープリタ)、`src/tests/vm_functionbox_call.rs`(MIR→VM)。 + +- Lambda→FunctionBox 値化(最小): `MirInstruction::FunctionNew` を導入。簡易キャプチャ/`me`を MIR で値として保持。 +- JIT: `Call`→`nyash_fn_call0..8` シムで FunctionBox 実行をブリッジ(最大8引数)。 +- LLVM: 本実装は低優先のため保留中(モック実行経路で FunctionNew/Call をサポート)。 + +次タスク(新フェーズ: リファクタリング / 圧縮) +- A1) `backend/vm_instructions.rs` を機能別へ分割(目安: <1000行) +- A2) `runtime/plugin_loader_v2.rs` を役割別へ分割(ffi_tlv/registry/host_bridge/loader) + - 完了: `src/runtime/plugin_loader_v2/` へ分割(enabled/{types,loader,globals}.rs + stub.rs)。公開APIは据え置き(`PluginLoaderV2`/`PluginBoxV2`/`make_plugin_box_v2`/`get_global_loader_v2` 等) + - A3) `backend/vm.rs` の状態/実行/GC/診断を分離 + - 進捗: `ControlFlow` を `src/backend/vm_control_flow.rs` に抽出し、`vm.rs` から再エクスポートで互換維持(次は実行ループの段階切り出し) +- B1) `mir/builder.rs` の `build_expression` 群を `builder/exprs.rs` へ抽出 +- B2) `interpreter/plugin_loader.rs` 分割(scan/link/abi/util) +- C) 命名/コメント整備・公開APIのre-export最適化・軽微な util 抽出 + +実行メモ(抜粋) +- ユニット: `cargo test`(test時はBuiltin自動ON) +- E2E: `cargo test --features e2e -- --nocapture`(普段は無効) +- FunctionBoxテスト: + - `cargo test --lib functionbox_call_tests -- --nocapture` + - `cargo test --lib vm_functionbox_call -- --nocapture` + +運用フラグ +- 既定: `plugins-only`(Builtin未登録) +- 自動ON: `wasm32` / `test` では Builtin 有効 +- 手動ON: `--features builtin-core`(Builtin最小コアを有効化) + + +## 次タスク(優先順) +- フェーズA(安全分割・挙動不変) + - A1) vm_instructions を 10前後のモジュールへ分割(consts/arith/compare/flow/call/boxcall/array/refs/future/newbox/print_debug) + - A2) plugin_loader_v2 を 4〜5ファイルへ分割(ffi_tlv/registry/host_bridge/loader/errors) + - A3) vm を 3〜4ファイルへ分割(state/exec/gc/format) +- フェーズB(読みやすさ整形) + - B1) mir/builder の expr系切り出し + - B2) interpreter/plugin_loader の役割分離 +- フェーズC(軽整理) + - 命名/コメント整備、公開API re-export、1000行未満へ微調整 ## 完了(Done) - TypeBox ABI 雛形: `src/runtime/type_box_abi.rs` @@ -24,12 +74,48 @@ - Console.readLine フォールバック(VM/Plugin 両経路): stdin 読み込み/EOF=Null 返却で無限ループ防止 - WASM v2 統一ディスパッチ(最小): console/array/map のスロット対応 +進捗アップデート(Phase 13 / 2025-09-03) +- A1 完了: `src/backend/vm_instructions.rs` をモジュール分割 + - 新構成: `src/backend/vm_instructions/{mod.rs, core.rs, call.rs, newbox.rs, function_new.rs, extern_call.rs, boxcall.rs, plugin_invoke.rs}` + - 役割: + - `core.rs`: const/binop/unary/compare/print/ctrl/type/phi/mem/array/refs/weak/barriers/exn/await + - `call.rs`: 関数呼び出し(FunctionBox対応) + - `newbox.rs`: NewBox + - `function_new.rs`: Lambda→FunctionBox 値化 + - `extern_call.rs`: ExternCall(env./registry経路) + - `boxcall.rs`: BoxCall + VTableスタブ + 汎用フォールバック + - `plugin_invoke.rs`: PluginInvoke(強制プラグイン経路) + - 可視性: `pub(crate)`/`pub(super)` 調整、`dispatch.rs` 経由の呼び出し互換維持 + - ビルド: `cargo build` 緑(警告のみ/挙動不変) + +- A2 完了: `runtime/plugin_loader_v2.rs` をサブモジュール化 + - 新構成: `src/runtime/plugin_loader_v2/` + - `mod.rs`(cfg切替) + - `enabled/{types.rs, loader.rs, globals.rs}` + - `types.rs`: `PluginBoxV2`/`PluginHandleInner`/`NyashTypeBoxFfi`、`make_plugin_box_v2`/`construct_plugin_box` + - `loader.rs`: `PluginLoaderV2`(extern_call/invoke_instance_method/create_box 等のAPIを維持) + - `globals.rs`: `get_global_loader_v2`/`init_global_loader_v2`/`shutdown_plugins_v2` + - `stub.rs`: plugins無効/wasm 用スタブ(同名API維持) + - 公開API/パス互換: 既存の `crate::runtime::plugin_loader_v2::*` 参照はそのまま + - ビルド: `cargo build` 緑(警告のみ) + +- A3 進行中: `backend/vm.rs` の段階分割 + - 第1段: `ControlFlow` を `src/backend/vm_control_flow.rs` に抽出し、`vm.rs` から再エクスポート(完了) + - 第2段: 実行ループを `src/backend/vm_exec.rs` へ抽出(完了) + - 移動対象: `VM::execute_module`、`VM::execute_function`、`VM::call_function_by_name`、`VM::execute_instruction`、`VM::print_cache_stats_summary` + - 可視性調整: `VM` の内部状態(`values/current_function/frame/previous_block/loop_executor/module/instr_counter/exec_start/scope_tracker` 等)を `pub(super)` に変更し、`vm_exec.rs` から安全に参照できるようにした。GCダイアグ用メソッド(`gc_print_roots_breakdown`/`gc_print_reachability_depth2`)も `pub(super)` 化。 + - `ControlFlow` を `pub(crate)` に変更し、`vm_instructions` サブモジュールの `pub(crate)` API と可視性を整合。 + - ビルド: 成功(警告あり)。`private_interfaces`/`unused_*`/`unexpected_cfg` などの警告は機能的影響なし。 + - 第3段: GC ルート管理と診断を `src/backend/vm_gc.rs` へ抽出(完了) + - 移動対象: `enter_root_region`、`pin_roots`、`leave_root_region`、`gc_site_info`、`gc_print_roots_breakdown`、`gc_print_reachability_depth2` + - 既存呼び出しは変更なし(同名メソッドとして移設)。旧定義は一時的に `*_old` 名へ退避し、後続で削除予定。 + - 注記: `vm.rs` 内の旧メソッドは一時的に `*_old` 名へリネームし残置(安全移行用)。後続ステップで完全削除予定。 + - 次段候補: VM状態→ `vm_state.rs`(構造体フィールド/コンストラクタ/値のget/set/記録系)、GC/診断→ `vm_gc.rs`(ルート管理ラッパ/統計出力など)。 + ## 残タスク(To‑Do) -1) アプリ3モード実行(Script/VM/JIT)の整合確認(ny-echo/ny-array-bench/ny-mem-bench) - - ログ抑制(`NYASH_CLI_VERBOSE=0`)で確認 - - `StatsBox` 未定義は別件として扱う -2) Docs 最終化(slot表・vtable優先方針・トレース変数) -3) Phase 12 クローズ準備(チェックリスト/次フェーズへの接続) +1) リファクタフェーズA/B/C 実施(段階コミット+スモーク) +2) ドキュメント更新(開発者向け構成図・分割指針・API安定ポリシー) +3) LLVM(本実装)は低優先:Call シム import/Lower の設計だけ先に下書き ## 実行コマンド(サマリ) - ビルド: `cargo build --release --features cranelift-jit` @@ -49,6 +135,14 @@ --- 詳細な履歴や議事録は docs 配下の Phase 12 セクションを参照してください。 +## フェーズ13(リファクタ)方針・成功条件 +- 方針: 公開APIを維持しつつ内部構造を機能別に分割。1ファイル1000行以内を目安に段階導入。 +- 手順: 1モジュールずつ分割→ビルド→限定ユニット/スモーク→次へ。 +- 成功条件: + - 大規模ファイル(>1000行)が解消(vm_instructions / plugin_loader_v2 / vm / builder) + - ビルド/主要ユニットが従来通り通る(挙動不変) + - 他AI/将来タスクが読みやすいレイアウト(役割ごとに参照しやすい) + Docs(Phase 12 直近) - [x] Minimal Core ABI方針の文書化(NYASH_ABI_MIN_CORE.md) - [ ] TECHNICAL_DECISIONSの最小ABI/API交渉・互換・安全の章を精緻化(進行中) @@ -58,6 +152,32 @@ Phase 12 ゴール(検証観点) - Cross-backend 同値性: 同一プログラム(Nyashコード)が VM と JIT で同一の最終結果・ログ・副作用(Box状態)を生む。 - ゴールデン/スモーク: 共通テストハーネスで VM/JIT を同条件で走らせ比較(差分があれば落とす)。 +## 残件・課題と対応方針(2025-09-03) + +- VMユニット赤の原因と対応(plugins-onlyでBuiltin未登録) + - 症状: Array/Map などの生成で Unknown Box type(プラグインのみのレジストリ)。 + - 対応: 既定を Builtin 登録に戻し、plugins-only は feature 化。 + - 実装: BuiltinBoxFactory を追加し、NyashRuntime/UnifiedRegistry 初期化時に登録(plugins-only 時はスキップ)。 + - 追加: Cargo.toml に `plugins-only` feature を定義。 + +- P2PBox の once/ping 安定化方針 + - once: deliver 後にフラグ無効化(現行仕様維持、分岐独立を再確認)。 + - ping: `sys.pong` 返信スレッドの遅延を 3ms に調整、`ping()` 既定タイムアウトは 300ms に。 + - 目的: 記録タイミング(last_from/last_intent)競合の低減とCI安定化。 + +- FunctionBox 呼び出しの MIR/VM 統一(段階計画) + - C1) MIR 正規化: `ASTNode::Call` は Lambda 以外を `MirInstruction::Call(func, args)` に正規化(完了)。 + - C2) VM 実行: `func` が 文字列なら名前呼び出し、`FunctionBox` ならインタープリタヘルパで本体実行(完了)。 + - C3) LLVM/JIT: C2 のシムを後続で移植(VMで安定化→JITへ)。Lambda→FunctionBox 値化は小PRで導入予定(MIRのみで関数値を生成できるように)。 + +- テスト整理 + - E2E は feature で切替済み(`--features e2e`)。 + - ユニットは初期化ヘルパ(Builtin登録)で安定化。plugins-only 依存は `#[cfg(feature="plugins-only")]` で保護。 + +- ドキュメント/デモ更新 + - FunctionBox ハンドラのデモを追加(`apps/p2p-function-handler-demo`)。 + - function values / captures / `this→me` / `Parent::` / `?` / `peek` のガイドを `docs/development/current/function_values_and_captures.md` に追記済み。 + > Quick Resume (Phase 12 bridge) @@ -1248,3 +1368,27 @@ How to Run(再現手順) - NewBox(Instance) はJIT側で UnifiedRegistry を直接叩くため、グローバルに必要Factoryを登録しておく(テスト内で注入済み) (以下、旧詳細ログは履歴のため残置) + - 第4段: VM 基本状態を `src/backend/vm_state.rs` へ抽出(完了) + - 移動: `new/with_runtime`、`get_value/set_value`、`record_instruction`、`jit_threshold_from_env`、`loop_execute_phi`(各 `impl VM`) + - `mod.rs` に `mod vm_state;` を追加。各呼び出し元のシンボルは従来どおり `VM::...` で参照可。 + - ビルド: 成功。 + +現状のレイアウト(A3 途中) +- `backend/vm.rs`: VM 本体(構造体・値型・最小グルー)。現在 ~1295 行(旧メソッド退避を除き圧縮済み) +- `backend/vm_exec.rs`: 実行エントリ/ループ/1命令実行 +- `backend/vm_gc.rs`: ルート領域 API と GC 診断出力 +- `backend/vm_state.rs`: 生成・状態・値アクセス・統計・phi 補助 +- `backend/vm_values.rs`: 算術/論理/比較の内部演算 +- `backend/vm_instructions/`: 命令ハンドラ群 +- `backend/vm_boxcall.rs`: VTable/PIC スタブと BoxCall 補助 +- `backend/dispatch.rs`: MIR 命令 → 実行関数 振り分け + +次の分割(提案 / おすすめ) +- S1) `vm_methods.rs` 抽出(Box メソッドディスパッチ) + - 対象: `VM::call_box_method`(大ブロック)+`call_unified_method` ラッパ + - 期待効果: `vm.rs` を < 1000 行へ。呼び出しは現行どおり `VM::call_box_method`。 +- S2) `vm.rs` 旧プレースホルダ(`*_old`, `execute_module_old_moved` など)を段階削除 + - 互換検証後に削除してノイズ低減。 +- S3) `vm_types.rs`(任意) + - `VMError`/`VMValue` 定義を分離し参照しやすく。 + - ただし変更範囲が大きいため最後に予定。 diff --git a/Cargo.toml b/Cargo.toml index 388e13ca..2b9c4398 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ categories = ["development-tools::parsing", "interpreters"] default = ["cli", "plugins"] e2e = [] cli = [] +plugins-only = [] +builtin-core = [] gui = ["dep:egui", "dep:eframe", "dep:egui_extras", "dep:image"] gui-examples = ["gui"] all-examples = ["gui-examples"] diff --git a/docs/development/current/function_values_and_captures.md b/docs/development/current/function_values_and_captures.md index d3fb347c..a38166a3 100644 --- a/docs/development/current/function_values_and_captures.md +++ b/docs/development/current/function_values_and_captures.md @@ -21,3 +21,10 @@ Notes - RefCell-backed locals captured by closures will reflect assignments (`x = ...`) in the outer scope. - For plugin-backed boxes, assignment and argument passing uses share semantics to preserve identity. +MIR/VM call unification (Phase 12) + +- MIR `Call`: accepts either a function name (String) or a `FunctionBox` value. + - If the callee is a String, VM performs a named-function dispatch (existing path). + - If the callee is a `FunctionBox` (BoxRef), VM runs it via the interpreter helper with captures/`me` injected and proper return propagation. +- Lambda immediate calls are still directly lowered inline for P1 compatibility. +- Lambda→FunctionBox: Lambda expressions now lower to a `FunctionNew` MIR instruction that constructs a `FunctionBox` value (minimal: captures currently omitted). This enables MIR-only pipelines to construct and call function values. diff --git a/docs/development/roadmap/phases/phase-12.7/README.md b/docs/development/roadmap/phases/phase-12.7/README.md index cdcbdd4a..8f975f69 100644 --- a/docs/development/roadmap/phases/phase-12.7/README.md +++ b/docs/development/roadmap/phases/phase-12.7/README.md @@ -62,14 +62,26 @@ $NyashCompiler{compile(src){l ast=m.parse(src)l mir=m.lower(ast)r m.codegen(mir) ## 🎯 最重要ドキュメント ### 📚 実装者必読 -- **[🔥 究極のAIコーディングガイド](ULTIMATE-AI-CODING-GUIDE.md)** ← ⭐ START HERE! ⭐ -- [📝 文法改革最終決定](grammar-reform-final-decision.txt) -- [🔧 実装チェックリスト](implementation-final-checklist.txt) -- [⚡ 極限糖衣構文提案](extreme-sugar-proposals.txt) -- [🔄 糖衣構文フォーマッター](sugar-formatter-tool.txt) +- **[🚀 ANCP実装計画(統合版)](implementation/ANCP-IMPLEMENTATION-PLAN.md)** ← ⭐ START HERE! ⭐ +- **[📋 ANCP Token仕様書 v1](ancp-specs/ANCP-Token-Specification-v1.md)** - ChatGPT5作成の最新仕様 +- [🔧 実装チェックリスト](implementation/implementation-final-checklist.txt) -### 🏗️ 技術仕様 -- [📐 文法技術仕様書](grammar-technical-spec.txt) +### 📐 ANCP仕様書 +- **[🔥 究極のAIコーディングガイド](ancp-specs/ULTIMATE-AI-CODING-GUIDE.md)** - 5層圧縮体系 +- [⚡ 極限糖衣構文提案](ancp-specs/extreme-sugar-proposals.txt) +- [🔄 糖衣構文フォーマッター](ancp-specs/sugar-formatter-tool.txt) +- [🔬 圧縮技術参考ライブラリ](ancp-specs/compression-reference-libraries.md) + +### 📝 文法仕様書 +- [📝 文法改革最終決定](grammar-specs/grammar-reform-final-decision.txt) +- [📐 文法技術仕様書](grammar-specs/grammar-technical-spec.txt) + +### 🤖 AIアドバイザーフィードバック +- **[📋 統合フィードバック](ai-feedback/)** - 全AIアドバイザーの知見 + - [ChatGPT5実装アドバイス](ai-feedback/chatgpt5-ancp-implementation-advice.md) + - [Claude/Codex技術分析](ai-feedback/codex-ancp-response.md) + - [Gemini革命的評価](ai-feedback/gemini-ancp-response.md) + - [即座実装ガイド](ai-feedback/quick-implementation-guide.md) ### 📁 アーカイブ(検討過程) - [🗃️ 過去の議論・検討資料](archive/) diff --git a/docs/development/roadmap/phases/phase-12.7/ai-feedback/chatgpt5-ancp-implementation-advice.md b/docs/development/roadmap/phases/phase-12.7/ai-feedback/chatgpt5-ancp-implementation-advice.md new file mode 100644 index 00000000..c47534a4 --- /dev/null +++ b/docs/development/roadmap/phases/phase-12.7/ai-feedback/chatgpt5-ancp-implementation-advice.md @@ -0,0 +1,111 @@ +# ChatGPT5先生のANCP実装アドバイス - 2025-09-03 + +## 🎯 総評:全面支持 + 事故防止のガードレール + +> 「めっちゃ良い計画。やる価値デカいにゃ。」 +> 「Phase 12のABI安定+MIR最適化の上に載る"上物"で、下層を壊さない。」 + +## 📋 Go/No-Go評価 + +- **Go(強)**: 即座にAI効率が出る、下層を壊さない +- **注意**: 文法改革と圧縮を**混ぜない**。段階導入必須 + +## ✅ 成功条件(出荷時に断言できるライン) + +1. **完全可逆**: `P → C → P` & `P → C → F → C → P` が常に一致 +2. **コンパイラ等価**: `compile(P)` と `compile(F)` の MIRハッシュ一致 +3. **曖昧性ゼロ**: 字句規則を形式化(最大貪欲+必要箇所のみセミコロン自動挿入) +4. **ソースマップ2.0**: トークン単位の双方向マップ + BoxID +5. **測定公開**: 削減率・パース時間・LLMトークン消費を数字で提示 + +## 📅 4週間実装計画 + +### Week 1(Phase 12.7-A)— 最小で回す +- P↔C のトークンベース変換(正規表現は使わない) +- 固定辞書20語から開始 +- `nyashc --compact/--decompact` + `sourcemap.json` +- CI: 既存サンプル全ファイルで P→C→P 等価テスト + +### Week 2(12.7-B)— スマート化 +- 文字列/コメント/正規表現リテラル非変換ゾーン認識 +- 自動セミコロン挿入の衝突検出 +- LLMパック: `--llm-pack` が最小仕様カード生成 + +### Week 3(12.7-C)— F層(読み込み専用) +- F(Fusion)を入力専用で導入(出力はC) +- MIR直行デコーダ + 等価性検証 +- 代表5命令だけ実装して漸進展開 + +### Week 4(12.7-D)— ツール/拡張 +- VSCode拡張(C表示⇄Pホバー) +- `nyash fmt --mode=pretty|compact` +- ベンチ自動化:CSV出力 + +## 🚨 設計の"赤線"(破ると事故る) + +1. **Pは正典** - PR/レビューは常にPで行う +2. **識別子衝突禁止** - 予約語→記号化でも曖昧にならない +3. **Unicode強制しない** - 常にASCIIモード完備 +4. **クロージャ/可変長演算** - ASTテンプレで可逆に + +## 💡 記号マッピング実務案 + +### ASCIIモード(デフォルト) +``` +box → $ new → ~n me → m +local → ~l return → ~r from → @ +init → # if → ? else → : +loop → ~L +``` + +### 区切り規則 +- 記号トークンの左右に英数が来たら必ず1スペース自動挿入 +- `~[A-Za-z]` は将来予約 + +## 🔧 実装の鍵 + +### フォーマッタ可逆性 +- 変換は**トークン列→トークン列** +- 文字列/テンプレ/正規表現の中身は一切触らない +- 演算子の前置/後置/単項は個別トークン型 +- セミコロン自動挿入はスタック機械で判定 + +### CLI設計 +```bash +nyashc --compact --mode=ascii|uni --emit=code,sourcemap +nyashc --fusion --emit=fusion,mir --read-only +nyash fmt --mode=pretty|compact +nyash llm-pack +``` + +## 📊 KPI(毎週測定) + +- コード削減率(P→C、P→F) +- パース/フォーマット時間(ms/MB) +- MIR生成時間の差(P vs C vs F) +- LLMトークン削減率 +- 等価テスト失敗率(%) + +## 🔐 互換性とバージョニング + +- **ANCP v1**: `ancp://v1?mode=ascii|uni` マジックコメント +- 将来の記法変更は `ver` と `feature_bits` で表明 + +## ⚠️ リスクと対処 + +| リスク | 対処 | +|--------|------| +| 読みづらさ | Pを正典・Cは生成物。編集は常にP | +| デバッグ困難 | ソースマップ2.0で例外・ログをPへ逆引き | +| 学習コスト | VSCode拡張のホバー復元とQuick Toggle | +| LLMの誤解 | llm-packに最小仕様カード必須付与 | + +--- + +## 💬 結論 + +> **Phase 12.7 は"今やるべき"**。 +> ただし「常にPを正典」「トークン変換で可逆」「Fはまず入力専用」の三原則を守れば、 +> **90%圧縮×AI最適化**の恩恵を"事故ゼロで"取りにいけるにゃ。 + +**次の一歩**: AncpTranscoderのトークン仕様書(BNF/EBNF)作成! \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12.7/ancp-specs/ANCP-Token-Specification-v1.md b/docs/development/roadmap/phases/phase-12.7/ancp-specs/ANCP-Token-Specification-v1.md new file mode 100644 index 00000000..efc65717 --- /dev/null +++ b/docs/development/roadmap/phases/phase-12.7/ancp-specs/ANCP-Token-Specification-v1.md @@ -0,0 +1,230 @@ +# ANCP Transcoder v1 トークン仕様(EBNF+運用ルール) + +Author: ChatGPT5 +Date: 2025-09-03 +Version: 1.0 + +> まずは「P=Pretty(正典)→C=Compact(ANCP ASCII/Unicode)」の**トークン変換**が安全に往復できる最小コア。 + +## 1) レクサの前提 + +* 入力は UTF-8。**ASCIIモード**と**Unicodeモード**を切替(既定=ASCII)。 +* 変換は**トークン列→トークン列**。**文字列/コメント/正規表現**内部は**絶対に変換しない**。 +* 空白/改行/コメントは**隣接トークン間のメタに付与**して保持(ソースマップ2.0)。 + +### 1.1 トークンクラス(共通) + +```ebnf +Identifier = IDStart IDContinue* ; +IDStart = Letter | "_" ; +IDContinue = IDStart | Digit ; +Digit = "0"…"9" ; +Letter = "A"…"Z" | "a"…"z" | NonAsciiLetter ; + +IntLiteral = Digit+ ; +FloatLiteral = Digit+ "." Digit+ (ExponentPart)? | "." Digit+ (ExponentPart)? ; +ExponentPart = ("e"|"E") ("+"|"-")? Digit+ ; + +StringLit = '"' ( Escape | ~["\r\n] )* '"' + | "'" ( Escape | ~['\r\n] )* "'" ; +Escape = "\\" . ; + +RegexLit = "/" ( Escape | ~[/\r\n] )+ "/" [a-z]* ; // Pのみ(Cでは素通し) + +CommentLine = "//" ~[\r\n]* ; +CommentBlock = "/*" .*? "*/" ; // ネスト不可(Phase1) + +WS = Space | Tab ; +NL = "\r"? "\n" ; +``` + +## 2) 予約語と記号マップ(P→C) + +**衝突しないASCII記号**を採用。Unicodeモードは `→` の右側を `uni` 欄で置換。 +**識別子と区別**するため、`~x` 形は**先頭に `~`**を付ける(通常のIDに現れにくい)。 + +| 機能 | Pretty(P) | Compact(C ascii) | Compact(C uni) | +|------|-----------|------------------|----------------| +| Box定義 | `box` | `$` | `$` | +| 新規生成 | `new` | `~n` | `ⁿ` | +| 自参照 | `me` | `m` | `m` | +| 局所変数 | `local` | `~l` | `ˡ` | +| 戻り | `return` | `~r` | `↩` | +| 継承/委譲 | `from` | `@` | `@` | +| 初期化 | `init` | `#` | `#` | +| コンストラクタ | `birth` | `b` | `ᵇ` | +| 静的 | `static` | `S` | `S` | +| 条件 | `if` | `?` | `?` | +| else | `else` | `:` | `:` | +| ループ | `loop` | `~L` | `ᴸ` | +| 継続 | `continue` | `~c` | `↻` | +| 分岐peek | `peek` | `~p` | `ᵖ` | + +> 予約域:`~[A-Za-z]` は**将来予約**で識別子に使えないことを仕様化。 + +## 3) 演算子・糖衣(P↔C 等価) + +* パイプ |>: `a |> f(x)` → **そのまま**(記号は等価、空白最小化のみ) +* セーフアクセス ?.: `o?.f` → **そのまま** +* ディレクティブ /:: `/: name` → **そのまま**(意味を壊さず最小化) + +## 4) セパレータ・自動挿入規約 + +* **C出力**時、**記号トークンの左右に英数IDが隣接**する場合は**1スペース**を強制挿入(`m$X` の誤読防止)。 +* セミコロンは P 側の規約準拠。C では**危険箇所のみ挿入**(§6の「ASI判定」参照)。 + +## 5) 変換アルゴリズム(疑似コード) + +```text +encode(P → C): + lex P → tokens[] + for t in tokens: + if t in (StringLit, Comment*, RegexLit): emit t (verbatim); continue + if t is Keyword and t.lexeme in MAP: emit MAP[t.lexeme] as SymbolToken + else emit t (with WS-minify rules) + apply ASI (only-when-necessary) + attach inter-token trivia to sourcemap + +decode(C → P): + lex C → tokens[] + for t in tokens: + if t is SymbolToken and t.lexeme in INV_MAP: emit INV_MAP[t.lexeme] as Keyword + else emit t + restore WS/comments by sourcemap if available +``` + +## 6) ASI(セミコロン自動挿入)判定(最小) + +**挿入する**条件(どれか): + +1. 次トークンが `}` / EOF +2. 現トークンが `return (~r) / continue (~c) / break` 等で、**直後が行末** +3. 構文上、次トークンが**先頭に来るべき**(例えば次が `box/$` 定義) + +**挿入しない**: + +* 行末でも、次トークンが `(` `[` `{` `.` `?.` `/:` のとき + +## 7) EBNF(P→C 変換で必要なサブセット) + +**目的**:可逆のための**字句と一部構文の境界**を定義。完全文法ではなく、トークン接合規則に必要な核のみ。 + +```ebnf +Program = WS_NL* (Stmt WS_NL*)* ; + +Stmt = BoxDecl + | LocalDecl + | ReturnStmt + | ExprStmt + ; + +BoxDecl = "box" Identifier BoxBody ; +BoxBody = "{" (MemberDecl WS_NL*)* "}" ; + +MemberDecl = ( FieldDecl | MethodDecl | StaticDecl ) ; + +FieldDecl = ( "init" | "#" ) Identifier ( "=" Expr )? ";"? ; +MethodDecl = Identifier ParamList Block ; +StaticDecl = ( "static" | "S" ) MethodDecl ; + +LocalDecl = ( "local" | "~l" ) Identifier ( "=" Expr )? ";"? ; + +ReturnStmt = ( "return" | "~r" ) Expr? ";"? ; + +ExprStmt = Expr ";"? ; + +Expr = AssignExpr ; +AssignExpr = OrExpr ( AssignOp OrExpr )? ; +AssignOp = "=" | "+=" | "-=" | "*=" | "/=" ; + +OrExpr = AndExpr ( "||" AndExpr )* ; +AndExpr = PipeExpr ( "&&" PipeExpr )* ; + +PipeExpr = TernaryExpr ( "|>" CallLike )* ; + +TernaryExpr = NullsafeExpr ( "?" Expr ":" Expr )? ; + +NullsafeExpr = MemberExpr | MemberExpr "?." Identifier | MemberExpr "/:" Identifier ; + +MemberExpr = Primary ( ("." | "[") ... )? ; // 省略(可逆に影響しない部分) + +CallLike = Identifier | Call ; + +Call = Identifier "(" ArgList? ")" ; +ArgList = Expr ("," Expr)* ; + +Primary = Identifier + | Literal + | "(" Expr ")" + ; + +Literal = IntLiteral | FloatLiteral | StringLit | RegexLit ; + +Identifier = see §1.1 ; +``` + +> **ポイント** +> * `FieldDecl` は `init` と `#` を等価扱い(Cでは `#` に寄せる) +> * `StaticDecl` は `static` と `S` を等価 +> * `LocalDecl` は `local` と `~l` を等価 +> * `ReturnStmt` は `return` と `~r` を等価 +> * `box` は `$` と等価(`BoxDecl`) + +## 8) ソースマップ2.0(トークン粒度) + +* **単一フォーマット(JSON Lines 推奨)**:各出力トークンに**元トークン範囲**と**トリビア**を付与。 + +```json +{"out_i":42,"out_span":[l1,c5,l1,c6],"in_file":"foo.ny","in_span":[l10,c1,l10,c3],"trivia":{"lead":" ","trail":""}} +``` + +* 例外/ログは**BoxID + トークン範囲**で P へ逆引き。 + +## 9) 衝突回避ルール(最重要) + +* **ASCIIモード**:`~[A-Za-z]` は**保留記号**。Identifier と**絶対に一致しない**。 +* **記号の周囲**:`$ m` のように**必要時1スペース**(前後が英数IDの場合)。 +* **文字列/コメント/Regex**:**一切変換せず** verbatim。 + +## 10) 例(往復保証) + +**P (Pretty)** + +```nyash +box NyashCompiler { + compile(source) { + local ast = me.parse(source) + local mir = me.lower(ast) + return me.codegen(mir) + } +} +``` + +**C (Compact ASCII)** + +``` +$ NyashCompiler{ + compile(src){ + ~l ast=m.parse(src) + ~l mir=m.lower(ast) + ~r m.codegen(mir) + } +} +``` + +**decode(C) → P** は上記Pと**等価**(空白/改行はソースマップで復元)。 + +--- + +## 実装メモ(すぐ書ける骨組み) + +* レクサは **状態機械**:`DEFAULT / STRING / REGEX / COMMENT` +* 置換は**辞書マッチ → 最長一致**(`box`→`$` を `Identifier` と衝突させない) +* 出力時に**区切り挿入規則**を適用:`need_space(prev, next)` +* ASI は §6 の規則のみ実装(Phase1)。曖昧時は**セミコロン挿入を選ぶ**。 + +--- + +これで **Phase 12.7-A(Week1)** の「P↔C 可逆・安全」まで一気に行けるにゃ。 + +次にやるなら:この仕様をそのまま基に**トークナイザのテストケース**(OK/NG 30本)を並べよう。 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12.7/ancp-specs/README.md b/docs/development/roadmap/phases/phase-12.7/ancp-specs/README.md new file mode 100644 index 00000000..7c771182 --- /dev/null +++ b/docs/development/roadmap/phases/phase-12.7/ancp-specs/README.md @@ -0,0 +1,57 @@ +# ANCP (AI-Nyash Compact Notation Protocol) 仕様書 + +このフォルダには、ANCP圧縮技法に関する全ての仕様書と技術文書が含まれています。 + +## 📄 ドキュメント一覧 + +### 🎯 中核仕様 +- **[ANCP-Token-Specification-v1.md](ANCP-Token-Specification-v1.md)** - ChatGPT5作成のトークン仕様書 v1.0 + - P(Pretty)→ C(Compact)変換の完全仕様 + - EBNF文法定義 + - トークン変換ルール + - 衝突回避メカニズム + +### 🔥 圧縮体系 +- **[ULTIMATE-AI-CODING-GUIDE.md](ULTIMATE-AI-CODING-GUIDE.md)** - 5層圧縮体系の統合ガイド + - L0: Standard (通常のNyash) + - L1: Pretty (整形済み) + - L2: Compact (48%圧縮) + - L3: Sugar (75%圧縮) + - L4: Fusion (90%圧縮) + +### ⚡ 糖衣構文 +- **[extreme-sugar-proposals.txt](extreme-sugar-proposals.txt)** - 極限糖衣構文の提案集 + - パイプライン演算子 `|>` + - 安全アクセス演算子 `?.` + - ディレクティブ記法 `/:` + - その他の革新的構文 + +### 🔄 ツール仕様 +- **[sugar-formatter-tool.txt](sugar-formatter-tool.txt)** - 可逆フォーマッターの設計 + - 双方向変換の保証 + - ソースマップ2.0仕様 + - VSCode統合計画 + +### 📚 参考資料 +- **[compression-reference-libraries.md](compression-reference-libraries.md)** - 関連技術の調査 + - 既存圧縮ツールの比較 + - 学術研究の参照 + - 実装のヒント + +## 🚀 実装優先順位 + +1. **Week 1**: ANCP-Token-Specification-v1 に基づく基本実装 +2. **Week 2**: 糖衣構文の統合 +3. **Week 3**: Fusion層(F)の追加 +4. **Week 4**: ツール・IDE統合 + +## 💡 重要な設計原則 + +- **完全可逆性**: P ↔ C ↔ F の変換で情報損失ゼロ +- **安全性優先**: 文字列・コメント内は変換しない +- **段階的導入**: まずCから、次にF層へ +- **AI最適化**: トークン削減率を最大化 + +--- + +最新の仕様については、ANCP-Token-Specification-v1.md を参照してください。 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12.7/ULTIMATE-AI-CODING-GUIDE.md b/docs/development/roadmap/phases/phase-12.7/ancp-specs/ULTIMATE-AI-CODING-GUIDE.md similarity index 100% rename from docs/development/roadmap/phases/phase-12.7/ULTIMATE-AI-CODING-GUIDE.md rename to docs/development/roadmap/phases/phase-12.7/ancp-specs/ULTIMATE-AI-CODING-GUIDE.md diff --git a/docs/development/roadmap/phases/phase-12.7/compression-reference-libraries.md b/docs/development/roadmap/phases/phase-12.7/ancp-specs/compression-reference-libraries.md similarity index 100% rename from docs/development/roadmap/phases/phase-12.7/compression-reference-libraries.md rename to docs/development/roadmap/phases/phase-12.7/ancp-specs/compression-reference-libraries.md diff --git a/docs/development/roadmap/phases/phase-12.7/extreme-sugar-proposals.txt b/docs/development/roadmap/phases/phase-12.7/ancp-specs/extreme-sugar-proposals.txt similarity index 100% rename from docs/development/roadmap/phases/phase-12.7/extreme-sugar-proposals.txt rename to docs/development/roadmap/phases/phase-12.7/ancp-specs/extreme-sugar-proposals.txt diff --git a/docs/development/roadmap/phases/phase-12.7/sugar-formatter-tool.txt b/docs/development/roadmap/phases/phase-12.7/ancp-specs/sugar-formatter-tool.txt similarity index 100% rename from docs/development/roadmap/phases/phase-12.7/sugar-formatter-tool.txt rename to docs/development/roadmap/phases/phase-12.7/ancp-specs/sugar-formatter-tool.txt diff --git a/docs/development/roadmap/phases/phase-12.7/grammar-specs/README.md b/docs/development/roadmap/phases/phase-12.7/grammar-specs/README.md new file mode 100644 index 00000000..fb9fe809 --- /dev/null +++ b/docs/development/roadmap/phases/phase-12.7/grammar-specs/README.md @@ -0,0 +1,72 @@ +# Nyash文法改革仕様書 + +このフォルダには、Phase 12.7で決定されたNyash文法改革の仕様書が含まれています。 + +## 📄 ドキュメント一覧 + +### 📝 最終決定事項 +- **[grammar-reform-final-decision.txt](grammar-reform-final-decision.txt)** - 文法改革の最終決定 + - 予約語15個への削減 + - peek構文の導入 + - birth統一コンストラクタ + - フィールド宣言の明示化 + +### 📐 技術仕様 +- **[grammar-technical-spec.txt](grammar-technical-spec.txt)** - 詳細な技術仕様書 + - 構文のBNF定義 + - パーサー実装ガイド + - 後方互換性の考慮事項 + +## 🎯 文法改革の要点 + +### 15個の予約語 +``` +box, new, me, public, if, else, loop, break, continue, +peek, return, import, from, birth, fn +``` + +### 主要な変更点 + +#### 1. peek構文(switch/case代替) +```nyash +peek value { + "hello" => print("Hi!") + 42 => print("The answer") + else => print("Other") +} +``` + +#### 2. birth統一(コンストラクタ) +```nyash +box Life { + init { name, energy } + + birth(lifeName) { // すべてのBoxでbirth使用 + me.name = lifeName + me.energy = 100 + } +} +``` + +#### 3. fn{}でFunctionBox作成 +```nyash +local add = fn{a, b => a + b} +``` + +#### 4. フィールド宣言の明示化 +```nyash +box Person { + init { name, age } // フィールドを明示的に宣言 +} +``` + +## 🔄 実装状況 + +- ✅ 仕様決定完了 +- ✅ ChatGPT5による基本実装 +- 🔄 テスト作成中 +- 📅 完全移行(Phase 12.7-B) + +--- + +詳細な実装については、implementation/フォルダを参照してください。 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12.7/grammar-reform-final-decision.txt b/docs/development/roadmap/phases/phase-12.7/grammar-specs/grammar-reform-final-decision.txt similarity index 100% rename from docs/development/roadmap/phases/phase-12.7/grammar-reform-final-decision.txt rename to docs/development/roadmap/phases/phase-12.7/grammar-specs/grammar-reform-final-decision.txt diff --git a/docs/development/roadmap/phases/phase-12.7/grammar-technical-spec.txt b/docs/development/roadmap/phases/phase-12.7/grammar-specs/grammar-technical-spec.txt similarity index 100% rename from docs/development/roadmap/phases/phase-12.7/grammar-technical-spec.txt rename to docs/development/roadmap/phases/phase-12.7/grammar-specs/grammar-technical-spec.txt diff --git a/docs/development/roadmap/phases/phase-12.7/implementation/ANCP-IMPLEMENTATION-PLAN.md b/docs/development/roadmap/phases/phase-12.7/implementation/ANCP-IMPLEMENTATION-PLAN.md new file mode 100644 index 00000000..941b2112 --- /dev/null +++ b/docs/development/roadmap/phases/phase-12.7/implementation/ANCP-IMPLEMENTATION-PLAN.md @@ -0,0 +1,153 @@ +# ANCP実装計画 - 統合ドキュメント + +Date: 2025-09-03 +Status: Implementation Ready + +## 🎯 概要 + +ANCP (AI-Nyash Compact Notation Protocol) - 90%可逆圧縮技法の実装計画。 +3人のAIアドバイザー(ChatGPT5、Claude、Gemini)の知見を統合。 + +## 📊 三者の評価まとめ + +| アドバイザー | 評価 | 重要アドバイス | +|-------------|------|----------------| +| ChatGPT5 | 全面支持・即実行推奨 | 段階導入・ガードレール・事故防止 | +| Claude | 革命的発明 | 実装順序・技術チェックリスト | +| Gemini | パラダイムシフト | IDE統合・段階的導入・学術価値 | +| Codex | 技術的厳密性重視 | AST正規化・トークン最適化・検証 | + +## 🚀 統合実装計画(4週間) + +### Week 1: 最小実装(P↔C) +**ChatGPT5案 + Codex技術仕様** +```bash +# 実装内容 +- 固定辞書20語(ASCII記号マッピング) +- トークンベース変換(正規表現不使用) +- AST正規化(P*)ルール確立 +- nyashc CLI基本実装 +``` + +**成果物**: +- [ ] BNF/EBNF仕様書 +- [ ] 最小エンコーダー/デコーダー +- [ ] ラウンドトリップテスト +- [ ] sourcemap.json生成 + +### Week 2: スマート化 +**Gemini提案 + ChatGPT5安全策** +```bash +# 機能追加 +- 文字列/コメント保護 +- セミコロン自動挿入 +- プロジェクト辞書(.ancprc) +- エラー位置逆引き +``` + +**成果物**: +- [ ] 非変換ゾーン認識 +- [ ] 衝突検出メカニズム +- [ ] LLMパック機能 +- [ ] デバッグ体験改善 + +### Week 3: F層導入(読み込み専用) +**Codex仕様 + ChatGPT5段階導入** +```bash +# F層実装 +- 入力専用モード +- MIR直行デコーダー +- 等価性検証(MIRハッシュ) +- 文法圧縮(Re-Pair/Sequitur) +``` + +**成果物**: +- [ ] F層パーサー +- [ ] MIR等価性テスト +- [ ] 圧縮率90%達成 +- [ ] Property-based testing + +### Week 4: ツール・統合 +**Gemini IDE統合 + Codex CLI設計** +```bash +# 開発ツール +- VS Code拡張(ホバー表示) +- フォーマッター統合 +- ベンチマーク自動化 +- CI/CD統合 +``` + +**成果物**: +- [ ] VS Code拡張α版 +- [ ] nyash fmt統合 +- [ ] ベンチマークCSV +- [ ] ドキュメント完成 + +## ⚠️ 設計原則(赤線) + +### ChatGPT5の三原則 +1. **常にPを正典** - C/Fは生成物 +2. **トークン変換で可逆** - 正規表現は使わない +3. **Fはまず入力専用** - 段階的に拡張 + +### Codexの技術要件 +1. **AST正規化必須** - P*の厳密定義 +2. **トークン最適化** - GPT/Claude向け +3. **MIR等価性証明** - ハッシュ一致 + +### Geminiの実用要件 +1. **IDE統合最優先** - 開発体験重視 +2. **段階的導入** - fusion{}ブロック +3. **意味論的圧縮** - 将来への道筋 + +## 📈 測定指標(KPI) + +| 指標 | 目標 | 測定方法 | +|------|------|----------| +| 圧縮率 | 90% | トークン数比較 | +| 可逆性 | 100% | ラウンドトリップテスト | +| MIR等価 | 100% | ハッシュ一致率 | +| 変換速度 | <100ms/1000行 | ベンチマーク | +| LLM効率 | 2-3倍 | コンテキスト拡張率 | + +## 🛠️ 実装優先順位 + +### 今すぐ(Day 1-3) +1. BNF/EBNF仕様書作成 +2. 20語辞書決定 +3. 最小プロトタイプ + +### 第1週(Day 4-7) +1. トークナイザー拡張 +2. 基本CLI実装 +3. CIテスト準備 + +### 第2週以降 +- Week 2-4の計画通り実行 + +## 📚 関連ドキュメント + +### 設計・仕様 +- [grammar-reform-final-decision.txt](archive/grammar-reform-final-decision.txt) +- [extreme-sugar-proposals.txt](extreme-sugar-proposals.txt) +- [ULTIMATE-AI-CODING-GUIDE.md](ULTIMATE-AI-CODING-GUIDE.md) + +### AIフィードバック +- [ChatGPT5実装アドバイス](ai-feedback/chatgpt5-ancp-implementation-advice.md) +- [Claude技術分析](ai-feedback/codex-ancp-response.md) +- [Gemini革命的評価](ai-feedback/gemini-ancp-response.md) + +### 実装ガイド +- [即座実装ガイド](ai-feedback/quick-implementation-guide.md) +- [技術チェックリスト](ai-feedback/technical-checklist.md) +- [実用的洞察](ai-feedback/actionable-insights.md) + +## 🎉 結論 + +**全AIアドバイザーが「今すぐやるべき」と評価!** + +ChatGPT5の事故防止ガードレール、Codexの技術的厳密性、Geminiの実用性を統合し、**4週間で90%圧縮を実現**する。 + +--- + +**次のアクション**: BNF/EBNF仕様書作成開始! \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12.7/implementation/README.md b/docs/development/roadmap/phases/phase-12.7/implementation/README.md new file mode 100644 index 00000000..8c14205d --- /dev/null +++ b/docs/development/roadmap/phases/phase-12.7/implementation/README.md @@ -0,0 +1,68 @@ +# 実装ガイド・計画 + +このフォルダには、Phase 12.7の実装に関する計画とチェックリストが含まれています。 + +## 📄 ドキュメント一覧 + +### 🚀 実装計画 +- **[ANCP-IMPLEMENTATION-PLAN.md](ANCP-IMPLEMENTATION-PLAN.md)** - 統合実装計画 + - 4週間の段階的実装スケジュール + - 全AIアドバイザーの知見を統合 + - KPI(測定指標)の定義 + - リスクと対策 + +### 🔧 チェックリスト +- **[implementation-final-checklist.txt](implementation-final-checklist.txt)** - 実装チェックリスト + - 文法改革の実装項目 + - ANCP実装の必須タスク + - テスト・検証項目 + - ツール統合タスク + +## 📅 実装スケジュール概要 + +### Week 1: 基礎実装(P↔C) +- [ ] BNF/EBNF仕様書完成 +- [ ] 20語の固定辞書実装 +- [ ] トークンベース変換器 +- [ ] 基本的なCLI(nyashc) +- [ ] ラウンドトリップテスト + +### Week 2: スマート化 +- [ ] 文字列・コメント保護 +- [ ] セミコロン自動挿入 +- [ ] プロジェクト辞書(.ancprc) +- [ ] エラー位置逆引き +- [ ] LLMパック機能 + +### Week 3: F層導入 +- [ ] Fusion層パーサー(読み込み専用) +- [ ] MIR直行デコーダー +- [ ] 等価性検証(MIRハッシュ) +- [ ] 90%圧縮達成 +- [ ] Property-based testing + +### Week 4: ツール統合 +- [ ] VS Code拡張(ホバー表示) +- [ ] フォーマッター統合 +- [ ] ベンチマーク自動化 +- [ ] CI/CD統合 +- [ ] ドキュメント完成 + +## 🎯 次のアクション + +1. **ANCP-Token-Specification-v1.md** に基づくトークナイザー実装 +2. テストケース(OK/NG 30本)の作成 +3. 最小プロトタイプの開発開始 + +## 📊 成功指標 + +| 指標 | 目標値 | 測定方法 | +|------|--------|----------| +| 圧縮率 | 90% | トークン数比較 | +| 可逆性 | 100% | ラウンドトリップテスト | +| MIR等価 | 100% | ハッシュ一致率 | +| 変換速度 | <100ms/1000行 | ベンチマーク | + +--- + +実装を開始する前に、必ずANCP-IMPLEMENTATION-PLAN.mdを熟読してください。 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12.7/implementation-final-checklist.txt b/docs/development/roadmap/phases/phase-12.7/implementation/implementation-final-checklist.txt similarity index 100% rename from docs/development/roadmap/phases/phase-12.7/implementation-final-checklist.txt rename to docs/development/roadmap/phases/phase-12.7/implementation/implementation-final-checklist.txt diff --git a/src/ast.rs b/src/ast.rs index 5de8ff74..518af381 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -245,7 +245,7 @@ pub enum StatementNode { } /// Catch節の構造体 -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct CatchClause { pub exception_type: Option, // None = catch-all pub variable_name: Option, // 例外を受け取る変数名 @@ -254,7 +254,7 @@ pub struct CatchClause { } /// リテラル値の型 (Clone可能) -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum LiteralValue { String(String), Integer(i64), @@ -375,7 +375,7 @@ impl fmt::Display for BinaryOperator { } /// AST Node - Everything is Box哲学に基づく統一構造 -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum ASTNode { /// プログラム全体 - 文のリスト Program { diff --git a/src/backend/dispatch.rs b/src/backend/dispatch.rs index 269b90c9..4a944da0 100644 --- a/src/backend/dispatch.rs +++ b/src/backend/dispatch.rs @@ -67,6 +67,7 @@ pub(super) fn execute_instruction(vm: &mut VM, instruction: &MirInstruction, deb // Complex operations MirInstruction::Call { dst, func, args, effects: _ } => vm.execute_call(*dst, *func, args), + MirInstruction::FunctionNew { dst, params, body, captures, me } => vm.execute_function_new(*dst, params, body, captures, me), MirInstruction::BoxCall { dst, box_val, method, method_id, args, effects: _ , .. } => vm.execute_boxcall(*dst, *box_val, method, *method_id, args), MirInstruction::PluginInvoke { dst, box_val, method, args, effects: _ } => vm.execute_plugin_invoke(*dst, *box_val, method, args), MirInstruction::NewBox { dst, box_type, args } => vm.execute_newbox(*dst, box_type, args), diff --git a/src/backend/llvm/compiler.rs b/src/backend/llvm/compiler.rs index 60676028..b04f81f1 100644 --- a/src/backend/llvm/compiler.rs +++ b/src/backend/llvm/compiler.rs @@ -7,6 +7,7 @@ use crate::mir::instruction::{MirInstruction, ConstValue, BinaryOp, UnaryOp, Com use crate::mir::ValueId; use crate::box_trait::{NyashBox, IntegerBox, StringBox, BoolBox}; use crate::boxes::math_box::FloatBox; +use crate::boxes::function_box::FunctionBox; use crate::boxes::null_box::NullBox; use super::context::CodegenContext; use std::collections::HashMap; @@ -157,6 +158,50 @@ impl LLVMCompiler { } } + MirInstruction::FunctionNew { dst, params, body, captures, me } => { + // Minimal: build FunctionBox with empty captures unless provided + let mut env = crate::boxes::function_box::ClosureEnv::new(); + // Materialize captures (by value) if any + for (name, vid) in captures.iter() { + let v = self.values.get(vid).ok_or_else(|| format!("Value %{} not found for capture {}", vid.0, name))?; + env.captures.insert(name.clone(), v.clone_box()); + } + // me capture (weak) if provided and is a box + if let Some(m) = me { + if let Some(b) = self.values.get(m) { + if let Some(arc) = std::sync::Arc::downcast::({ + let bx: std::sync::Arc = std::sync::Arc::from(b.clone_box()); + bx + }).ok() { + env.me_value = Some(std::sync::Arc::downgrade(&arc)); + } + } + } + let fun = FunctionBox::with_env(params.clone(), body.clone(), env); + self.values.insert(*dst, Box::new(fun)); + println!(" 🧰 %{} = function_new (params={})", dst.0, params.len()); + } + + MirInstruction::Call { dst, func, args, .. } => { + // Resolve callee + let cal = self.values.get(func) + .ok_or_else(|| format!("Call target %{} not found", func.0))?; + if let Some(fb) = cal.as_any().downcast_ref::() { + // Collect args as NyashBox + let mut argv: Vec> = Vec::new(); + for a in args { + let av = self.values.get(a).ok_or_else(|| format!("Arg %{} not found", a.0))?; + argv.push(av.clone_box()); + } + let out = crate::interpreter::run_function_box(fb, argv) + .map_err(|e| format!("FunctionBox call failed: {:?}", e))?; + if let Some(d) = dst { self.values.insert(*d, out); } + println!(" 📞 call %{} -> {}", func.0, dst.map(|v| v.0).unwrap_or(u32::MAX)); + } else { + println!(" ⚠️ Skipping call: callee not FunctionBox"); + } + } + _ => { // Other instructions not yet implemented println!(" ⚠️ Skipping instruction: {:?}", inst); diff --git a/src/backend/mod.rs b/src/backend/mod.rs index eb3464d4..70998622 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -13,6 +13,10 @@ pub mod control_flow; pub mod dispatch; pub mod frame; pub mod gc_helpers; +pub mod vm_control_flow; +mod vm_gc; // A3: GC roots & diagnostics extracted +mod vm_exec; // A3: execution loop extracted +mod vm_state; // A3: state & basic helpers extracted pub mod abi_util; // Shared ABI/utility helpers pub mod mir_interpreter; // Lightweight MIR interpreter diff --git a/src/backend/vm.rs b/src/backend/vm.rs index c3b5fc9d..b42f151a 100644 --- a/src/backend/vm.rs +++ b/src/backend/vm.rs @@ -7,7 +7,7 @@ * Typical Callers: runner (VM backend), instruction handlers (vm_instructions) */ -use crate::mir::{MirModule, MirFunction, MirInstruction, ConstValue, BinaryOp, CompareOp, UnaryOp, ValueId, BasicBlockId}; +use crate::mir::{ConstValue, ValueId, BasicBlockId, MirModule, MirFunction, MirInstruction}; use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox, VoidBox}; use std::collections::HashMap; use std::sync::Arc; @@ -183,13 +183,13 @@ impl From<&ConstValue> for VMValue { /// Virtual Machine state pub struct VM { /// Value storage (uses ValueId as direct index into Vec for O(1) access) - values: Vec>, + pub(super) values: Vec>, /// Current function being executed - current_function: Option, + pub(super) current_function: Option, /// Frame state (current block, pc, last result) - frame: ExecutionFrame, + pub(super) frame: ExecutionFrame, /// Previous basic block (for phi node resolution) - previous_block: Option, + pub(super) previous_block: Option, /// Simple field storage for objects (maps reference -> field -> value) pub(super) object_fields: HashMap>, /// Class name mapping for objects (for visibility checks) @@ -197,13 +197,13 @@ pub struct VM { /// Marks ValueIds that represent internal (me/this) references within the current function pub(super) object_internal: std::collections::HashSet, /// Loop executor for handling phi nodes and loop-specific logic - loop_executor: LoopExecutor, + pub(super) loop_executor: LoopExecutor, /// Shared runtime for box creation and declarations pub(super) runtime: NyashRuntime, /// Scope tracker for calling fini on scope exit - scope_tracker: ScopeTracker, + pub(super) scope_tracker: ScopeTracker, /// Active MIR module during execution (for function calls) - module: Option, + pub(super) module: Option, /// Instruction execution counters (by MIR opcode) pub(super) instr_counter: std::collections::HashMap<&'static str, usize>, /// Execution start time for optional stats @@ -243,31 +243,9 @@ pub struct VM { impl VM { pub fn runtime_ref(&self) -> &NyashRuntime { &self.runtime } - /// Enter a GC root region and return a guard that leaves on drop - pub(super) fn enter_root_region(&mut self) { - self.scope_tracker.enter_root_region(); - } - /// Pin a slice of VMValue as roots in the current region - pub(super) fn pin_roots<'a>(&mut self, values: impl IntoIterator) { - for v in values { - self.scope_tracker.pin_root(v); - } - } - - /// Leave current GC root region - pub(super) fn leave_root_region(&mut self) { self.scope_tracker.leave_root_region(); } - - /// Site info for GC logs: (func, bb, pc) - pub(super) fn gc_site_info(&self) -> (String, i64, i64) { - let func = self.current_function.as_deref().unwrap_or("").to_string(); - let bb = self.frame.current_block.map(|b| b.0 as i64).unwrap_or(-1); - let pc = self.frame.pc as i64; - (func, bb, pc) - } - - /// Print a simple breakdown of root VMValue kinds and top BoxRef types - fn gc_print_roots_breakdown(&self) { + /// Print a simple breakdown of root VMValue kinds and top BoxRef types (old-moved placeholder) + pub(super) fn gc_print_roots_breakdown_old(&self) { use std::collections::HashMap; let roots = self.scope_tracker.roots_snapshot(); let mut kinds: HashMap<&'static str, u64> = HashMap::new(); @@ -292,7 +270,7 @@ impl VM { top.truncate(5); eprintln!("[GC] roots_boxref_top5: {:?}", top); } - fn gc_print_reachability_depth2(&self) { + pub(super) fn gc_print_reachability_depth2_old(&self) { use std::collections::HashMap; let roots = self.scope_tracker.roots_snapshot(); let mut child_types: HashMap = HashMap::new(); @@ -327,106 +305,7 @@ impl VM { top.truncate(5); eprintln!("[GC] depth2_children: total={} top5={:?}", child_count, top); } - fn jit_threshold_from_env() -> u32 { - std::env::var("NYASH_JIT_THRESHOLD") - .ok() - .and_then(|s| s.parse::().ok()) - .filter(|&v| v > 0) - .unwrap_or(64) - } - /// Helper: execute phi via LoopExecutor with previous_block-based selection (Step2 skeleton) - pub(super) fn loop_execute_phi(&mut self, dst: ValueId, inputs: &[(BasicBlockId, ValueId)]) -> Result { - if inputs.is_empty() { - return Err(VMError::InvalidInstruction("Phi node has no inputs".to_string())); - } - let debug_phi = std::env::var("NYASH_VM_DEBUG").ok().as_deref() == Some("1") - || std::env::var("NYASH_VM_DEBUG_PHI").ok().as_deref() == Some("1"); - if debug_phi { eprintln!("[VM] phi-select (delegated) prev={:?} inputs={:?}", self.previous_block, inputs); } - // Borrow just the values storage immutably to avoid borrowing entire &self in closure - let values_ref = &self.values; - let res = self.loop_executor.execute_phi(dst, inputs, |val_id| { - let index = val_id.to_usize(); - if index < values_ref.len() { - if let Some(ref value) = values_ref[index] { - Ok(value.clone()) - } else { - Err(VMError::InvalidValue(format!("Value {} not set", val_id))) - } - } else { - Err(VMError::InvalidValue(format!("Value {} out of bounds", val_id))) - } - }); - if debug_phi { - match &res { - Ok(v) => eprintln!("[VM] phi-result -> {:?}", v), - Err(e) => eprintln!("[VM] phi-error -> {:?}", e), - } - } - res - } - /// Create a new VM instance - pub fn new() -> Self { - Self { - values: Vec::new(), - current_function: None, - frame: ExecutionFrame::new(), - previous_block: None, - object_fields: HashMap::new(), - object_class: HashMap::new(), - object_internal: std::collections::HashSet::new(), - loop_executor: LoopExecutor::new(), - runtime: NyashRuntime::new(), - scope_tracker: ScopeTracker::new(), - module: None, - instr_counter: std::collections::HashMap::new(), - exec_start: None, - boxcall_hits_vtable: 0, - boxcall_hits_poly_pic: 0, - boxcall_hits_mono_pic: 0, - boxcall_hits_generic: 0, - boxcall_pic_hits: 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(), - type_versions: std::collections::HashMap::new(), - jit_manager: Some(crate::jit::manager::JitManager::new(Self::jit_threshold_from_env())), - // TODO: Re-enable when interpreter refactoring is complete - // box_registry: Arc::new(UnifiedBoxRegistry::new()), - // #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] - // plugin_loader: None, - // scope_tracker: ScopeTracker::new(), - // box_declarations: Arc::new(RwLock::new(HashMap::new())), - } - } - - /// Create a VM with an external runtime (dependency injection) - pub fn with_runtime(runtime: NyashRuntime) -> Self { - Self { - values: Vec::new(), - current_function: None, - frame: ExecutionFrame::new(), - previous_block: None, - object_fields: HashMap::new(), - object_class: HashMap::new(), - object_internal: std::collections::HashSet::new(), - loop_executor: LoopExecutor::new(), - runtime, - scope_tracker: ScopeTracker::new(), - module: None, - instr_counter: std::collections::HashMap::new(), - exec_start: None, - boxcall_hits_vtable: 0, - boxcall_hits_poly_pic: 0, - boxcall_hits_mono_pic: 0, - boxcall_hits_generic: 0, - boxcall_pic_hits: 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(), - type_versions: std::collections::HashMap::new(), - jit_manager: Some(crate::jit::manager::JitManager::new(Self::jit_threshold_from_env())), - } - } + // TODO: Re-enable when interpreter refactoring is complete /* @@ -451,58 +330,12 @@ impl VM { } */ - /// Execute a MIR module - pub fn execute_module(&mut self, module: &MirModule) -> Result, VMError> { - // Store module for nested calls - 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 - self.instr_counter.clear(); - self.exec_start = Some(Instant::now()); - // Find main function - let main_function = module.get_function("main") - .ok_or_else(|| VMError::InvalidInstruction("No main function found".to_string()))?; - - // Execute main function - let result = self.execute_function(main_function)?; - - // Optional: print VM stats - self.maybe_print_stats(); - // Optional: print concise JIT unified stats - self.maybe_print_jit_unified_stats(); - - // Optional: print cache stats summary - if crate::config::env::vm_pic_stats() { - self.print_cache_stats_summary(); - } - - // Optional: print JIT detailed summary (top functions) - if let Some(jm) = &self.jit_manager { jm.print_summary(); } - - // Optional: GC diagnostics if enabled - { - let lvl = crate::config::env::gc_trace_level(); - if lvl > 0 { - if let Some((sp, rd, wr)) = self.runtime.gc.snapshot_counters() { - eprintln!("[GC] counters: safepoints={} read_barriers={} write_barriers={}", sp, rd, wr); - } - let roots_total = self.scope_tracker.root_count_total(); - let root_regions = self.scope_tracker.root_regions(); - let field_slots: usize = self.object_fields.values().map(|m| m.len()).sum(); - eprintln!("[GC] mock_mark: roots_total={} regions={} object_field_slots={}", roots_total, root_regions, field_slots); - if lvl >= 2 { self.gc_print_roots_breakdown(); } - if lvl >= 3 { self.gc_print_reachability_depth2(); } - } - } - - // Convert result to NyashBox - Ok(result.to_nyash_box()) + /// Execute a MIR module (old placeholder; moved to vm_exec.rs) + pub fn execute_module_old_moved(&mut self, _module: &MirModule) -> Result, VMError> { + Ok(Box::new(VoidBox::new())) } - fn print_cache_stats_summary(&self) { + fn print_cache_stats_summary_old(&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 }; @@ -523,7 +356,7 @@ impl VM { } /// Call a MIR function by name with VMValue arguments - pub(super) fn call_function_by_name(&mut self, func_name: &str, args: Vec) -> Result { + pub(super) fn call_function_by_name_old(&mut self, func_name: &str, args: Vec) -> Result { // Root region: ensure args stay rooted during nested call self.enter_root_region(); self.pin_roots(args.iter()); @@ -574,7 +407,7 @@ impl VM { } /// Execute a single function - fn execute_function(&mut self, function: &MirFunction) -> Result { + fn execute_function_old(&mut self, function: &MirFunction) -> Result { self.current_function = Some(function.signature.name.clone()); // Phase 10_a: JIT profiling (function entry) if let Some(jm) = &mut self.jit_manager { @@ -721,89 +554,15 @@ impl VM { } } - /// Execute a single instruction - fn execute_instruction(&mut self, instruction: &MirInstruction) -> Result { - // Record instruction for stats - let debug_global = std::env::var("NYASH_VM_DEBUG").ok().as_deref() == Some("1"); - let debug_exec = debug_global || std::env::var("NYASH_VM_DEBUG_EXEC").ok().as_deref() == Some("1"); - if debug_exec { eprintln!("[VM] execute_instruction: {:?}", instruction); } - self.record_instruction(instruction); - super::dispatch::execute_instruction(self, instruction, debug_global) - - } + /// Execute a single instruction (old placeholder; moved to vm_exec.rs) + fn execute_instruction_old(&mut self, _instruction: &MirInstruction) -> Result { unreachable!("moved") } - /// Get a value from storage - pub(super) fn get_value(&self, value_id: ValueId) -> Result { - let index = value_id.to_usize(); - if index < self.values.len() { - if let Some(ref value) = self.values[index] { - Ok(value.clone()) - } else { - Err(VMError::InvalidValue(format!("Value {} not set", value_id))) - } - } else { - Err(VMError::InvalidValue(format!("Value {} out of bounds", value_id))) - } - } - /// Set a value in the VM storage - pub(super) fn set_value(&mut self, value_id: ValueId, value: VMValue) { - let index = value_id.to_usize(); - // Resize Vec if necessary - if index >= self.values.len() { - self.values.resize(index + 1, None); - } - self.values[index] = Some(value); - } - /// Record an instruction execution for statistics - pub(super) fn record_instruction(&mut self, instruction: &MirInstruction) { - let key: &'static str = match instruction { - MirInstruction::Const { .. } => "Const", - MirInstruction::BinOp { .. } => "BinOp", - MirInstruction::UnaryOp { .. } => "UnaryOp", - MirInstruction::Compare { .. } => "Compare", - MirInstruction::Load { .. } => "Load", - MirInstruction::Store { .. } => "Store", - MirInstruction::Call { .. } => "Call", - MirInstruction::BoxCall { .. } => "BoxCall", - MirInstruction::Branch { .. } => "Branch", - MirInstruction::Jump { .. } => "Jump", - MirInstruction::Return { .. } => "Return", - MirInstruction::Phi { .. } => "Phi", - MirInstruction::NewBox { .. } => "NewBox", - MirInstruction::TypeCheck { .. } => "TypeCheck", - MirInstruction::Cast { .. } => "Cast", - MirInstruction::TypeOp { .. } => "TypeOp", - MirInstruction::ArrayGet { .. } => "ArrayGet", - MirInstruction::ArraySet { .. } => "ArraySet", - MirInstruction::Copy { .. } => "Copy", - MirInstruction::Debug { .. } => "Debug", - MirInstruction::Print { .. } => "Print", - MirInstruction::Nop => "Nop", - MirInstruction::Throw { .. } => "Throw", - MirInstruction::Catch { .. } => "Catch", - MirInstruction::Safepoint => "Safepoint", - MirInstruction::RefNew { .. } => "RefNew", - MirInstruction::RefGet { .. } => "RefGet", - MirInstruction::RefSet { .. } => "RefSet", - MirInstruction::WeakNew { .. } => "WeakNew", - MirInstruction::WeakLoad { .. } => "WeakLoad", - MirInstruction::BarrierRead { .. } => "BarrierRead", - MirInstruction::BarrierWrite { .. } => "BarrierWrite", - MirInstruction::WeakRef { .. } => "WeakRef", - MirInstruction::Barrier { .. } => "Barrier", - MirInstruction::FutureNew { .. } => "FutureNew", - MirInstruction::FutureSet { .. } => "FutureSet", - MirInstruction::Await { .. } => "Await", - MirInstruction::ExternCall { .. } => "ExternCall", - MirInstruction::PluginInvoke { .. } => "PluginInvoke", - }; - *self.instr_counter.entry(key).or_insert(0) += 1; - } + /// Phase 9.78a: Unified method dispatch for all Box types @@ -1254,12 +1013,7 @@ impl VM { /// RAII guard for GC root regions // Root region guard removed in favor of explicit enter/leave to avoid borrow conflicts -/// Control flow result from instruction execution -pub(super) enum ControlFlow { - Continue, - Jump(BasicBlockId), - Return(VMValue), -} +pub(super) use crate::backend::vm_control_flow::ControlFlow; impl Default for VM { fn default() -> Self { diff --git a/src/backend/vm_control_flow.rs b/src/backend/vm_control_flow.rs new file mode 100644 index 00000000..bc7a08e6 --- /dev/null +++ b/src/backend/vm_control_flow.rs @@ -0,0 +1,9 @@ +use crate::mir::BasicBlockId; +use crate::backend::vm::VMValue; + +/// Control flow result from instruction execution +pub(crate) enum ControlFlow { + Continue, + Jump(BasicBlockId), + Return(VMValue), +} diff --git a/src/backend/vm_exec.rs b/src/backend/vm_exec.rs new file mode 100644 index 00000000..af95e931 --- /dev/null +++ b/src/backend/vm_exec.rs @@ -0,0 +1,317 @@ +/*! + * VM Execution Loop (extracted from vm.rs) + * + * Contains the high-level execution entrypoints and per-instruction dispatch glue: + * - VM::execute_module + * - VM::execute_function + * - VM::call_function_by_name + * - VM::execute_instruction (delegates to backend::dispatch) + * - VM::print_cache_stats_summary (stats helper) + * + * Behavior and public APIs are preserved. This is a pure move/refactor. + */ + +use crate::mir::{MirModule, MirFunction, MirInstruction, ValueId, BasicBlockId}; +use crate::box_trait::NyashBox; +use super::{vm::VM, vm::VMError, vm::VMValue}; +use crate::backend::vm_control_flow::ControlFlow; +use std::sync::Arc; + +impl VM { + /// Execute a MIR module + pub fn execute_module(&mut self, module: &MirModule) -> Result, VMError> { + self.module = Some(module.clone()); + if std::env::var("NYASH_REG_DUMP").ok().as_deref() == Some("1") { + crate::runtime::type_meta::dump_registry(); + } + self.instr_counter.clear(); + self.exec_start = Some(std::time::Instant::now()); + let main_function = module + .get_function("main") + .ok_or_else(|| VMError::InvalidInstruction("No main function found".to_string()))?; + let result = self.execute_function(main_function)?; + self.maybe_print_stats(); + self.maybe_print_jit_unified_stats(); + if crate::config::env::vm_pic_stats() { + self.print_cache_stats_summary(); + } + if let Some(jm) = &self.jit_manager { jm.print_summary(); } + { + let lvl = crate::config::env::gc_trace_level(); + if lvl > 0 { + if let Some((sp, rd, wr)) = self.runtime.gc.snapshot_counters() { + eprintln!("[GC] counters: safepoints={} read_barriers={} write_barriers={}", sp, rd, wr); + } + let roots_total = self.scope_tracker.root_count_total(); + let root_regions = self.scope_tracker.root_regions(); + let field_slots: usize = self.object_fields.values().map(|m| m.len()).sum(); + eprintln!( + "[GC] mock_mark: roots_total={} regions={} object_field_slots={}", + roots_total, root_regions, field_slots + ); + if lvl >= 2 { self.gc_print_roots_breakdown(); } + if lvl >= 3 { self.gc_print_reachability_depth2(); } + } + } + Ok(result.to_nyash_box()) + } + + pub(super) 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={} | hits: vt={} poly={} mono={} generic={}", + sites_poly, + avg_entries, + sites_mono, + hits_total, + vt_entries, + self.boxcall_hits_vtable, + self.boxcall_hits_poly_pic, + self.boxcall_hits_mono_pic, + self.boxcall_hits_generic + ); + 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 + pub(super) fn call_function_by_name( + &mut self, + func_name: &str, + args: Vec, + ) -> Result { + self.enter_root_region(); + self.pin_roots(args.iter()); + let module_ref = self + .module + .as_ref() + .ok_or_else(|| VMError::InvalidInstruction("No active module".to_string()))?; + let function_ref = module_ref + .get_function(func_name) + .ok_or_else(|| VMError::InvalidInstruction(format!("Function '{}' not found", func_name)))?; + let function = function_ref.clone(); + + let saved_values = std::mem::take(&mut self.values); + let saved_current_function = self.current_function.clone(); + let saved_current_block = self.frame.current_block; + let saved_previous_block = self.previous_block; + let saved_pc = self.frame.pc; + let saved_last_result = self.frame.last_result; + + for (i, param_id) in function.params.iter().enumerate() { + if let Some(arg) = args.get(i) { + self.set_value(*param_id, arg.clone()); + } + } + if let Some(first) = function.params.get(0) { + if let Some((class_part, _rest)) = func_name.split_once('.') { + self.object_class.insert(*first, class_part.to_string()); + self.object_internal.insert(*first); + } + } + + let result = self.execute_function(&function); + + self.values = saved_values; + self.current_function = saved_current_function; + self.frame.current_block = saved_current_block; + self.previous_block = saved_previous_block; + self.frame.pc = saved_pc; + self.frame.last_result = saved_last_result; + self.scope_tracker.leave_root_region(); + result + } + + /// Execute a single function + pub(super) fn execute_function(&mut self, function: &MirFunction) -> Result { + use crate::box_trait::{StringBox, IntegerBox, BoolBox, VoidBox}; + use crate::runtime::global_hooks; + use crate::instance_v2::InstanceBox; + use super::control_flow; + + self.current_function = Some(function.signature.name.clone()); + if let Some(jm) = &mut self.jit_manager { + if let Ok(s) = std::env::var("NYASH_JIT_THRESHOLD") { + if let Ok(t) = s.parse::() { if t > 0 { jm.set_threshold(t); } } + } + jm.record_entry(&function.signature.name); + let _ = jm.maybe_compile(&function.signature.name, function); + 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) { + eprintln!( + "[JIT] dispatch would go to handle={} for {} (stub)", + h, function.signature.name + ); + } + } + } + + self.loop_executor.initialize(); + self.scope_tracker.push_scope(); + global_hooks::push_task_scope(); + + let args_vec: Vec = function + .params + .iter() + .filter_map(|pid| self.get_value(*pid).ok()) + .collect(); + 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"); + self.enter_root_region(); + self.pin_roots(args_vec.iter()); + if let Some(compiled) = self + .jit_manager + .as_ref() + .map(|jm| jm.is_compiled(&function.signature.name)) + { + if compiled { + crate::runtime::host_api::set_current_vm(self as *mut _); + let jit_val = if let Some(jm_mut) = self.jit_manager.as_mut() { + jm_mut.execute_compiled( + &function.signature.name, + &function.signature.return_type, + &args_vec, + ) + } else { + None + }; + crate::runtime::host_api::clear_current_vm(); + if let Some(val) = jit_val { + self.leave_root_region(); + self.scope_tracker.pop_scope(); + global_hooks::pop_task_scope(); + return Ok(val); + } 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") + { + eprintln!("[JIT] fallback: VM path taken for {}", function.signature.name); + if jit_only { + self.leave_root_region(); + self.scope_tracker.pop_scope(); + global_hooks::pop_task_scope(); + return Err(VMError::InvalidInstruction(format!( + "JIT-only enabled and JIT trap occurred for {}", + function.signature.name + ))); + } + } + } else if jit_only { + if let Some(jm_mut) = self.jit_manager.as_mut() { + let _ = jm_mut.maybe_compile(&function.signature.name, function); + } + if self + .jit_manager + .as_ref() + .map(|jm| jm.is_compiled(&function.signature.name)) + .unwrap_or(false) + { + crate::runtime::host_api::set_current_vm(self as *mut _); + let jit_val = if let Some(jm_mut) = self.jit_manager.as_mut() { + jm_mut.execute_compiled( + &function.signature.name, + &function.signature.return_type, + &args_vec, + ) + } else { + None + }; + crate::runtime::host_api::clear_current_vm(); + if let Some(val) = jit_val { + self.leave_root_region(); + self.scope_tracker.pop_scope(); + global_hooks::pop_task_scope(); + return Ok(val); + } else { + self.leave_root_region(); + self.scope_tracker.pop_scope(); + global_hooks::pop_task_scope(); + return Err(VMError::InvalidInstruction(format!( + "JIT-only mode: compiled execution failed for {}", + function.signature.name + ))); + } + } else { + self.leave_root_region(); + self.scope_tracker.pop_scope(); + global_hooks::pop_task_scope(); + return Err(VMError::InvalidInstruction(format!( + "JIT-only mode: function {} not compiled", + function.signature.name + ))); + } + } + } + } + + let mut current_block = function.entry_block; + self.frame.current_block = Some(current_block); + self.frame.pc = 0; + let mut should_return: Option = None; + let mut next_block: Option = None; + + loop { + if let Some(block) = function.blocks.get(¤t_block) { + for instruction in &block.instructions { + match self.execute_instruction(instruction)? { + ControlFlow::Continue => continue, + ControlFlow::Jump(target) => { + next_block = Some(target); + break; + } + ControlFlow::Return(value) => { + should_return = Some(value); + break; + } + } + } + } else { + return Err(VMError::InvalidBasicBlock(format!( + "Basic block {:?} not found", + current_block + ))); + } + + if let Some(return_value) = should_return { + self.scope_tracker.pop_scope(); + global_hooks::pop_task_scope(); + return Ok(return_value); + } else if let Some(target) = next_block { + control_flow::record_transition( + &mut self.previous_block, + &mut self.loop_executor, + current_block, + target, + ) + .ok(); + current_block = target; + } else { + self.scope_tracker.pop_scope(); + global_hooks::pop_task_scope(); + return Ok(VMValue::Void); + } + } + } + + /// Execute a single instruction + pub(super) fn execute_instruction(&mut self, instruction: &MirInstruction) -> Result { + let debug_global = std::env::var("NYASH_VM_DEBUG").ok().as_deref() == Some("1"); + let debug_exec = debug_global || std::env::var("NYASH_VM_DEBUG_EXEC").ok().as_deref() == Some("1"); + if debug_exec { eprintln!("[VM] execute_instruction: {:?}", instruction); } + self.record_instruction(instruction); + super::dispatch::execute_instruction(self, instruction, debug_global) + } +} diff --git a/src/backend/vm_gc.rs b/src/backend/vm_gc.rs new file mode 100644 index 00000000..9043dd7a --- /dev/null +++ b/src/backend/vm_gc.rs @@ -0,0 +1,99 @@ +/*! + * VM GC Roots & Diagnostics (extracted from vm.rs) + * + * - Root region helpers: enter_root_region / pin_roots / leave_root_region + * - Site info for GC logs + * - Debug prints for roots snapshot and shallow reachability + */ + +use super::vm::{VM, VMError, VMValue}; + +impl VM { + /// Enter a GC root region and return a guard that leaves on drop + pub(super) fn enter_root_region(&mut self) { + self.scope_tracker.enter_root_region(); + } + + /// Pin a slice of VMValue as roots in the current region + pub(super) fn pin_roots<'a>(&mut self, values: impl IntoIterator) { + for v in values { + self.scope_tracker.pin_root(v); + } + } + + /// Leave current GC root region + pub(super) fn leave_root_region(&mut self) { self.scope_tracker.leave_root_region(); } + + /// Site info for GC logs: (func, bb, pc) + pub(super) fn gc_site_info(&self) -> (String, i64, i64) { + let func = self.current_function.as_deref().unwrap_or("").to_string(); + let bb = self.frame.current_block.map(|b| b.0 as i64).unwrap_or(-1); + let pc = self.frame.pc as i64; + (func, bb, pc) + } + + /// Print a simple breakdown of root VMValue kinds and top BoxRef types + pub(super) fn gc_print_roots_breakdown(&self) { + use std::collections::HashMap; + let roots = self.scope_tracker.roots_snapshot(); + let mut kinds: HashMap<&'static str, u64> = HashMap::new(); + let mut box_types: HashMap = HashMap::new(); + for v in &roots { + match v { + VMValue::Integer(_) => *kinds.entry("Integer").or_insert(0) += 1, + VMValue::Float(_) => *kinds.entry("Float").or_insert(0) += 1, + VMValue::Bool(_) => *kinds.entry("Bool").or_insert(0) += 1, + VMValue::String(_) => *kinds.entry("String").or_insert(0) += 1, + VMValue::Future(_) => *kinds.entry("Future").or_insert(0) += 1, + VMValue::Void => *kinds.entry("Void").or_insert(0) += 1, + VMValue::BoxRef(b) => { + let tn = b.type_name().to_string(); + *box_types.entry(tn).or_insert(0) += 1; + } + } + } + eprintln!("[GC] roots_breakdown: kinds={:?}", kinds); + let mut top: Vec<(String, u64)> = box_types.into_iter().collect(); + top.sort_by(|a, b| b.1.cmp(&a.1)); + top.truncate(5); + eprintln!("[GC] roots_boxref_top5: {:?}", top); + } + + /// Print shallow reachability from current roots into ArrayBox/MapBox values + pub(super) fn gc_print_reachability_depth2(&self) { + use std::collections::HashMap; + let roots = self.scope_tracker.roots_snapshot(); + let mut child_types: HashMap = HashMap::new(); + let mut child_count = 0u64; + for v in &roots { + if let VMValue::BoxRef(b) = v { + if let Some(arr) = b.as_any().downcast_ref::() { + if let Ok(items) = arr.items.read() { + for item in items.iter() { + let tn = item.type_name().to_string(); + *child_types.entry(tn).or_insert(0) += 1; + child_count += 1; + } + } + } + if let Some(map) = b.as_any().downcast_ref::() { + let vals = map.values(); + if let Some(arr2) = vals.as_any().downcast_ref::() { + if let Ok(items) = arr2.items.read() { + for item in items.iter() { + let tn = item.type_name().to_string(); + *child_types.entry(tn).or_insert(0) += 1; + child_count += 1; + } + } + } + } + } + } + let mut top: Vec<(String, u64)> = child_types.into_iter().collect(); + top.sort_by(|a, b| b.1.cmp(&a.1)); + top.truncate(5); + eprintln!("[GC] depth2_children: total={} top5={:?}", child_count, top); + } +} + diff --git a/src/backend/vm_instructions.rs b/src/backend/vm_instructions.rs deleted file mode 100644 index 3d01ddb5..00000000 --- a/src/backend/vm_instructions.rs +++ /dev/null @@ -1,1927 +0,0 @@ -/*! - * VM Instruction Handlers - * - * Purpose: Implementation of each MIR instruction handler (invoked by vm.rs) - * Responsibilities: load/store/branch/phi/call/array/ref/await/extern_call - * Key APIs: execute_const/execute_binop/execute_unaryop/execute_compare/... - * Typical Callers: VM::execute_instruction (dispatch point) - */ - -use crate::mir::{ConstValue, BinaryOp, CompareOp, UnaryOp, ValueId, BasicBlockId, TypeOpKind, MirType}; -use crate::box_trait::{NyashBox, BoolBox, VoidBox}; -use crate::boxes::ArrayBox; -use std::sync::Arc; -use super::{VM, VMValue, VMError}; -use super::vm::ControlFlow; - -impl VM { - // moved helpers to backend::gc_helpers - /// Build a PIC key from receiver and method identity - fn build_pic_key(&self, recv: &VMValue, method: &str, method_id: Option) -> String { - let label = self.cache_label_for_recv(recv); - let ver = self.cache_version_for_label(&label); - if let Some(mid) = method_id { - format!("v{}:{}#{}", ver, label, mid) - } else { - format!("v{}:{}#{}", ver, label, method) - } - } - - /// Record a PIC hit for the given key (skeleton: increments a counter) - fn pic_record_hit(&mut self, key: &str) { - use std::collections::hash_map::Entry; - match self.boxcall_pic_hits.entry(key.to_string()) { - Entry::Occupied(mut e) => { - let v = e.get_mut(); - *v = v.saturating_add(1); - if std::env::var("NYASH_VM_PIC_DEBUG").ok().as_deref() == Some("1") { - if *v == 8 || *v == 32 { - eprintln!("[PIC] Hot BoxCall site '{}' hits={} (skeleton)", key, v); - } - } - } - Entry::Vacant(v) => { - v.insert(1); - } - } - } - - /// Read current PIC hit count for a key - fn pic_hits(&self, key: &str) -> u32 { - *self.boxcall_pic_hits.get(key).unwrap_or(&0) - } - - /// Build vtable cache key for InstanceBox: TypeName#slot/arity - fn build_vtable_key(&self, class_name: &str, method_id: u16, arity: usize) -> String { - // Use same versioning as PIC for BoxRef - let label = format!("BoxRef:{}", class_name); - let ver = self.cache_version_for_label(&label); - 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 { - 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 - fn cache_label_for_recv(&self, recv: &VMValue) -> String { - match recv { - VMValue::Integer(_) => "Int".to_string(), - VMValue::Float(_) => "Float".to_string(), - VMValue::Bool(_) => "Bool".to_string(), - VMValue::String(_) => "String".to_string(), - VMValue::Future(_) => "Future".to_string(), - VMValue::Void => "Void".to_string(), - VMValue::BoxRef(b) => format!("BoxRef:{}", b.type_name()), - } - } - - /// Get current version for a cache label (default 0) - fn cache_version_for_label(&self, label: &str) -> u32 { - // Prefer global cache versions so that loaders can invalidate across VMs - crate::runtime::cache_versions::get_version(label) - } - - /// Bump version for a label (used to invalidate caches) - #[allow(dead_code)] - pub fn bump_cache_version(&mut self, label: &str) { - crate::runtime::cache_versions::bump_version(label) - } - /// Execute a constant instruction - pub(super) fn execute_const(&mut self, dst: ValueId, value: &ConstValue) -> Result { - let vm_value = VMValue::from(value); - self.set_value(dst, vm_value); - Ok(ControlFlow::Continue) - } - - /// Execute a binary operation instruction - pub(super) fn execute_binop(&mut self, dst: ValueId, op: &BinaryOp, lhs: ValueId, rhs: ValueId) -> Result { - // Short-circuit semantics for logical ops using boolean coercion - match *op { - BinaryOp::And | BinaryOp::Or => { - if std::env::var("NYASH_VM_DEBUG_ANDOR").ok().as_deref() == Some("1") { - eprintln!("[VM] And/Or short-circuit path"); - } - let left = self.get_value(lhs)?; - let right = self.get_value(rhs)?; - let lb = left.as_bool()?; - let rb = right.as_bool()?; - let out = match *op { BinaryOp::And => lb && rb, BinaryOp::Or => lb || rb, _ => unreachable!() }; - self.set_value(dst, VMValue::Bool(out)); - Ok(ControlFlow::Continue) - } - _ => { - let left = self.get_value(lhs)?; - let right = self.get_value(rhs)?; - let result = self.execute_binary_op(op, &left, &right)?; - self.set_value(dst, result); - Ok(ControlFlow::Continue) - } - } - } - - /// Execute a unary operation instruction - pub(super) fn execute_unaryop(&mut self, dst: ValueId, op: &UnaryOp, operand: ValueId) -> Result { - let operand_val = self.get_value(operand)?; - let result = self.execute_unary_op(op, &operand_val)?; - self.set_value(dst, result); - Ok(ControlFlow::Continue) - } - - /// Execute a comparison instruction - pub(super) fn execute_compare(&mut self, dst: ValueId, op: &CompareOp, lhs: ValueId, rhs: ValueId) -> Result { - let debug_cmp = std::env::var("NYASH_VM_DEBUG").ok().as_deref() == Some("1") || - std::env::var("NYASH_VM_DEBUG_CMP").ok().as_deref() == Some("1"); - if debug_cmp { eprintln!("[VM] execute_compare enter op={:?} lhs={:?} rhs={:?}", op, lhs, rhs); } - let mut left = self.get_value(lhs)?; - let mut right = self.get_value(rhs)?; - if debug_cmp { eprintln!("[VM] execute_compare values: left={:?} right={:?}", left, right); } - - // Canonicalize BoxRef(any) → try Integer via downcast/parse (no type_name reliance) - left = match left { - VMValue::BoxRef(b) => { - if let Some(ib) = b.as_any().downcast_ref::() { - VMValue::Integer(ib.value) - } else { - match b.to_string_box().value.trim().parse::() { Ok(n) => VMValue::Integer(n), Err(_) => VMValue::BoxRef(b) } - } - } - other => other, - }; - right = match right { - VMValue::BoxRef(b) => { - if let Some(ib) = b.as_any().downcast_ref::() { - VMValue::Integer(ib.value) - } else { - match b.to_string_box().value.trim().parse::() { Ok(n) => VMValue::Integer(n), Err(_) => VMValue::BoxRef(b) } - } - } - other => other, - }; - - let result = self.execute_compare_op(op, &left, &right)?; - self.set_value(dst, VMValue::Bool(result)); - Ok(ControlFlow::Continue) - } - - /// Execute a print instruction - pub(super) fn execute_print(&self, value: ValueId) -> Result { - let val = self.get_value(value)?; - println!("{}", val.to_string()); - Ok(ControlFlow::Continue) - } - - /// Execute control flow instructions (Jump, Branch, Return) - pub(super) fn execute_jump(&self, target: BasicBlockId) -> Result { - Ok(ControlFlow::Jump(target)) - } - - pub(super) fn execute_branch(&self, condition: ValueId, then_bb: BasicBlockId, else_bb: BasicBlockId) -> Result { - let cond_val = self.get_value(condition)?; - let should_branch = match &cond_val { - VMValue::Bool(b) => *b, - VMValue::Void => false, - VMValue::Integer(i) => *i != 0, - VMValue::BoxRef(b) => { - if let Some(bool_box) = b.as_any().downcast_ref::() { - bool_box.value - } else if b.as_any().downcast_ref::().is_some() { - false - } else { - return Err(VMError::TypeError( - format!("Branch condition must be bool, void, or integer, got BoxRef({})", b.type_name()) - )); - } - } - _ => return Err(VMError::TypeError( - format!("Branch condition must be bool, void, or integer, got {:?}", cond_val) - )), - }; - - Ok(ControlFlow::Jump(if should_branch { then_bb } else { else_bb })) - } - - pub(super) fn execute_return(&self, value: Option) -> Result { - if let Some(val_id) = value { - let return_val = self.get_value(val_id)?; - Ok(ControlFlow::Return(return_val)) - } else { - Ok(ControlFlow::Return(VMValue::Void)) - } - } - - /// Execute TypeOp instruction - pub(super) fn execute_typeop(&mut self, dst: ValueId, op: &TypeOpKind, value: ValueId, ty: &MirType) -> Result { - let val = self.get_value(value)?; - - match op { - TypeOpKind::Check => { - let is_type = match (&val, ty) { - (VMValue::Integer(_), MirType::Integer) => true, - (VMValue::Float(_), MirType::Float) => true, - (VMValue::Bool(_), MirType::Bool) => true, - (VMValue::String(_), MirType::String) => true, - (VMValue::Void, MirType::Void) => true, - (VMValue::BoxRef(arc_box), MirType::Box(box_name)) => { - arc_box.type_name() == box_name - } - _ => false, - }; - self.set_value(dst, VMValue::Bool(is_type)); - Ok(ControlFlow::Continue) - } - TypeOpKind::Cast => { - let result = match (&val, ty) { - // Integer to Float - (VMValue::Integer(i), MirType::Float) => VMValue::Float(*i as f64), - // Float to Integer - (VMValue::Float(f), MirType::Integer) => VMValue::Integer(*f as i64), - // Identity casts - (VMValue::Integer(_), MirType::Integer) => val.clone(), - (VMValue::Float(_), MirType::Float) => val.clone(), - (VMValue::Bool(_), MirType::Bool) => val.clone(), - (VMValue::String(_), MirType::String) => val.clone(), - // BoxRef identity cast - (VMValue::BoxRef(arc_box), MirType::Box(box_name)) if arc_box.type_name() == box_name => { - val.clone() - } - // Invalid cast - _ => { - return Err(VMError::TypeError( - format!("Cannot cast {:?} to {:?}", val, ty) - )); - } - }; - self.set_value(dst, result); - Ok(ControlFlow::Continue) - } - } - } - - /// Execute Phi instruction - pub(super) fn execute_phi(&mut self, dst: ValueId, inputs: &[(BasicBlockId, ValueId)]) -> Result { - // Minimal correct phi: select input based on previous_block via LoopExecutor - let selected = self.loop_execute_phi(dst, inputs)?; - self.set_value(dst, selected); - Ok(ControlFlow::Continue) - } - - /// Execute Load/Store instructions - pub(super) fn execute_load(&mut self, dst: ValueId, ptr: ValueId) -> Result { - let loaded_value = self.get_value(ptr)?; - self.set_value(dst, loaded_value); - Ok(ControlFlow::Continue) - } - - pub(super) fn execute_store(&mut self, value: ValueId, ptr: ValueId) -> Result { - let val = self.get_value(value)?; - self.set_value(ptr, val); - Ok(ControlFlow::Continue) - } - - /// Execute Copy instruction - pub(super) fn execute_copy(&mut self, dst: ValueId, src: ValueId) -> Result { - let value = self.get_value(src)?; - let cloned = match &value { - VMValue::BoxRef(arc_box) => { - // Use clone_or_share to handle cloning properly - let cloned_box = arc_box.clone_or_share(); - VMValue::BoxRef(Arc::from(cloned_box)) - } - other => other.clone(), - }; - self.set_value(dst, cloned); - Ok(ControlFlow::Continue) - } - - /// Execute Call instruction - pub(super) fn execute_call(&mut self, dst: Option, func: ValueId, args: &[ValueId]) -> Result { - // Get the function name from the ValueId - let func_name = match self.get_value(func)? { - VMValue::String(s) => s, - _ => return Err(VMError::TypeError("Function name must be a string".to_string())), - }; - - let arg_values: Vec = args.iter() - .map(|arg| self.get_value(*arg)) - .collect::, _>>()?; - - let result = self.call_function_by_name(&func_name, arg_values)?; - - if let Some(dst_id) = dst { - self.set_value(dst_id, result); - } - - Ok(ControlFlow::Continue) - } - - /// Execute NewBox instruction - pub(super) fn execute_newbox(&mut self, dst: ValueId, box_type: &str, args: &[ValueId]) -> Result { - // Convert args to NyashBox values - let arg_values: Vec> = args.iter() - .map(|arg| { - let val = self.get_value(*arg)?; - Ok(val.to_nyash_box()) - }) - .collect::, VMError>>()?; - - // Create new box using runtime's registry - let new_box = { - let registry = self.runtime.box_registry.lock() - .map_err(|_| VMError::InvalidInstruction("Failed to lock box registry".to_string()))?; - registry.create_box(box_type, &arg_values) - .map_err(|e| VMError::InvalidInstruction(format!("Failed to create {}: {}", box_type, e)))? - }; - - // 80/20: Basic boxes are stored as primitives in VMValue for simpler ops - if box_type == "IntegerBox" { - if let Some(ib) = new_box.as_any().downcast_ref::() { - self.set_value(dst, VMValue::Integer(ib.value)); - return Ok(ControlFlow::Continue); - } - } else if box_type == "BoolBox" { - if let Some(bb) = new_box.as_any().downcast_ref::() { - self.set_value(dst, VMValue::Bool(bb.value)); - return Ok(ControlFlow::Continue); - } - } else if box_type == "StringBox" { - if let Some(sb) = new_box.as_any().downcast_ref::() { - self.set_value(dst, VMValue::String(sb.value.clone())); - return Ok(ControlFlow::Continue); - } - } - - self.set_value(dst, VMValue::BoxRef(Arc::from(new_box))); - Ok(ControlFlow::Continue) - } - - /// Execute ArrayGet instruction - pub(super) fn execute_array_get(&mut self, dst: ValueId, array: ValueId, index: ValueId) -> Result { - let array_val = self.get_value(array)?; - let index_val = self.get_value(index)?; - - if let VMValue::BoxRef(array_box) = &array_val { - if let Some(array) = array_box.as_any().downcast_ref::() { - // ArrayBox expects Box for index - let index_box = index_val.to_nyash_box(); - let result = array.get(index_box); - self.set_value(dst, VMValue::BoxRef(Arc::from(result))); - Ok(ControlFlow::Continue) - } else { - Err(VMError::TypeError("ArrayGet requires an ArrayBox".to_string())) - } - } else { - Err(VMError::TypeError("ArrayGet requires array and integer index".to_string())) - } - } - - /// Execute ArraySet instruction - pub(super) fn execute_array_set(&mut self, array: ValueId, index: ValueId, value: ValueId) -> Result { - let array_val = self.get_value(array)?; - let index_val = self.get_value(index)?; - let value_val = self.get_value(value)?; - - if let VMValue::BoxRef(array_box) = &array_val { - if let Some(array) = array_box.as_any().downcast_ref::() { - // GC write barrier (array contents mutation) - crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "ArraySet"); - // ArrayBox expects Box for index - let index_box = index_val.to_nyash_box(); - let box_value = value_val.to_nyash_box(); - array.set(index_box, box_value); - Ok(ControlFlow::Continue) - } else { - Err(VMError::TypeError("ArraySet requires an ArrayBox".to_string())) - } - } else { - Err(VMError::TypeError("ArraySet requires array and integer index".to_string())) - } - } - - /// Execute RefNew instruction - pub(super) fn execute_ref_new(&mut self, dst: ValueId, box_val: ValueId) -> Result { - // For now, a reference is just the same as the box value - // In a real implementation, this would create a proper reference - let box_value = self.get_value(box_val)?; - self.set_value(dst, box_value); - Ok(ControlFlow::Continue) - } - - /// Execute RefGet instruction - pub(super) fn execute_ref_get(&mut self, dst: ValueId, reference: ValueId, field: &str) -> Result { - let debug_ref = std::env::var("NYASH_VM_DEBUG_REF").ok().as_deref() == Some("1"); - if debug_ref { eprintln!("[VM] RefGet ref={:?} field={}", reference, field); } - // Visibility check (if class known and visibility declared). Skip for internal refs. - let is_internal = self.object_internal.contains(&reference); - if !is_internal { - if let Some(class_name) = self.object_class.get(&reference) { - if let Ok(decls) = self.runtime.box_declarations.read() { - if let Some(decl) = decls.get(class_name) { - let has_vis = !decl.public_fields.is_empty() || !decl.private_fields.is_empty(); - if has_vis && !decl.public_fields.iter().any(|f| f == field) { - return Err(VMError::TypeError(format!("Field '{}' is private in {}", field, class_name))); - } - } - } - } - } - // Get field value from object - let mut field_value = if let Some(fields) = self.object_fields.get(&reference) { - if let Some(value) = fields.get(field) { - if debug_ref { eprintln!("[VM] RefGet hit: {} -> {:?}", field, value); } - value.clone() - } else { - // Field not set yet, return default - if debug_ref { eprintln!("[VM] RefGet miss: {} -> default 0", field); } - VMValue::Integer(0) - } - } else { - // Object has no fields yet, return default - if debug_ref { eprintln!("[VM] RefGet no fields: -> default 0"); } - VMValue::Integer(0) - }; - - // Special binding for environment-like fields: map 'console' to plugin ConsoleBox - if matches!(field_value, VMValue::Integer(0)) && field == "console" { - if debug_ref { eprintln!("[VM] RefGet special binding: console -> Plugin ConsoleBox"); } - let host = crate::runtime::get_global_plugin_host(); - let host = host.read().unwrap(); - if let Ok(pbox) = host.create_box("ConsoleBox", &[]) { - field_value = VMValue::from_nyash_box(pbox); - // Cache on the object so subsequent ref_get uses the same instance - if !self.object_fields.contains_key(&reference) { - self.object_fields.insert(reference, std::collections::HashMap::new()); - } - if let Some(fields) = self.object_fields.get_mut(&reference) { - fields.insert(field.to_string(), field_value.clone()); - } - } - } - - self.set_value(dst, field_value); - Ok(ControlFlow::Continue) - } - - /// Execute RefSet instruction - pub(super) fn execute_ref_set(&mut self, reference: ValueId, field: &str, value: ValueId) -> Result { - let debug_ref = std::env::var("NYASH_VM_DEBUG_REF").ok().as_deref() == Some("1"); - // Get the value to set - let new_value = self.get_value(value)?; - if debug_ref { eprintln!("[VM] RefSet ref={:?} field={} value={:?}", reference, field, new_value); } - // Visibility check (Skip for internal refs; otherwise enforce public) - let is_internal = self.object_internal.contains(&reference); - if !is_internal { - if let Some(class_name) = self.object_class.get(&reference) { - if let Ok(decls) = self.runtime.box_declarations.read() { - if let Some(decl) = decls.get(class_name) { - let has_vis = !decl.public_fields.is_empty() || !decl.private_fields.is_empty(); - if has_vis && !decl.public_fields.iter().any(|f| f == field) { - return Err(VMError::TypeError(format!("Field '{}' is private in {}", field, class_name))); - } - } - } - } - } - - // Ensure object has field storage - if !self.object_fields.contains_key(&reference) { - self.object_fields.insert(reference, std::collections::HashMap::new()); - } - - // Set the field (with GC write barrier) - crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "RefSet"); - if let Some(fields) = self.object_fields.get_mut(&reference) { - fields.insert(field.to_string(), new_value); - if debug_ref { eprintln!("[VM] RefSet stored: {}", field); } - } - - Ok(ControlFlow::Continue) - } - - /// Execute WeakNew instruction - pub(super) fn execute_weak_new(&mut self, dst: ValueId, box_val: ValueId) -> Result { - // For now, a weak reference is just a copy of the value - // In a real implementation, this would create a proper weak reference - let box_value = self.get_value(box_val)?; - self.set_value(dst, box_value); - Ok(ControlFlow::Continue) - } - - /// Execute WeakLoad instruction - pub(super) fn execute_weak_load(&mut self, dst: ValueId, weak_ref: ValueId) -> Result { - // For now, loading from weak ref is the same as getting the value - // In a real implementation, this would check if the weak ref is still valid - let weak_value = self.get_value(weak_ref)?; - self.set_value(dst, weak_value); - Ok(ControlFlow::Continue) - } - - /// Execute BarrierRead instruction - pub(super) fn execute_barrier_read(&mut self, dst: ValueId, value: ValueId) -> Result { - // Memory barrier read is currently a simple value copy - // In a real implementation, this would ensure memory ordering - let val = self.get_value(value)?; - self.set_value(dst, val); - Ok(ControlFlow::Continue) - } - - /// Execute BarrierWrite instruction - pub(super) fn execute_barrier_write(&mut self, _value: ValueId) -> Result { - // Memory barrier write is a no-op for now - // In a real implementation, this would ensure memory ordering - Ok(ControlFlow::Continue) - } - - /// Execute Throw instruction - pub(super) fn execute_throw(&mut self, exception: ValueId) -> Result { - let exc_value = self.get_value(exception)?; - Err(VMError::InvalidInstruction(format!("Exception thrown: {:?}", exc_value))) - } - - /// Execute Catch instruction - pub(super) fn execute_catch(&mut self, exception_value: ValueId) -> Result { - // For now, catch is a no-op - // In a real implementation, this would handle exception catching - self.set_value(exception_value, VMValue::Void); - Ok(ControlFlow::Continue) - } - - /// Execute Await instruction - pub(super) fn execute_await(&mut self, dst: ValueId, future: ValueId) -> Result { - let future_val = self.get_value(future)?; - - if let VMValue::Future(ref future_box) = future_val { - // Cooperative wait with scheduler polling and timeout to avoid deadlocks - let max_ms: u64 = crate::config::env::await_max_ms(); - let start = std::time::Instant::now(); - let mut spins = 0usize; - while !future_box.ready() { - // Poll GC/scheduler similar to Safepoint - self.runtime.gc.safepoint(); - if let Some(s) = &self.runtime.scheduler { s.poll(); } - std::thread::yield_now(); - spins += 1; - if spins % 1024 == 0 { std::thread::sleep(std::time::Duration::from_millis(1)); } - if start.elapsed() >= std::time::Duration::from_millis(max_ms) { - // Timeout -> Result.Err("Timeout") - let err = Box::new(crate::box_trait::StringBox::new("Timeout")); - let rb = crate::boxes::result::NyashResultBox::new_err(err); - let vm_value = VMValue::from_nyash_box(Box::new(rb)); - self.set_value(dst, vm_value); - return Ok(ControlFlow::Continue); - } - } - // Ready: get value and wrap into Result.Ok - let result = future_box.get(); - let ok = crate::boxes::result::NyashResultBox::new_ok(result); - let vm_value = VMValue::from_nyash_box(Box::new(ok)); - self.set_value(dst, vm_value); - Ok(ControlFlow::Continue) - } else { - Err(VMError::TypeError(format!("Expected Future, got {:?}", future_val))) - } - } - - /// Execute ExternCall instruction - pub(super) fn execute_extern_call(&mut self, dst: Option, iface_name: &str, method_name: &str, args: &[ValueId]) -> Result { - // Optional routing to name→slot handlers for stability and diagnostics - if crate::config::env::extern_route_slots() { - if let Some(slot) = crate::runtime::extern_registry::resolve_slot(iface_name, method_name) { - // Decode args to VMValue as needed by handlers below - let vm_args: Vec = args.iter().filter_map(|a| self.get_value(*a).ok()).collect(); - match (iface_name, method_name, slot) { - ("env.console", m @ ("log" | "warn" | "error" | "println"), 10) => { - if let Some(a0) = vm_args.get(0) { - match m { - "warn" => eprintln!("[warn] {}", a0.to_string()), - "error" => eprintln!("[error] {}", a0.to_string()), - _ => println!("{}", a0.to_string()), - } - } - if let Some(d) = dst { self.set_value(d, VMValue::Void); } - return Ok(ControlFlow::Continue); - } - ("env.debug", "trace", 11) => { - if let Some(a0) = vm_args.get(0) { eprintln!("[trace] {}", a0.to_string()); } - if let Some(d) = dst { self.set_value(d, VMValue::Void); } - return Ok(ControlFlow::Continue); - } - ("env.runtime", "checkpoint", 12) => { - if crate::config::env::runtime_checkpoint_trace() { - let (func, bb, pc) = self.gc_site_info(); - eprintln!("[rt] checkpoint @{} bb={} pc={}", func, bb, pc); - } - self.runtime.gc.safepoint(); - if let Some(s) = &self.runtime.scheduler { s.poll(); } - if let Some(d) = dst { self.set_value(d, VMValue::Void); } - return Ok(ControlFlow::Continue); - } - ("env.future", "new", 20) | ("env.future", "birth", 20) => { - // Create a new Future and optionally set initial value from arg0 - let fut = crate::boxes::future::FutureBox::new(); - if let Some(a0) = vm_args.get(0) { fut.set_result(a0.to_nyash_box()); } - if let Some(d) = dst { self.set_value(d, VMValue::Future(fut)); } - return Ok(ControlFlow::Continue); - } - ("env.future", "set", 21) => { - // set(future, value) - if vm_args.len() >= 2 { - if let VMValue::Future(f) = &vm_args[0] { f.set_result(vm_args[1].to_nyash_box()); } - } - if let Some(d) = dst { self.set_value(d, VMValue::Void); } - return Ok(ControlFlow::Continue); - } - ("env.future", "await", 22) => { - if let Some(VMValue::Future(fb)) = vm_args.get(0) { - // Simple blocking await using existing helper pattern - let start = std::time::Instant::now(); - let max_ms = crate::config::env::await_max_ms(); - while !fb.ready() { - std::thread::yield_now(); - if start.elapsed() >= std::time::Duration::from_millis(max_ms) { break; } - } - let result = if fb.ready() { fb.get() } else { Box::new(crate::box_trait::StringBox::new("Timeout")) }; - let ok = crate::boxes::result::NyashResultBox::new_ok(result); - if let Some(d) = dst { self.set_value(d, VMValue::from_nyash_box(Box::new(ok))); } - } else if let Some(d) = dst { self.set_value(d, VMValue::Void); } - return Ok(ControlFlow::Continue); - } - ("env.task", "cancelCurrent", 30) => { - // No-op scaffold - if let Some(d) = dst { self.set_value(d, VMValue::Void); } - return Ok(ControlFlow::Continue); - } - ("env.task", "currentToken", 31) => { - // Minimal token placeholder (0) - if let Some(d) = dst { self.set_value(d, VMValue::Integer(0)); } - return Ok(ControlFlow::Continue); - } - ("env.task", "yieldNow", 32) => { - std::thread::yield_now(); - if let Some(d) = dst { self.set_value(d, VMValue::Void); } - return Ok(ControlFlow::Continue); - } - ("env.task", "sleepMs", 33) => { - let ms = vm_args.get(0).map(|v| match v { VMValue::Integer(i) => *i, _ => 0 }).unwrap_or(0); - if ms > 0 { std::thread::sleep(std::time::Duration::from_millis(ms as u64)); } - if let Some(d) = dst { self.set_value(d, VMValue::Void); } - return Ok(ControlFlow::Continue); - } - _ => { /* fallthrough to host */ } - } - } - } - - // Evaluate arguments as NyashBox for loader - let mut nyash_args: Vec> = Vec::new(); - for arg_id in args { - let arg_value = self.get_value(*arg_id)?; - nyash_args.push(arg_value.to_nyash_box()); - } - - // Optional trace - if crate::config::env::extern_trace() { - if let Some(slot) = crate::runtime::extern_registry::resolve_slot(iface_name, method_name) { - eprintln!("[EXT] call {}.{} slot={} argc={}", iface_name, method_name, slot, nyash_args.len()); - } else { - eprintln!("[EXT] call {}.{} argc={}", iface_name, method_name, nyash_args.len()); - } - } - // Route through unified plugin host (delegates to v2, handles env.* stubs) - let host = crate::runtime::get_global_plugin_host(); - let host = host.read().map_err(|_| VMError::InvalidInstruction("Plugin host lock poisoned".into()))?; - match host.extern_call(iface_name, method_name, &nyash_args) { - Ok(Some(result_box)) => { - if let Some(dst_id) = dst { - self.set_value(dst_id, VMValue::from_nyash_box(result_box)); - } - } - Ok(None) => { - if let Some(dst_id) = dst { - self.set_value(dst_id, VMValue::Void); - } - } - Err(_) => { - let strict = crate::config::env::extern_strict() || crate::config::env::abi_strict(); - // Build suggestion list - let mut msg = String::new(); - if strict { msg.push_str("ExternCall STRICT: unregistered or unsupported call "); } else { msg.push_str("ExternCall failed: "); } - msg.push_str(&format!("{}.{}", iface_name, method_name)); - // Arity check when known - if let Err(detail) = crate::runtime::extern_registry::check_arity(iface_name, method_name, nyash_args.len()) { - msg.push_str(&format!(" ({})", detail)); - } - if let Some(spec) = crate::runtime::extern_registry::resolve(iface_name, method_name) { - msg.push_str(&format!(" (expected arity {}..{})", spec.min_arity, spec.max_arity)); - } else { - let known = crate::runtime::extern_registry::known_for_iface(iface_name); - if !known.is_empty() { - msg.push_str(&format!("; known methods: {}", known.join(", "))); - } else { - let ifaces = crate::runtime::extern_registry::all_ifaces(); - msg.push_str(&format!("; known interfaces: {}", ifaces.join(", "))); - } - } - return Err(VMError::InvalidInstruction(msg)); - } - } - Ok(ControlFlow::Continue) - } - - /// Execute BoxCall instruction - pub(super) fn execute_boxcall(&mut self, dst: Option, box_val: ValueId, method: &str, method_id: Option, args: &[ValueId]) -> Result { - let recv = self.get_value(box_val)?; - - // Debug logging if enabled - let debug_boxcall = std::env::var("NYASH_VM_DEBUG_BOXCALL").is_ok(); - - // Fast-path: ConsoleBox.readLine — provide safe stdin fallback with EOF→Void - if let VMValue::BoxRef(arc_box) = &recv { - if let Some(p) = arc_box.as_any().downcast_ref::() { - if p.box_type == "ConsoleBox" && method == "readLine" { - use std::io::Read; - let mut s = String::new(); - let mut stdin = std::io::stdin(); - // Read bytes until '\n' or EOF - let mut buf = [0u8; 1]; - loop { - match stdin.read(&mut buf) { - Ok(0) => { // EOF → return NullBox - if let Some(dst_id) = dst { - let nb = crate::boxes::null_box::NullBox::new(); - self.set_value(dst_id, VMValue::from_nyash_box(Box::new(nb))); - } - return Ok(ControlFlow::Continue); - } - Ok(_) => { - let ch = buf[0] as char; - if ch == '\n' { break; } - s.push(ch); - if s.len() > 1_000_000 { break; } - } - Err(_) => { // On error, return NullBox - if let Some(dst_id) = dst { - let nb = crate::boxes::null_box::NullBox::new(); - self.set_value(dst_id, VMValue::from_nyash_box(Box::new(nb))); - } - return Ok(ControlFlow::Continue); - } - } - } - if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::String(s)); } - return Ok(ControlFlow::Continue); - } - } - } - - // Phase 12 Tier-0: vtable優先経路(雛形) - if crate::config::env::abi_vtable() { - if let Some(res) = self.try_boxcall_vtable_stub(dst, &recv, method, method_id, args) { return res; } - } - - // Record PIC hit (per-receiver-type × method) - let pic_key = self.build_pic_key(&recv, method, method_id); - self.pic_record_hit(&pic_key); - - // Explicit fast-path: ArrayBox get/set by (type, slot) or method name - if let VMValue::BoxRef(arc_box) = &recv { - if arc_box.as_any().downcast_ref::().is_some() { - let get_slot = crate::mir::slot_registry::resolve_slot_by_type_name("ArrayBox", "get"); - let set_slot = crate::mir::slot_registry::resolve_slot_by_type_name("ArrayBox", "set"); - let is_get = (method_id.is_some() && method_id == get_slot) || method == "get"; - let is_set = (method_id.is_some() && method_id == set_slot) || method == "set"; - if is_get && args.len() >= 1 { - // Convert index - let idx_val = self.get_value(args[0])?; - let idx_box = idx_val.to_nyash_box(); - // Call builtin directly - let arr = arc_box.as_any().downcast_ref::().unwrap(); - let out = arr.get(idx_box); - if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } - return Ok(ControlFlow::Continue); - } else if is_set && args.len() >= 2 { - // Barrier for mutation - crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "BoxCall.fastpath.Array.set"); - let idx_val = self.get_value(args[0])?; - let val_val = self.get_value(args[1])?; - let idx_box = idx_val.to_nyash_box(); - let val_box = val_val.to_nyash_box(); - let arr = arc_box.as_any().downcast_ref::().unwrap(); - let out = arr.set(idx_box, val_box); - if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } - return Ok(ControlFlow::Continue); - } - } - // Explicit fast-path: InstanceBox getField/setField by name - if let Some(inst) = arc_box.as_any().downcast_ref::() { - let is_getf = method == "getField"; - let is_setf = method == "setField"; - if (is_getf && args.len() >= 1) || (is_setf && args.len() >= 2) { - // Extract field name - let name_val = self.get_value(args[0])?; - let field_name = match &name_val { - VMValue::String(s) => s.clone(), - _ => name_val.to_string(), - }; - if is_getf { - let out_opt = inst.get_field_unified(&field_name); - let out_vm = match out_opt { - Some(nv) => match nv { - crate::value::NyashValue::Integer(i) => VMValue::Integer(i), - crate::value::NyashValue::Float(f) => VMValue::Float(f), - crate::value::NyashValue::Bool(b) => VMValue::Bool(b), - crate::value::NyashValue::String(s) => VMValue::String(s), - crate::value::NyashValue::Void | crate::value::NyashValue::Null => VMValue::Void, - crate::value::NyashValue::Box(b) => { - if let Ok(g) = b.lock() { VMValue::from_nyash_box(g.share_box()) } else { VMValue::Void } - } - _ => VMValue::Void, - }, - None => VMValue::Void, - }; - if let Some(dst_id) = dst { self.set_value(dst_id, out_vm); } - return Ok(ControlFlow::Continue); - } else { - // setField - crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "BoxCall.fastpath.Instance.setField"); - let val_vm = self.get_value(args[1])?; - let nv_opt = match val_vm.clone() { - VMValue::Integer(i) => Some(crate::value::NyashValue::Integer(i)), - VMValue::Float(f) => Some(crate::value::NyashValue::Float(f)), - VMValue::Bool(b) => Some(crate::value::NyashValue::Bool(b)), - VMValue::String(s) => Some(crate::value::NyashValue::String(s)), - VMValue::Void => Some(crate::value::NyashValue::Void), - _ => None, - }; - if let Some(nv) = nv_opt { - let _ = inst.set_field_unified(field_name, nv); - if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::Void); } - return Ok(ControlFlow::Continue); - } - } - } - } - } - - // 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) { - // Determine class label for TypeMeta - let mut class_label: Option = 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::() { - class_label = Some(inst.class_name.clone()); - is_instance = true; - } else if let Some(p) = arc_box.as_any().downcast_ref::() { - 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); - } - // 実行: 受け取り→VM引数並べ→関数呼出 - 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)?; - - // 10_e: Thunk経路でもPIC/VTableを直結更新するにゃ - // - Poly-PIC: 直ちに記録(最大4件ローカルLRU) - self.record_poly_pic(&pic_key, &recv, &func_name); - // - HotならMono-PICにも格納(しきい値=8) - let threshold = crate::config::env::vm_pic_threshold(); - if self.pic_hits(&pic_key) >= threshold { - self.boxcall_pic_funcname.insert(pic_key.clone(), func_name.clone()); - } - // - InstanceBoxならVTableキーにも登録(method_id/arity直結) - if is_instance { - let vkey = self.build_vtable_key(&label, mid, args.len()); - self.boxcall_vtable_funcname.entry(vkey).or_insert(func_name.clone()); - } - - 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::() { - // Root region for plugin call (pin recv + args) - self.enter_root_region(); - // Convert args prepared earlier (we need NyashBox args) - let nyash_args: Vec> = args.iter() - .map(|arg| { - let val = self.get_value(*arg)?; - Ok(val.to_nyash_box()) - }) - .collect::, VMError>>()?; - // Pin roots: receiver and VMValue args - self.pin_roots(std::iter::once(&recv)); - let pinned_args: Vec = args.iter().filter_map(|a| self.get_value(*a).ok()).collect(); - self.pin_roots(pinned_args.iter()); - // 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::runtime::plugin_ffi_common::encode::string(&mut tlv, &s.value); - } else if let Some(i) = a.as_any().downcast_ref::() { - 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_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(); - // Bind current VM for potential reverse host calls - crate::runtime::host_api::set_current_vm(self as *mut _); - 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, - ) - }; - crate::runtime::host_api::clear_current_vm(); - if code == 0 { - let vm_out = if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) { - match tag { - 6 | 7 => VMValue::String(crate::runtime::plugin_ffi_common::decode::string(payload)), - 2 => crate::runtime::plugin_ffi_common::decode::i32(payload).map(|v| VMValue::Integer(v as i64)).unwrap_or(VMValue::Void), - 9 => { - if let Some(h) = crate::runtime::plugin_ffi_common::decode::u64(payload) { - if let Some(arc) = crate::runtime::host_handles::get(h) { VMValue::BoxRef(arc) } else { VMValue::Void } - } else { VMValue::Void } - } - _ => VMValue::Void, - } - } else { VMValue::Void }; - if let Some(dst_id) = dst { self.set_value(dst_id, vm_out); } - // Leave root region - self.leave_root_region(); - return Ok(ControlFlow::Continue); - } - // Leave root region also on error path - self.leave_root_region(); - } - } - } - } - crate::runtime::type_meta::ThunkTarget::BuiltinCall { method: ref m } => { - if is_builtin { - // Prepare NyashBox args and call by name - let nyash_args: Vec> = args.iter() - .map(|arg| { - let val = self.get_value(*arg)?; - Ok(val.to_nyash_box()) - }) - .collect::, VMError>>()?; - // Write barrier for known mutating builtins or setField - if crate::backend::gc_helpers::is_mutating_builtin_call(&recv, m) { - crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "BoxCall.builtin"); - } else if m == "setField" { - crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "BoxCall.setField"); - } - let cloned_box = arc_box.share_box(); - self.boxcall_hits_generic = self.boxcall_hits_generic.saturating_add(1); - 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::().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::().is_some() { - if let Some(func_name) = self.try_poly_pic(&pic_key, &recv) { - if crate::config::env::vm_pic_trace() { eprintln!("[PIC] poly hit {}", pic_key); } - self.boxcall_hits_poly_pic = self.boxcall_hits_poly_pic.saturating_add(1); - 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); - } - // Fallback to Mono-PIC (legacy) if present - if let Some(func_name) = self.boxcall_pic_funcname.get(&pic_key).cloned() { - if crate::config::env::vm_pic_trace() { eprintln!("[PIC] mono hit {}", pic_key); } - self.boxcall_hits_mono_pic = self.boxcall_hits_mono_pic.saturating_add(1); - // Build VM args: receiver first, then original args - 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); - } - } - } - - // Fast path: universal method slots via method_id (0..3) - if let Some(mid) = method_id { - if let Some(fast_res) = self.try_fast_universal(mid, &recv, args)? { - if let Some(dst_id) = dst { self.set_value(dst_id, fast_res); } - return Ok(ControlFlow::Continue); - } - } - - // Convert args to NyashBox - let nyash_args: Vec> = args.iter() - .map(|arg| { - let val = self.get_value(*arg)?; - Ok(val.to_nyash_box()) - }) - .collect::, VMError>>()?; - - // PluginBoxV2 fast-path via method_id -> direct invoke_fn (skip name->id resolution) - if let (Some(mid), VMValue::BoxRef(arc_box)) = (method_id, &recv) { - if let Some(p) = arc_box.as_any().downcast_ref::() { - // Root region for plugin call - self.enter_root_region(); - // Encode TLV args (support: int, string, plugin handle) - 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 { - // Prefer BufferBox → bytes - if let Some(buf) = a.as_any().downcast_ref::() { - 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::runtime::plugin_ffi_common::encode::string(&mut tlv, &s.value); - } else if let Some(i) = a.as_any().downcast_ref::() { - // 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::runtime::plugin_ffi_common::encode::bool(&mut tlv, b.value); - } else if let Some(f) = a.as_any().downcast_ref::() { - crate::runtime::plugin_ffi_common::encode::f64(&mut tlv, f.value); - } else if let Some(arr) = a.as_any().downcast_ref::() { - // 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::() { - 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_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, - mid as u32, - p.inner.instance_id, - tlv.as_ptr(), - tlv.len(), - out.as_mut_ptr(), - &mut out_len, - ) - }; - 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 - let vm_out = if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) { - match tag { - 1 => crate::runtime::plugin_ffi_common::decode::bool(payload).map(VMValue::Bool).unwrap_or(VMValue::Void), - 2 => crate::runtime::plugin_ffi_common::decode::i32(payload).map(|v| VMValue::Integer(v as i64)).unwrap_or(VMValue::Void), - 5 => crate::runtime::plugin_ffi_common::decode::f64(payload).map(VMValue::Float).unwrap_or(VMValue::Void), - 6 => VMValue::String(crate::runtime::plugin_ffi_common::decode::string(payload)), - _ => VMValue::Void, - } - } else { - VMValue::Void - }; - if let Some(dst_id) = dst { self.set_value(dst_id, vm_out); } - self.leave_root_region(); - return Ok(ControlFlow::Continue); - } - // Leave root region also on non-zero code path - self.leave_root_region(); - } - } - } - - if debug_boxcall { - self.debug_log_boxcall(&recv, method, &nyash_args, "START", None); - } - - // Call the method based on receiver type - let result = match &recv { - VMValue::BoxRef(arc_box) => { - // If this is a user InstanceBox, redirect to lowered function: Class.method/arity - if let Some(inst) = arc_box.as_any().downcast_ref::() { - let func_name = format!("{}.{}{}", inst.class_name, method, format!("/{}", args.len())); - // Populate TypeMeta thunk table and legacy vtable cache if method_id is known - 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()); - self.boxcall_vtable_funcname.entry(vkey).or_insert(func_name.clone()); - } - // 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 - let threshold = crate::config::env::vm_pic_threshold(); - if self.pic_hits(&pic_key) >= threshold { - self.boxcall_pic_funcname.insert(pic_key.clone(), func_name.clone()); - } - if debug_boxcall { eprintln!("[BoxCall] InstanceBox -> call {}", func_name); } - // Build VMValue args: receiver first, then original VMValue args - 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)?; - return { - if let Some(dst_id) = dst { self.set_value(dst_id, res); } - Ok(ControlFlow::Continue) - }; - } - // Otherwise, direct box method call - 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()); - } - // Write barrier for known mutating builtins or setField - if crate::backend::gc_helpers::is_mutating_builtin_call(&recv, method) { - crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "BoxCall"); - } else if method == "setField" { - crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "BoxCall.setField"); - } - let cloned_box = arc_box.share_box(); - self.call_box_method(cloned_box, method, nyash_args)? - } - _ => { - // Convert primitive to box and call - if debug_boxcall { eprintln!("[BoxCall] Converting primitive to box for method '{}'", method); } - - let box_value = recv.to_nyash_box(); - self.call_box_method(box_value, method, nyash_args)? - } - }; - - // Convert result back to VMValue - let result_val = VMValue::from_nyash_box(result); - - if debug_boxcall { - self.debug_log_boxcall(&recv, method, &[], "END", Some(&result_val)); - } - - if let Some(dst_id) = dst { - self.set_value(dst_id, result_val); - } - - Ok(ControlFlow::Continue) - } - - /// Phase 12 Tier-0: vtable優先経路の雛形(常に未処理)。 - /// 目的: 将来のTypeBox ABI配線ポイントを先置きしても既存挙動を変えないこと。 - fn try_boxcall_vtable_stub(&mut self, _dst: Option, _recv: &VMValue, _method: &str, _method_id: Option, _args: &[ValueId]) -> Option> { - if crate::config::env::vm_vt_trace() { - match _recv { - VMValue::BoxRef(b) => eprintln!("[VT] probe recv_ty={} method={} argc={}", b.type_name(), _method, _args.len()), - other => eprintln!("[VT] probe recv_prim={:?} method={} argc={}", other, _method, _args.len()), - } - } - // Tier-1 PoC: Array/Map/String の get/set/len/size/has を vtable 経路で処理(read-onlyまたは明示barrier不要) - if let VMValue::BoxRef(b) = _recv { - // 型解決(雛形レジストリ使用) - let ty_name = b.type_name(); - // PluginBoxV2 は実型名でレジストリ解決する - let ty_name_for_reg: std::borrow::Cow<'_, str> = if let Some(p) = b.as_any().downcast_ref::() { - std::borrow::Cow::Owned(p.box_type.clone()) - } else { - std::borrow::Cow::Borrowed(ty_name) - }; - if let Some(_tb) = crate::runtime::type_registry::resolve_typebox_by_name(&ty_name_for_reg) { - // name+arity→slot 解決 - let slot = crate::runtime::type_registry::resolve_slot_by_name(&ty_name_for_reg, _method, _args.len()); - // PluginBoxV2: vtable経由で host.invoke_instance_method を使用(内蔵廃止と整合) - if let Some(p) = b.as_any().downcast_ref::() { - if crate::config::env::vm_vt_trace() { eprintln!("[VT] plugin recv ty={} method={} arity={}", ty_name, _method, _args.len()); } - // 事前に引数を NyashBox に変換 - let mut nyash_args: Vec> = Vec::with_capacity(_args.len()); - for aid in _args.iter() { - if let Ok(v) = self.get_value(*aid) { nyash_args.push(v.to_nyash_box()); } else { nyash_args.push(Box::new(crate::box_trait::VoidBox::new())); } - } - // Instance/Map/Array/String などに対して型名とスロットで分岐(最小セット) - match ty_name { - "MapBox" => { - match slot { - Some(200) | Some(201) => { // size/len - let host = crate::runtime::get_global_plugin_host(); - let ro = host.read().unwrap(); - if let Ok(val_opt) = ro.invoke_instance_method("MapBox", _method, p.inner.instance_id, &[]) { - if let Some(out) = val_opt { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } } - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - return Some(Ok(ControlFlow::Continue)); - } - } - Some(202) | Some(203) | Some(204) => { // has/get/set - if matches!(slot, Some(204)) { - crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "VTable.Plugin.Map.set"); - } - let host = crate::runtime::get_global_plugin_host(); - let ro = host.read().unwrap(); - // Route string-key variants to getS/hasS when applicable - let mut method_eff = _method; - if (matches!(slot, Some(202)) && _args.len() >= 1) || (matches!(slot, Some(203)) && _args.len() >= 1) { - if let Ok(a0v) = self.get_value(_args[0]) { - if matches!(a0v, VMValue::String(_)) { method_eff = if matches!(slot, Some(203)) { "getS" } else { "hasS" }; } - } - } - if let Ok(val_opt) = ro.invoke_instance_method("MapBox", method_eff, p.inner.instance_id, &nyash_args) { - if let Some(out) = val_opt { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } } - else if _dst.is_some() { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::Void); } } - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - return Some(Ok(ControlFlow::Continue)); - } - } - _ => {} - } - } - "ArrayBox" => { - match slot { - Some(100) | Some(101) | Some(102) => { - if matches!(slot, Some(101)) { - crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "VTable.Plugin.Array.set"); - } - let host = crate::runtime::get_global_plugin_host(); - let ro = host.read().unwrap(); - if let Ok(val_opt) = ro.invoke_instance_method("ArrayBox", _method, p.inner.instance_id, &nyash_args) { - if let Some(out) = val_opt { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } } - else if _dst.is_some() { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::Void); } } - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - return Some(Ok(ControlFlow::Continue)); - } - } - _ => {} - } - } - "StringBox" => { - if matches!(slot, Some(300)) { - let host = crate::runtime::get_global_plugin_host(); - let ro = host.read().unwrap(); - if let Ok(val_opt) = ro.invoke_instance_method("StringBox", _method, p.inner.instance_id, &[]) { - if let Some(out) = val_opt { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } } - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - return Some(Ok(ControlFlow::Continue)); - } - } - } - _ => {} - } - } - // InstanceBox: getField/setField/has/size - if let Some(inst) = b.as_any().downcast_ref::() { - match slot { - Some(1) => { // getField - if let Ok(a0) = self.get_value(_args[0]) { - let fname = match a0 { VMValue::String(s) => s, v => v.to_string() }; - let out_vm = match inst.get_field_unified(&fname) { - Some(nv) => match nv { - crate::value::NyashValue::Integer(i) => VMValue::Integer(i), - crate::value::NyashValue::Float(f) => VMValue::Float(f), - crate::value::NyashValue::Bool(b) => VMValue::Bool(b), - crate::value::NyashValue::String(s) => VMValue::String(s), - crate::value::NyashValue::Void | crate::value::NyashValue::Null => VMValue::Void, - crate::value::NyashValue::Box(bx) => { - if let Ok(g) = bx.lock() { VMValue::from_nyash_box(g.share_box()) } else { VMValue::Void } - } - _ => VMValue::Void, - }, - None => VMValue::Void, - }; - if let Some(dst_id) = _dst { self.set_value(dst_id, out_vm); } - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - return Some(Ok(ControlFlow::Continue)); - } - } - Some(2) => { // setField - if let (Ok(a0), Ok(a1)) = (self.get_value(_args[0]), self.get_value(_args[1])) { - let fname = match a0 { VMValue::String(s) => s, v => v.to_string() }; - let nv_opt = match a1.clone() { - VMValue::Integer(i) => Some(crate::value::NyashValue::Integer(i)), - VMValue::Float(f) => Some(crate::value::NyashValue::Float(f)), - VMValue::Bool(b) => Some(crate::value::NyashValue::Bool(b)), - VMValue::String(s) => Some(crate::value::NyashValue::String(s)), - VMValue::Void => Some(crate::value::NyashValue::Void), - _ => None, - }; - if let Some(nv) = nv_opt { - crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "VTable.Instance.setField"); - let _ = inst.set_field_unified(fname, nv); - if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::Void); } - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - return Some(Ok(ControlFlow::Continue)); - } - } - } - Some(3) => { // has - if let Ok(a0) = self.get_value(_args[0]) { - let fname = match a0 { VMValue::String(s) => s, v => v.to_string() }; - let has = inst.get_field_unified(&fname).is_some(); - if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::Bool(has)); } - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - return Some(Ok(ControlFlow::Continue)); - } - } - Some(4) => { // size - let sz = inst.fields_ng.lock().map(|m| m.len() as i64).unwrap_or(0); - if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::Integer(sz)); } - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - return Some(Ok(ControlFlow::Continue)); - } - _ => {} - } - } - // MapBox: size/len/has/get/set - if let Some(map) = b.as_any().downcast_ref::() { - if matches!(slot, Some(200|201)) { - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - if crate::config::env::vm_vt_trace() { eprintln!("[VT] MapBox.size/len slot={:?}", slot); } - let out = map.size(); - if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } - return Some(Ok(ControlFlow::Continue)); - } - if matches!(slot, Some(202)) { - if let Ok(arg_v) = self.get_value(_args[0]) { - let key_box = match arg_v { - VMValue::String(ref s) => Box::new(crate::box_trait::StringBox::new(s)) as Box, - VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)), - VMValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(b)), - VMValue::BoxRef(ref bx) => bx.share_box(), - VMValue::Float(f) => Box::new(crate::boxes::FloatBox::new(f)), - VMValue::Future(ref fut) => Box::new(fut.clone()), - VMValue::Void => Box::new(crate::box_trait::VoidBox::new()), - }; - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - if crate::config::env::vm_vt_trace() { eprintln!("[VT] MapBox.has"); } - let out = map.has(key_box); - if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } - return Some(Ok(ControlFlow::Continue)); - } - } - if matches!(slot, Some(203)) { - if let Ok(arg_v) = self.get_value(_args[0]) { - let key_box = match arg_v { - VMValue::String(ref s) => Box::new(crate::box_trait::StringBox::new(s)) as Box, - VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)), - VMValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(b)), - VMValue::BoxRef(ref bx) => bx.share_box(), - VMValue::Float(f) => Box::new(crate::boxes::FloatBox::new(f)), - VMValue::Future(ref fut) => Box::new(fut.clone()), - VMValue::Void => Box::new(crate::box_trait::VoidBox::new()), - }; - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - if crate::config::env::vm_vt_trace() { eprintln!("[VT] MapBox.get"); } - let out = map.get(key_box); - if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } - return Some(Ok(ControlFlow::Continue)); - } - } - if matches!(slot, Some(204)) { - if let (Ok(a0), Ok(a1)) = (self.get_value(_args[0]), self.get_value(_args[1])) { - let key_box: Box = match a0 { - VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)), - VMValue::String(ref s) => Box::new(crate::box_trait::StringBox::new(s)), - VMValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(b)), - VMValue::Float(f) => Box::new(crate::boxes::math_box::FloatBox::new(f)), - VMValue::BoxRef(ref bx) => bx.share_box(), - VMValue::Future(ref fut) => Box::new(fut.clone()), - VMValue::Void => Box::new(crate::box_trait::VoidBox::new()), - }; - let val_box: Box = match a1 { - VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)), - VMValue::String(ref s) => Box::new(crate::box_trait::StringBox::new(s)), - VMValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(b)), - VMValue::Float(f) => Box::new(crate::boxes::math_box::FloatBox::new(f)), - VMValue::BoxRef(ref bx) => bx.share_box(), - VMValue::Future(ref fut) => Box::new(fut.clone()), - VMValue::Void => Box::new(crate::box_trait::VoidBox::new()), - }; - // Barrier: mutation - crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "VTable.Map.set"); - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - if crate::config::env::vm_vt_trace() { eprintln!("[VT] MapBox.set"); } - let out = map.set(key_box, val_box); - if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } - return Some(Ok(ControlFlow::Continue)); - } - } - } - // ArrayBox: get/set/len - if let Some(arr) = b.as_any().downcast_ref::() { - if matches!(slot, Some(102)) { - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - if crate::config::env::vm_vt_trace() { eprintln!("[VT] ArrayBox.len"); } - let out = arr.length(); - if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } - return Some(Ok(ControlFlow::Continue)); - } - if matches!(slot, Some(100)) { - if let Ok(arg_v) = self.get_value(_args[0]) { - let idx_box: Box = match arg_v { - VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)), - VMValue::String(ref s) => Box::new(crate::box_trait::IntegerBox::new(s.parse::().unwrap_or(0))), - VMValue::Bool(b) => Box::new(crate::box_trait::IntegerBox::new(if b {1} else {0})), - VMValue::Float(f) => Box::new(crate::box_trait::IntegerBox::new(f as i64)), - VMValue::BoxRef(_) | VMValue::Future(_) | VMValue::Void => Box::new(crate::box_trait::IntegerBox::new(0)), - }; - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - if crate::config::env::vm_vt_trace() { eprintln!("[VT] ArrayBox.get"); } - let out = arr.get(idx_box); - if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } - return Some(Ok(ControlFlow::Continue)); - } - } - if matches!(slot, Some(101)) { - if let (Ok(a0), Ok(a1)) = (self.get_value(_args[0]), self.get_value(_args[1])) { - let idx_box: Box = match a0 { - VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)), - VMValue::String(ref s) => Box::new(crate::box_trait::IntegerBox::new(s.parse::().unwrap_or(0))), - VMValue::Bool(b) => Box::new(crate::box_trait::IntegerBox::new(if b {1} else {0})), - VMValue::Float(f) => Box::new(crate::box_trait::IntegerBox::new(f as i64)), - VMValue::BoxRef(_) | VMValue::Future(_) | VMValue::Void => Box::new(crate::box_trait::IntegerBox::new(0)), - }; - let val_box: Box = match a1 { - VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)), - VMValue::String(ref s) => Box::new(crate::box_trait::StringBox::new(s)), - VMValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(b)), - VMValue::Float(f) => Box::new(crate::boxes::math_box::FloatBox::new(f)), - VMValue::BoxRef(ref bx) => bx.share_box(), - VMValue::Future(ref fut) => Box::new(fut.clone()), - VMValue::Void => Box::new(crate::box_trait::VoidBox::new()), - }; - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - // Barrier: mutation - crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "VTable.Array.set"); - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - if crate::config::env::vm_vt_trace() { eprintln!("[VT] ArrayBox.set"); } - let out = arr.set(idx_box, val_box); - if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } - return Some(Ok(ControlFlow::Continue)); - } - } - } - // StringBox: len - if let Some(sb) = b.as_any().downcast_ref::() { - if matches!(slot, Some(300)) { - self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); - if crate::config::env::vm_vt_trace() { eprintln!("[VT] StringBox.len"); } - let out = crate::box_trait::IntegerBox::new(sb.value.len() as i64); - if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(Box::new(out))); } - return Some(Ok(ControlFlow::Continue)); - } - } - // STRICT: 型は登録されているがメソッドが未対応 → エラー(最終仕様フォーマット) - if crate::config::env::abi_strict() { - let known = crate::runtime::type_registry::known_methods_for(ty_name) - .unwrap_or_default() - .join(", "); - let msg = format!( - "ABI_STRICT: undefined vtable method {}.{}(arity={}) — known: {}", - ty_name, _method, _args.len(), known - ); - return Some(Err(VMError::TypeError(msg))); - } - } - } - None - } - - /// Execute a forced plugin invocation (no builtin fallback) - pub(super) fn execute_plugin_invoke(&mut self, dst: Option, box_val: ValueId, method: &str, args: &[ValueId]) -> Result { - // Helper: extract UTF-8 string from internal StringBox, Result.Ok(String-like), or plugin StringBox via toUtf8 - fn extract_string_from_box(bx: &dyn crate::box_trait::NyashBox) -> Option { - // Internal StringBox - if let Some(sb) = bx.as_any().downcast_ref::() { - return Some(sb.value.clone()); - } - // Result.Ok(inner) → recurse - if let Some(res) = bx.as_any().downcast_ref::() { - if let crate::boxes::result::NyashResultBox::Ok(inner) = res { return extract_string_from_box(inner.as_ref()); } - } - // Plugin StringBox → call toUtf8 - if let Some(p) = bx.as_any().downcast_ref::() { - if p.box_type == "StringBox" { - let host = crate::runtime::get_global_plugin_host(); - let tmp: Option = if let Ok(ro) = host.read() { - if let Ok(val_opt) = ro.invoke_instance_method("StringBox", "toUtf8", p.inner.instance_id, &[]) { - if let Some(vb) = val_opt { - if let Some(sb2) = vb.as_any().downcast_ref::() { - Some(sb2.value.clone()) - } else { None } - } else { None } - } else { None } - } else { None }; - if tmp.is_some() { return tmp; } - } - } - None - } - let recv = self.get_value(box_val)?; - // Allow static birth on primitives/builtin boxes to create a plugin instance. - if method == "birth" && !matches!(recv, VMValue::BoxRef(ref b) if b.as_any().downcast_ref::().is_some()) { - eprintln!("[VM PluginInvoke] static birth fallback recv={:?}", recv); - // Map primitive/builtin receiver to plugin box type name and constructor args - let mut created: Option = None; - match &recv { - VMValue::String(s) => { - // Create plugin StringBox with initial content - let host = crate::runtime::get_global_plugin_host(); - let host = host.read().unwrap(); - let sb: Box = Box::new(crate::box_trait::StringBox::new(s.clone())); - if let Ok(b) = host.create_box("StringBox", &[sb]) { - created = Some(VMValue::from_nyash_box(b)); - } - } - VMValue::Integer(_n) => { - // Create plugin IntegerBox (value defaults to 0); args ignored by current plugin - let host = crate::runtime::get_global_plugin_host(); - let host = host.read().unwrap(); - if let Ok(b) = host.create_box("IntegerBox", &[]) { - created = Some(VMValue::from_nyash_box(b)); - } - } - _ => { - // no-op - } - } - if let Some(val) = created { - if let Some(dst_id) = dst { self.set_value(dst_id, val); } - return Ok(ControlFlow::Continue); - } - } - - // Only allowed on plugin boxes - if let VMValue::BoxRef(pbox) = &recv { - if let Some(p) = pbox.as_any().downcast_ref::() { - // Resolve method_id via unified host - let host = crate::runtime::get_global_plugin_host(); - let host = host.read().unwrap(); - let mh = host.resolve_method(&p.box_type, method) - .map_err(|_| VMError::InvalidInstruction(format!("Plugin method not found: {}.{}", p.box_type, method)))?; - - // Encode args to TLV - let mut tlv = crate::runtime::plugin_ffi_common::encode_tlv_header(args.len() as u16); - for (idx, a) in args.iter().enumerate() { - let v = self.get_value(*a)?; - match v { - VMValue::Integer(n) => { - if std::env::var("NYASH_DEBUG_PLUGIN").ok().as_deref() == Some("1") { - eprintln!("[VM→Plugin][vm] arg[{}] encode I64 {}", idx, n); - } - crate::runtime::plugin_ffi_common::encode::i64(&mut tlv, n) - } - VMValue::Float(x) => { - if std::env::var("NYASH_DEBUG_PLUGIN").ok().as_deref() == Some("1") { - eprintln!("[VM→Plugin][vm] arg[{}] encode F64 {}", idx, x); - } - crate::runtime::plugin_ffi_common::encode::f64(&mut tlv, x) - } - VMValue::Bool(b) => crate::runtime::plugin_ffi_common::encode::bool(&mut tlv, b), - VMValue::String(ref s) => crate::runtime::plugin_ffi_common::encode::string(&mut tlv, s), - VMValue::BoxRef(ref b) => { - if let Some(h) = b.as_any().downcast_ref::() { - // Coerce common plugin primitives to TLV primitives instead of handle when sensible - if h.box_type == "StringBox" { - // toUtf8 -> TLV string - let host = crate::runtime::get_global_plugin_host(); - let host = host.read().unwrap(); - if let Ok(val_opt) = host.invoke_instance_method("StringBox", "toUtf8", h.inner.instance_id, &[]) { - if let Some(sb) = val_opt.and_then(|bx| bx.as_any().downcast_ref::().map(|s| s.value.clone())) { - crate::runtime::plugin_ffi_common::encode::string(&mut tlv, &sb); - continue; - } - } - } else if h.box_type == "IntegerBox" { - // get() -> TLV i64 - let host = crate::runtime::get_global_plugin_host(); - let host = host.read().unwrap(); - if let Ok(val_opt) = host.invoke_instance_method("IntegerBox", "get", h.inner.instance_id, &[]) { - if let Some(ib) = val_opt.and_then(|bx| bx.as_any().downcast_ref::().map(|i| i.value)) { - crate::runtime::plugin_ffi_common::encode::i64(&mut tlv, ib); - continue; - } - } - } - // Fallback: pass plugin handle - crate::runtime::plugin_ffi_common::encode::plugin_handle(&mut tlv, h.inner.type_id, h.inner.instance_id); - } else { - // HostHandle: expose user/builtin box via host-managed handle (tag=9) - let h = crate::runtime::host_handles::to_handle_arc(b.clone()); - crate::runtime::plugin_ffi_common::encode::host_handle(&mut tlv, h); - } - } - VMValue::Void => crate::runtime::plugin_ffi_common::encode::string(&mut tlv, "void"), - _ => { - return Err(VMError::TypeError(format!("Unsupported VMValue in PluginInvoke args: {:?}", v))); - } - } - } - - if std::env::var("NYASH_DEBUG_PLUGIN").ok().as_deref() == Some("1") { - eprintln!("[VM→Plugin] invoke {}.{} inst_id={} argc={} (direct)", p.box_type, method, p.inner.instance_id, args.len()); - } - let mut out = vec![0u8; 4096]; - let mut out_len: usize = out.len(); - let code = unsafe { - (p.inner.invoke_fn)( - p.inner.type_id, - mh.method_id, - p.inner.instance_id, - tlv.as_ptr(), - tlv.len(), - out.as_mut_ptr(), - &mut out_len, - ) - }; - if code != 0 { - let host = crate::runtime::get_global_plugin_host(); - let host = host.read().unwrap(); - let rr = host.method_returns_result(&p.box_type, method); - if rr { - let be = crate::bid::BidError::from_raw(code); - let err = crate::exception_box::ErrorBox::new(&format!("{} (code: {})", be.message(), code)); - let res = crate::boxes::result::NyashResultBox::new_err(Box::new(err)); - let vmv = VMValue::BoxRef(std::sync::Arc::from(Box::new(res) as Box)); - if let Some(dst_id) = dst { self.set_value(dst_id, vmv); } - return Ok(ControlFlow::Continue); - } else { - return Err(VMError::InvalidInstruction(format!("PluginInvoke failed: {}.{} rc={}", p.box_type, method, code))); - } - } - let vm_out_raw = if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) { - if std::env::var("NYASH_DEBUG_PLUGIN").ok().as_deref() == Some("1") { - let preview_len = std::cmp::min(payload.len(), 16); - let preview: Vec = payload[..preview_len].iter().map(|b| format!("{:02X}", b)).collect(); - eprintln!("[Plugin→VM][vm] {}.{} tag={} sz={} preview=\n{}", p.box_type, method, tag, _sz, preview.join(" ")); - } - match tag { - 1 => crate::runtime::plugin_ffi_common::decode::bool(payload).map(VMValue::Bool).unwrap_or(VMValue::Void), - 2 => crate::runtime::plugin_ffi_common::decode::i32(payload).map(|v| VMValue::Integer(v as i64)).unwrap_or(VMValue::Void), - 3 => { // I64 - if payload.len() == 8 { let mut b=[0u8;8]; b.copy_from_slice(&payload[0..8]); VMValue::Integer(i64::from_le_bytes(b)) } else { VMValue::Void } - } - 5 => crate::runtime::plugin_ffi_common::decode::f64(payload).map(VMValue::Float).unwrap_or(VMValue::Void), - 6 => VMValue::String(crate::runtime::plugin_ffi_common::decode::string(payload)), - 8 => { - // Handle return: map (type_id, instance_id) to PluginBoxV2 using central config - if payload.len() == 8 { - let mut t = [0u8;4]; t.copy_from_slice(&payload[0..4]); - let mut i = [0u8;4]; i.copy_from_slice(&payload[4..8]); - let r_type = u32::from_le_bytes(t); - let r_inst = u32::from_le_bytes(i); - // Resolve box name via [box_types] reverse lookup - let host_arc = crate::runtime::get_global_plugin_host(); - let host_ro = host_arc.read().unwrap(); - if let Some(cfg) = host_ro.config_ref() { - let mut box_name_opt: Option = None; - for (name, id) in cfg.box_types.iter() { if *id == r_type { box_name_opt = Some(name.clone()); break; } } - if let Some(box_name) = box_name_opt { - // Find library providing this box - if let Some((lib_name, _)) = cfg.find_library_for_box(&box_name) { - // Read nyash.toml to resolve fini method - let cfg_path = "nyash.toml"; - if let Ok(toml_content) = std::fs::read_to_string(cfg_path) { - if let Ok(toml_value) = toml::from_str::(&toml_content) { - let fini_id = cfg.get_box_config(lib_name, &box_name, &toml_value) - .and_then(|bc| bc.methods.get("fini").map(|m| m.method_id)); - let pbox = crate::runtime::plugin_loader_v2::construct_plugin_box( - box_name, - r_type, - p.inner.invoke_fn, - r_inst, - fini_id, - ); - VMValue::BoxRef(Arc::from(Box::new(pbox) as Box)) - } else { VMValue::Void } - } else { VMValue::Void } - } else { VMValue::Void } - } else { VMValue::Void } - } else { VMValue::Void } - } else { VMValue::Void } - } - _ => VMValue::Void, - } - } else { VMValue::Void }; - // Wrap into Result.Ok when method is declared returns_result - let vm_out = { - let host = crate::runtime::get_global_plugin_host(); - let host = host.read().unwrap(); - let rr = host.method_returns_result(&p.box_type, method); - if rr { - // Boxify vm_out into NyashBox first - let boxed: Box = match vm_out_raw { - VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)), - VMValue::Float(f) => Box::new(crate::boxes::math_box::FloatBox::new(f)), - VMValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(b)), - VMValue::String(s) => Box::new(crate::box_trait::StringBox::new(s)), - VMValue::BoxRef(b) => b.share_box(), - VMValue::Void => Box::new(crate::box_trait::VoidBox::new()), - _ => Box::new(crate::box_trait::StringBox::new(vm_out_raw.to_string())) - }; - let res = crate::boxes::result::NyashResultBox::new_ok(boxed); - VMValue::BoxRef(std::sync::Arc::from(Box::new(res) as Box)) - } else { - vm_out_raw - } - }; - if let Some(dst_id) = dst { self.set_value(dst_id, vm_out); } - return Ok(ControlFlow::Continue); - } - } - // Fallback: support common string-like methods without requiring PluginBox receiver - if let VMValue::BoxRef(ref bx) = recv { - // Try to view receiver as string (internal, plugin, or Result.Ok) - if let Some(s) = extract_string_from_box(bx.as_ref()) { - match method { - "length" => { - if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::Integer(s.len() as i64)); } - return Ok(ControlFlow::Continue); - } - "is_empty" | "isEmpty" => { - if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::Bool(s.is_empty())); } - return Ok(ControlFlow::Continue); - } - "charCodeAt" => { - let idx_v = if let Some(a0) = args.get(0) { self.get_value(*a0)? } else { VMValue::Integer(0) }; - let idx = match idx_v { VMValue::Integer(i) => i.max(0) as usize, _ => 0 }; - let code = s.chars().nth(idx).map(|c| c as u32 as i64).unwrap_or(0); - if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::Integer(code)); } - return Ok(ControlFlow::Continue); - } - "concat" => { - let rhs_v = if let Some(a0) = args.get(0) { self.get_value(*a0)? } else { VMValue::String(String::new()) }; - let rhs_s = match rhs_v { - VMValue::String(ss) => ss, - VMValue::BoxRef(br) => extract_string_from_box(br.as_ref()).unwrap_or_else(|| br.to_string_box().value), - _ => rhs_v.to_string(), - }; - let mut new_s = s.clone(); - new_s.push_str(&rhs_s); - let out = Box::new(crate::box_trait::StringBox::new(new_s)); - if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::BoxRef(std::sync::Arc::from(out as Box))); } - return Ok(ControlFlow::Continue); - } - _ => {} - } - } - } - Err(VMError::InvalidInstruction(format!("PluginInvoke requires PluginBox receiver; method={} got {:?}", method, recv))) - } -} - -impl VM { - /// Try fast universal-thunk dispatch using reserved method slots 0..3. - /// Returns Some(result) if handled, or None to fall back to legacy path. - fn try_fast_universal(&mut self, method_id: u16, recv: &VMValue, args: &[ValueId]) -> Result, VMError> { - match method_id { - 0 => { // toString - let s = recv.to_string(); - return Ok(Some(VMValue::String(s))); - } - 1 => { // type - let t = match recv { - VMValue::Integer(_) => "Integer".to_string(), - VMValue::Float(_) => "Float".to_string(), - VMValue::Bool(_) => "Bool".to_string(), - VMValue::String(_) => "String".to_string(), - VMValue::Future(_) => "Future".to_string(), - VMValue::Void => "Void".to_string(), - VMValue::BoxRef(b) => b.type_name().to_string(), - }; - return Ok(Some(VMValue::String(t))); - } - 2 => { // equals - // equals(other): bool — naive implementation via VMValue equivalence/coercion - let other = if let Some(arg0) = args.get(0) { self.get_value(*arg0)? } else { VMValue::Void }; - let res = match (recv, &other) { - (VMValue::Integer(a), VMValue::Integer(b)) => a == b, - (VMValue::Bool(a), VMValue::Bool(b)) => a == b, - (VMValue::String(a), VMValue::String(b)) => a == b, - (VMValue::Void, VMValue::Void) => true, - _ => recv.to_string() == other.to_string(), - }; - return Ok(Some(VMValue::Bool(res))); - } - 3 => { // clone - // For primitives: just copy. For BoxRef: share_box then wrap back. - let v = match recv { - VMValue::Integer(i) => VMValue::Integer(*i), - VMValue::Float(f) => VMValue::Float(*f), - VMValue::Bool(b) => VMValue::Bool(*b), - VMValue::String(s) => VMValue::String(s.clone()), - VMValue::Future(f) => VMValue::Future(f.clone()), - VMValue::Void => VMValue::Void, - VMValue::BoxRef(b) => VMValue::from_nyash_box(b.share_box()), - }; - return Ok(Some(v)); - } - _ => {} - } - Ok(None) - } -} diff --git a/src/backend/vm_instructions/boxcall.rs b/src/backend/vm_instructions/boxcall.rs new file mode 100644 index 00000000..5268e8d0 --- /dev/null +++ b/src/backend/vm_instructions/boxcall.rs @@ -0,0 +1,559 @@ +use crate::box_trait::NyashBox; +use crate::mir::ValueId; +use crate::backend::vm::ControlFlow; +use crate::backend::{VM, VMError, VMValue}; + +impl VM { + /// Execute BoxCall instruction + pub(crate) fn execute_boxcall(&mut self, dst: Option, box_val: ValueId, method: &str, method_id: Option, args: &[ValueId]) -> Result { + let recv = self.get_value(box_val)?; + // Debug logging if enabled + let debug_boxcall = std::env::var("NYASH_VM_DEBUG_BOXCALL").is_ok(); + + // Fast-path: ConsoleBox.readLine — provide safe stdin fallback with EOF→Void + if let VMValue::BoxRef(arc_box) = &recv { + if let Some(p) = arc_box.as_any().downcast_ref::() { + if p.box_type == "ConsoleBox" && method == "readLine" { + use std::io::Read; + let mut s = String::new(); + let mut stdin = std::io::stdin(); + let mut buf = [0u8; 1]; + loop { + match stdin.read(&mut buf) { + Ok(0) => { + if let Some(dst_id) = dst { let nb = crate::boxes::null_box::NullBox::new(); self.set_value(dst_id, VMValue::from_nyash_box(Box::new(nb))); } + return Ok(ControlFlow::Continue); + } + Ok(_) => { + let ch = buf[0] as char; + if ch == '\n' { break; } + s.push(ch); + if s.len() > 1_000_000 { break; } + } + Err(_) => { + if let Some(dst_id) = dst { let nb = crate::boxes::null_box::NullBox::new(); self.set_value(dst_id, VMValue::from_nyash_box(Box::new(nb))); } + return Ok(ControlFlow::Continue); + } + } + } + if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::String(s)); } + return Ok(ControlFlow::Continue); + } + } + } + + // VTable-first stub path (optional) + if crate::config::env::abi_vtable() { + if let Some(res) = self.try_boxcall_vtable_stub(dst, &recv, method, method_id, args) { return res; } + } + + // Record PIC hit (per-receiver-type × method) + let pic_key = self.build_pic_key(&recv, method, method_id); + self.pic_record_hit(&pic_key); + + // Explicit fast-paths + if let VMValue::BoxRef(arc_box) = &recv { + // ArrayBox get/set + if arc_box.as_any().downcast_ref::().is_some() { + let get_slot = crate::mir::slot_registry::resolve_slot_by_type_name("ArrayBox", "get"); + let set_slot = crate::mir::slot_registry::resolve_slot_by_type_name("ArrayBox", "set"); + let is_get = (method_id.is_some() && method_id == get_slot) || method == "get"; + let is_set = (method_id.is_some() && method_id == set_slot) || method == "set"; + if is_get && args.len() >= 1 { + let idx_val = self.get_value(args[0])?; + let idx_box = idx_val.to_nyash_box(); + let arr = arc_box.as_any().downcast_ref::().unwrap(); + let out = arr.get(idx_box); + if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } + return Ok(ControlFlow::Continue); + } else if is_set && args.len() >= 2 { + crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "BoxCall.fastpath.Array.set"); + let idx_val = self.get_value(args[0])?; + let val_val = self.get_value(args[1])?; + let idx_box = idx_val.to_nyash_box(); + let val_box = val_val.to_nyash_box(); + let arr = arc_box.as_any().downcast_ref::().unwrap(); + let out = arr.set(idx_box, val_box); + if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } + return Ok(ControlFlow::Continue); + } + } + // InstanceBox getField/setField by name + if let Some(inst) = arc_box.as_any().downcast_ref::() { + let is_getf = method == "getField"; + let is_setf = method == "setField"; + if (is_getf && args.len() >= 1) || (is_setf && args.len() >= 2) { + let name_val = self.get_value(args[0])?; + let field_name = match &name_val { VMValue::String(s) => s.clone(), _ => name_val.to_string(), }; + if is_getf { + let out_opt = inst.get_field_unified(&field_name); + let out_vm = match out_opt { + Some(nv) => match nv { + crate::value::NyashValue::Integer(i) => VMValue::Integer(i), + crate::value::NyashValue::Float(f) => VMValue::Float(f), + crate::value::NyashValue::Bool(b) => VMValue::Bool(b), + crate::value::NyashValue::String(s) => VMValue::String(s), + crate::value::NyashValue::Void | crate::value::NyashValue::Null => VMValue::Void, + crate::value::NyashValue::Box(b) => { if let Ok(g) = b.lock() { VMValue::from_nyash_box(g.share_box()) } else { VMValue::Void } } + _ => VMValue::Void, + }, + None => VMValue::Void, + }; + if let Some(dst_id) = dst { self.set_value(dst_id, out_vm); } + return Ok(ControlFlow::Continue); + } else { + // setField + crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "BoxCall.fastpath.Instance.setField"); + let val_vm = self.get_value(args[1])?; + let nv_opt = match val_vm.clone() { + VMValue::Integer(i) => Some(crate::value::NyashValue::Integer(i)), + VMValue::Float(f) => Some(crate::value::NyashValue::Float(f)), + VMValue::Bool(b) => Some(crate::value::NyashValue::Bool(b)), + VMValue::String(s) => Some(crate::value::NyashValue::String(s)), + VMValue::Void => Some(crate::value::NyashValue::Void), + _ => None, + }; + if let Some(nv) = nv_opt { let _ = inst.set_field_unified(field_name, nv); if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::Void); } return Ok(ControlFlow::Continue); } + } + } + } + } + + // VTable-like thunk path via TypeMeta and method_id + if let (Some(mid), VMValue::BoxRef(arc_box)) = (method_id, &recv) { + let mut class_label: Option = 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::() { class_label = Some(inst.class_name.clone()); is_instance = true; } + else if let Some(p) = arc_box.as_any().downcast_ref::() { 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)?; + self.record_poly_pic(&pic_key, &recv, &func_name); + let threshold = crate::config::env::vm_pic_threshold(); + if self.pic_hits(&pic_key) >= threshold { self.boxcall_pic_funcname.insert(pic_key.clone(), func_name.clone()); } + if is_instance { + let vkey = self.build_vtable_key(&label, mid, args.len()); + self.boxcall_vtable_funcname.entry(vkey).or_insert(func_name.clone()); + } + 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::() { + self.enter_root_region(); + let nyash_args: Vec> = args.iter().map(|arg| { let val = self.get_value(*arg)?; Ok(val.to_nyash_box()) }).collect::, VMError>>()?; + self.pin_roots(std::iter::once(&recv)); + let pinned_args: Vec = args.iter().filter_map(|a| self.get_value(*a).ok()).collect(); + self.pin_roots(pinned_args.iter()); + 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::runtime::plugin_ffi_common::encode::string(&mut tlv, &s.value); } + else if let Some(i) = a.as_any().downcast_ref::() { 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_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(); + crate::runtime::host_api::set_current_vm(self as *mut _); + 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) }; + crate::runtime::host_api::clear_current_vm(); + if code == 0 { + let vm_out = if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) { + match tag { + 6 | 7 => VMValue::String(crate::runtime::plugin_ffi_common::decode::string(payload)), + 2 => crate::runtime::plugin_ffi_common::decode::i32(payload).map(|v| VMValue::Integer(v as i64)).unwrap_or(VMValue::Void), + 9 => { + if let Some(h) = crate::runtime::plugin_ffi_common::decode::u64(payload) { if let Some(arc) = crate::runtime::host_handles::get(h) { VMValue::BoxRef(arc) } else { VMValue::Void } } else { VMValue::Void } + } + _ => VMValue::Void, + } + } else { VMValue::Void }; + if let Some(dst_id) = dst { self.set_value(dst_id, vm_out); } + self.leave_root_region(); + return Ok(ControlFlow::Continue); + } + self.leave_root_region(); + } + } + } + } + crate::runtime::type_meta::ThunkTarget::BuiltinCall { method: ref m } => { + if is_builtin { + let nyash_args: Vec> = args.iter().map(|arg| { let val = self.get_value(*arg)?; Ok(val.to_nyash_box()) }).collect::, VMError>>()?; + if crate::backend::gc_helpers::is_mutating_builtin_call(&recv, m) { crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "BoxCall.builtin"); } + else if m == "setField" { crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "BoxCall.setField"); } + let cloned_box = arc_box.share_box(); + self.boxcall_hits_generic = self.boxcall_hits_generic.saturating_add(1); + 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); + } + } + } + } + } + // Legacy vtable cache for InstanceBox + if is_instance { + let inst = arc_box.as_any().downcast_ref::().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 to lowered function if present + if let VMValue::BoxRef(arc_box) = &recv { + if arc_box.as_any().downcast_ref::().is_some() { + if let Some(func_name) = self.try_poly_pic(&pic_key, &recv) { + if crate::config::env::vm_pic_trace() { eprintln!("[PIC] poly hit {}", pic_key); } + self.boxcall_hits_poly_pic = self.boxcall_hits_poly_pic.saturating_add(1); + 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); + } + if let Some(func_name) = self.boxcall_pic_funcname.get(&pic_key).cloned() { + if crate::config::env::vm_pic_trace() { eprintln!("[PIC] mono hit {}", pic_key); } + self.boxcall_hits_mono_pic = self.boxcall_hits_mono_pic.saturating_add(1); + 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); + } + } + } + + // Fast universal slots (0..3) + if let Some(mid) = method_id { + if let Some(fast_res) = self.try_fast_universal(mid, &recv, args)? { if let Some(dst_id) = dst { self.set_value(dst_id, fast_res); } return Ok(ControlFlow::Continue); } + } + + // Generic path: convert args to NyashBox and call + let nyash_args: Vec> = args.iter().map(|arg| { let val = self.get_value(*arg)?; Ok(val.to_nyash_box()) }).collect::, VMError>>()?; + + // PluginBoxV2 fast-path via direct invoke_fn when method_id present + if let (Some(mid), VMValue::BoxRef(arc_box)) = (method_id, &recv) { + if let Some(p) = arc_box.as_any().downcast_ref::() { + self.enter_root_region(); + 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(buf) = a.as_any().downcast_ref::() { 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::runtime::plugin_ffi_common::encode::string(&mut tlv, &s.value); } + else if let Some(i) = a.as_any().downcast_ref::() { 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::runtime::plugin_ffi_common::encode::bool(&mut tlv, b.value); } + else if let Some(f) = a.as_any().downcast_ref::() { crate::runtime::plugin_ffi_common::encode::f64(&mut tlv, f.value); } + else if let Some(arr) = a.as_any().downcast_ref::() { + 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::() { 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_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, mid as u32, p.inner.instance_id, tlv.as_ptr(), tlv.len(), out.as_mut_ptr(), &mut out_len) }; + 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); + let vm_out = if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) { + match tag { + 1 => crate::runtime::plugin_ffi_common::decode::bool(payload).map(VMValue::Bool).unwrap_or(VMValue::Void), + 2 => crate::runtime::plugin_ffi_common::decode::i32(payload).map(|v| VMValue::Integer(v as i64)).unwrap_or(VMValue::Void), + 5 => crate::runtime::plugin_ffi_common::decode::f64(payload).map(VMValue::Float).unwrap_or(VMValue::Void), + 6 => VMValue::String(crate::runtime::plugin_ffi_common::decode::string(payload)), + _ => VMValue::Void, + } + } else { VMValue::Void }; + if let Some(dst_id) = dst { self.set_value(dst_id, vm_out); } + self.leave_root_region(); + return Ok(ControlFlow::Continue); + } + self.leave_root_region(); + } + } + } + + if debug_boxcall { self.debug_log_boxcall(&recv, method, &nyash_args, "START", None); } + + // Call the method based on receiver type + let result = match &recv { + VMValue::BoxRef(arc_box) => { + if let Some(inst) = arc_box.as_any().downcast_ref::() { + let func_name = format!("{}.{}{}", inst.class_name, method, format!("/{}", args.len())); + if let Some(mid) = method_id { + 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()); + let vkey = self.build_vtable_key(&inst.class_name, mid, args.len()); + self.boxcall_vtable_funcname.entry(vkey).or_insert(func_name.clone()); + } + self.record_poly_pic(&pic_key, &recv, &func_name); + let threshold = crate::config::env::vm_pic_threshold(); + if self.pic_hits(&pic_key) >= threshold { self.boxcall_pic_funcname.insert(pic_key.clone(), func_name.clone()); } + if debug_boxcall { eprintln!("[BoxCall] InstanceBox -> call {}", 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)?; + return { if let Some(dst_id) = dst { self.set_value(dst_id, res); } Ok(ControlFlow::Continue) }; + } + if debug_boxcall { eprintln!("[BoxCall] Taking BoxRef path for method '{}'", method); } + 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()); + } + if crate::backend::gc_helpers::is_mutating_builtin_call(&recv, method) { crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "BoxCall"); } + else if method == "setField" { crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "BoxCall.setField"); } + let cloned_box = arc_box.share_box(); + self.call_box_method(cloned_box, method, nyash_args)? + } + _ => { + if debug_boxcall { eprintln!("[BoxCall] Converting primitive to box for method '{}'", method); } + let box_value = recv.to_nyash_box(); + self.call_box_method(box_value, method, nyash_args)? + } + }; + + let result_val = VMValue::from_nyash_box(result); + if debug_boxcall { self.debug_log_boxcall(&recv, method, &[], "END", Some(&result_val)); } + if let Some(dst_id) = dst { self.set_value(dst_id, result_val); } + Ok(ControlFlow::Continue) + } + + /// Phase 12 Tier-0: vtable-first stub for selected types + pub(super) fn try_boxcall_vtable_stub(&mut self, _dst: Option, _recv: &VMValue, _method: &str, _method_id: Option, _args: &[ValueId]) -> Option> { + if crate::config::env::vm_vt_trace() { + match _recv { VMValue::BoxRef(b) => eprintln!("[VT] probe recv_ty={} method={} argc={}", b.type_name(), _method, _args.len()), other => eprintln!("[VT] probe recv_prim={:?} method={} argc={}", other, _method, _args.len()), } + } + if let VMValue::BoxRef(b) = _recv { + let ty_name = b.type_name(); + let ty_name_for_reg: std::borrow::Cow<'_, str> = if let Some(p) = b.as_any().downcast_ref::() { std::borrow::Cow::Owned(p.box_type.clone()) } else { std::borrow::Cow::Borrowed(ty_name) }; + if let Some(_tb) = crate::runtime::type_registry::resolve_typebox_by_name(&ty_name_for_reg) { + let slot = crate::runtime::type_registry::resolve_slot_by_name(&ty_name_for_reg, _method, _args.len()); + if let Some(p) = b.as_any().downcast_ref::() { + if crate::config::env::vm_vt_trace() { eprintln!("[VT] plugin recv ty={} method={} arity={}", ty_name, _method, _args.len()); } + let mut nyash_args: Vec> = Vec::with_capacity(_args.len()); + for aid in _args.iter() { if let Ok(v) = self.get_value(*aid) { nyash_args.push(v.to_nyash_box()); } else { nyash_args.push(Box::new(crate::box_trait::VoidBox::new())); } } + match ty_name { + "MapBox" => { + match slot { + Some(200) | Some(201) => { + let host = crate::runtime::get_global_plugin_host(); + let ro = host.read().unwrap(); + if let Ok(val_opt) = ro.invoke_instance_method("MapBox", _method, p.inner.instance_id, &[]) { + if let Some(out) = val_opt { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } } + self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); + return Some(Ok(ControlFlow::Continue)); + } + } + Some(202) | Some(203) | Some(204) => { + if matches!(slot, Some(204)) { crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "VTable.Plugin.Map.set"); } + let host = crate::runtime::get_global_plugin_host(); + let ro = host.read().unwrap(); + let mut method_eff = _method; + if (matches!(slot, Some(202)) && _args.len() >= 1) || (matches!(slot, Some(203)) && _args.len() >= 1) { + if let Ok(a0v) = self.get_value(_args[0]) { if matches!(a0v, VMValue::String(_)) { method_eff = if matches!(slot, Some(203)) { "getS" } else { "hasS" }; } } + } + if let Ok(val_opt) = ro.invoke_instance_method("MapBox", method_eff, p.inner.instance_id, &nyash_args) { + if let Some(out) = val_opt { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } } + else if _dst.is_some() { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::Void); } } + self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); + return Some(Ok(ControlFlow::Continue)); + } + } + _ => {} + } + } + "ArrayBox" => { + match slot { + Some(100) | Some(101) | Some(102) => { + if matches!(slot, Some(101)) { crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "VTable.Plugin.Array.set"); } + let host = crate::runtime::get_global_plugin_host(); + let ro = host.read().unwrap(); + if let Ok(val_opt) = ro.invoke_instance_method("ArrayBox", _method, p.inner.instance_id, &nyash_args) { + if let Some(out) = val_opt { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } } + self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); + return Some(Ok(ControlFlow::Continue)); + } + } + _ => {} + } + } + _ => {} + } + } + // Builtin boxes + if let Some(map) = b.as_any().downcast_ref::() { + if matches!(slot, Some(200)) || matches!(slot, Some(201)) { + self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); + if crate::config::env::vm_vt_trace() { eprintln!("[VT] MapBox.size/len"); } + let out = map.size(); + if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } + return Some(Ok(ControlFlow::Continue)); + } + if matches!(slot, Some(202)) { + if let Ok(arg_v) = self.get_value(_args[0]) { + let key_box: Box = match arg_v { + VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)), + VMValue::String(ref s) => Box::new(crate::box_trait::StringBox::new(s)), + VMValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(b)), + VMValue::Float(f) => Box::new(crate::boxes::math_box::FloatBox::new(f)), + VMValue::BoxRef(ref bx) => bx.share_box(), + VMValue::Future(ref fut) => Box::new(fut.clone()), + VMValue::Void => Box::new(crate::box_trait::VoidBox::new()), + }; + self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); + if crate::config::env::vm_vt_trace() { eprintln!("[VT] MapBox.has"); } + let out = map.has(key_box); + if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } + return Some(Ok(ControlFlow::Continue)); + } + } + if matches!(slot, Some(203)) { + if let Ok(arg_v) = self.get_value(_args[0]) { + let key_box: Box = match arg_v { + VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)), + VMValue::String(ref s) => Box::new(crate::box_trait::StringBox::new(s)), + VMValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(b)), + VMValue::Float(f) => Box::new(crate::boxes::math_box::FloatBox::new(f)), + VMValue::BoxRef(ref bx) => bx.share_box(), + VMValue::Future(ref fut) => Box::new(fut.clone()), + VMValue::Void => Box::new(crate::box_trait::VoidBox::new()), + }; + self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); + if crate::config::env::vm_vt_trace() { eprintln!("[VT] MapBox.get"); } + let out = map.get(key_box); + if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } + return Some(Ok(ControlFlow::Continue)); + } + } + if matches!(slot, Some(204)) { + if let (Ok(a0), Ok(a1)) = (self.get_value(_args[0]), self.get_value(_args[1])) { + if let VMValue::String(ref s) = a0 { let vb: Box = match a1 { VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)), VMValue::String(ref s) => Box::new(crate::box_trait::StringBox::new(s)), VMValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(b)), VMValue::Float(f) => Box::new(crate::boxes::math_box::FloatBox::new(f)), VMValue::BoxRef(ref bx) => bx.share_box(), VMValue::Future(ref fut) => Box::new(fut.clone()), VMValue::Void => Box::new(crate::box_trait::VoidBox::new()), }; let out = map.set(Box::new(crate::box_trait::StringBox::new(s)), vb); if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } return Some(Ok(ControlFlow::Continue)); } + let key_box: Box = match a0 { + VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)), + VMValue::String(ref s) => Box::new(crate::box_trait::StringBox::new(s)), + VMValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(b)), + VMValue::Float(f) => Box::new(crate::boxes::math_box::FloatBox::new(f)), + VMValue::BoxRef(ref bx) => bx.share_box(), + VMValue::Future(ref fut) => Box::new(fut.clone()), + VMValue::Void => Box::new(crate::box_trait::VoidBox::new()), + }; + let val_box: Box = match a1 { + VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)), + VMValue::String(ref s) => Box::new(crate::box_trait::StringBox::new(s)), + VMValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(b)), + VMValue::Float(f) => Box::new(crate::boxes::math_box::FloatBox::new(f)), + VMValue::BoxRef(ref bx) => bx.share_box(), + VMValue::Future(ref fut) => Box::new(fut.clone()), + VMValue::Void => Box::new(crate::box_trait::VoidBox::new()), + }; + // Barrier: mutation + crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "VTable.Map.set"); + self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); + if crate::config::env::vm_vt_trace() { eprintln!("[VT] MapBox.set"); } + let out = map.set(key_box, val_box); + if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } + return Some(Ok(ControlFlow::Continue)); + } + } + } + // StringBox: len + if let Some(sb) = b.as_any().downcast_ref::() { + if matches!(slot, Some(300)) { + self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1); + if crate::config::env::vm_vt_trace() { eprintln!("[VT] StringBox.len"); } + let out = crate::box_trait::IntegerBox::new(sb.value.len() as i64); + if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(Box::new(out))); } + return Some(Ok(ControlFlow::Continue)); + } + } + if crate::config::env::abi_strict() { + let known = crate::runtime::type_registry::known_methods_for(ty_name).unwrap_or_default().join(", "); + let msg = format!("ABI_STRICT: undefined vtable method {}.{}(arity={}) — known: {}", ty_name, _method, _args.len(), known); + return Some(Err(VMError::TypeError(msg))); + } + } + } + None + } +} + +impl VM { + /// Try fast universal-thunk dispatch using reserved method slots 0..3. + fn try_fast_universal(&mut self, method_id: u16, recv: &VMValue, args: &[ValueId]) -> Result, VMError> { + match method_id { + 0 => { let s = recv.to_string(); return Ok(Some(VMValue::String(s))); } + 1 => { + let t = match recv { + VMValue::Integer(_) => "Integer".to_string(), + VMValue::Float(_) => "Float".to_string(), + VMValue::Bool(_) => "Bool".to_string(), + VMValue::String(_) => "String".to_string(), + VMValue::Future(_) => "Future".to_string(), + VMValue::Void => "Void".to_string(), + VMValue::BoxRef(b) => b.type_name().to_string(), + }; + return Ok(Some(VMValue::String(t))); + } + 2 => { + let other = if let Some(arg0) = args.get(0) { self.get_value(*arg0)? } else { VMValue::Void }; + let res = match (recv, &other) { + (VMValue::Integer(a), VMValue::Integer(b)) => a == b, + (VMValue::Bool(a), VMValue::Bool(b)) => a == b, + (VMValue::String(a), VMValue::String(b)) => a == b, + (VMValue::Void, VMValue::Void) => true, + _ => recv.to_string() == other.to_string(), + }; + return Ok(Some(VMValue::Bool(res))); + } + 3 => { + let v = match recv { + VMValue::Integer(i) => VMValue::Integer(*i), + VMValue::Float(f) => VMValue::Float(*f), + VMValue::Bool(b) => VMValue::Bool(*b), + VMValue::String(s) => VMValue::String(s.clone()), + VMValue::Future(f) => VMValue::Future(f.clone()), + VMValue::Void => VMValue::Void, + VMValue::BoxRef(b) => VMValue::from_nyash_box(b.share_box()), + }; + return Ok(Some(v)); + } + _ => {} + } + Ok(None) + } +} diff --git a/src/backend/vm_instructions/call.rs b/src/backend/vm_instructions/call.rs new file mode 100644 index 00000000..c650f7e9 --- /dev/null +++ b/src/backend/vm_instructions/call.rs @@ -0,0 +1,41 @@ +use crate::box_trait::NyashBox; +use crate::mir::ValueId; +use crate::backend::vm::ControlFlow; +use crate::backend::{VM, VMError, VMValue}; + +impl VM { + /// Execute Call instruction (supports function name or FunctionBox value) + pub(crate) fn execute_call(&mut self, dst: Option, func: ValueId, args: &[ValueId]) -> Result { + // Evaluate function value + let fval = self.get_value(func)?; + match fval { + VMValue::String(func_name) => { + // Legacy: call function by name + let arg_values: Vec = args.iter().map(|arg| self.get_value(*arg)).collect::, _>>()?; + let result = self.call_function_by_name(&func_name, arg_values)?; + if let Some(dst_id) = dst { self.set_value(dst_id, result); } + Ok(ControlFlow::Continue) + } + VMValue::BoxRef(arc_box) => { + // FunctionBox call path + if let Some(fun) = arc_box.as_any().downcast_ref::() { + // Convert args to NyashBox for interpreter helper + let nyash_args: Vec> = args.iter() + .map(|a| self.get_value(*a).map(|v| v.to_nyash_box())) + .collect::, VMError>>()?; + // Execute via interpreter helper + match crate::interpreter::run_function_box(fun, nyash_args) { + Ok(out) => { + if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } + Ok(ControlFlow::Continue) + } + Err(e) => Err(VMError::InvalidInstruction(format!("FunctionBox call failed: {:?}", e))) + } + } else { + Err(VMError::TypeError(format!("Call target not callable: {}", arc_box.type_name()))) + } + } + other => Err(VMError::TypeError(format!("Call target must be function name or FunctionBox, got {:?}", other))), + } + } +} diff --git a/src/backend/vm_instructions/core.rs b/src/backend/vm_instructions/core.rs new file mode 100644 index 00000000..f536708e --- /dev/null +++ b/src/backend/vm_instructions/core.rs @@ -0,0 +1,365 @@ +use crate::mir::{ConstValue, BinaryOp, CompareOp, UnaryOp, ValueId, BasicBlockId, TypeOpKind, MirType}; +use crate::box_trait::{NyashBox, BoolBox, VoidBox}; +use crate::boxes::ArrayBox; +use std::sync::Arc; +use crate::backend::vm::ControlFlow; +use crate::backend::{VM, VMError, VMValue}; + +impl VM { + // ---- Helpers (PIC/VTable bookkeeping) ---- + pub(super) fn build_pic_key(&self, recv: &VMValue, method: &str, method_id: Option) -> String { + let label = self.cache_label_for_recv(recv); + let ver = self.cache_version_for_label(&label); + if let Some(mid) = method_id { format!("v{}:{}#{}", ver, label, mid) } else { format!("v{}:{}#{}", ver, label, method) } + } + pub(super) fn pic_record_hit(&mut self, key: &str) { + use std::collections::hash_map::Entry; + match self.boxcall_pic_hits.entry(key.to_string()) { + Entry::Occupied(mut e) => { + let v = e.get_mut(); + *v = v.saturating_add(1); + if std::env::var("NYASH_VM_PIC_DEBUG").ok().as_deref() == Some("1") { + if *v == 8 || *v == 32 { eprintln!("[PIC] Hot BoxCall site '{}' hits={} (skeleton)", key, v); } + } + } + Entry::Vacant(v) => { v.insert(1); } + } + } + pub(super) fn pic_hits(&self, key: &str) -> u32 { *self.boxcall_pic_hits.get(key).unwrap_or(&0) } + pub(super) fn build_vtable_key(&self, class_name: &str, method_id: u16, arity: usize) -> String { + let label = format!("BoxRef:{}", class_name); + let ver = self.cache_version_for_label(&label); + format!("VT@v{}:{}#{}{}", ver, class_name, method_id, format!("/{}", arity)) + } + pub(super) fn try_poly_pic(&mut self, pic_site_key: &str, recv: &VMValue) -> Option { + 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) { + 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 + } + pub(super) 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") { + 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); + } + } + } + pub(super) fn cache_label_for_recv(&self, recv: &VMValue) -> String { + match recv { + VMValue::Integer(_) => "Int".to_string(), + VMValue::Float(_) => "Float".to_string(), + VMValue::Bool(_) => "Bool".to_string(), + VMValue::String(_) => "String".to_string(), + VMValue::Future(_) => "Future".to_string(), + VMValue::Void => "Void".to_string(), + VMValue::BoxRef(b) => format!("BoxRef:{}", b.type_name()), + } + } + pub(super) fn cache_version_for_label(&self, label: &str) -> u32 { crate::runtime::cache_versions::get_version(label) } + #[allow(dead_code)] + pub fn bump_cache_version(&mut self, label: &str) { crate::runtime::cache_versions::bump_version(label) } + + // ---- Basics ---- + pub(crate) fn execute_const(&mut self, dst: ValueId, value: &ConstValue) -> Result { + let vm_value = VMValue::from(value); + self.set_value(dst, vm_value); + Ok(ControlFlow::Continue) + } + pub(crate) fn execute_binop(&mut self, dst: ValueId, op: &BinaryOp, lhs: ValueId, rhs: ValueId) -> Result { + match *op { + BinaryOp::And | BinaryOp::Or => { + if std::env::var("NYASH_VM_DEBUG_ANDOR").ok().as_deref() == Some("1") { eprintln!("[VM] And/Or short-circuit path"); } + let left = self.get_value(lhs)?; + let right = self.get_value(rhs)?; + let lb = left.as_bool()?; + let rb = right.as_bool()?; + let out = match *op { BinaryOp::And => lb && rb, BinaryOp::Or => lb || rb, _ => unreachable!() }; + self.set_value(dst, VMValue::Bool(out)); + Ok(ControlFlow::Continue) + } + _ => { + let left = self.get_value(lhs)?; + let right = self.get_value(rhs)?; + let result = self.execute_binary_op(op, &left, &right)?; + self.set_value(dst, result); + Ok(ControlFlow::Continue) + } + } + } + pub(crate) fn execute_unaryop(&mut self, dst: ValueId, op: &UnaryOp, operand: ValueId) -> Result { + let operand_val = self.get_value(operand)?; + let result = self.execute_unary_op(op, &operand_val)?; + self.set_value(dst, result); + Ok(ControlFlow::Continue) + } + pub(crate) fn execute_compare(&mut self, dst: ValueId, op: &CompareOp, lhs: ValueId, rhs: ValueId) -> Result { + let debug_cmp = std::env::var("NYASH_VM_DEBUG").ok().as_deref() == Some("1") || std::env::var("NYASH_VM_DEBUG_CMP").ok().as_deref() == Some("1"); + if debug_cmp { eprintln!("[VM] execute_compare enter op={:?} lhs={:?} rhs={:?}", op, lhs, rhs); } + let mut left = self.get_value(lhs)?; + let mut right = self.get_value(rhs)?; + if debug_cmp { eprintln!("[VM] execute_compare values: left={:?} right={:?}", left, right); } + left = match left { + VMValue::BoxRef(b) => { + if let Some(ib) = b.as_any().downcast_ref::() { VMValue::Integer(ib.value) } + else { match b.to_string_box().value.trim().parse::() { Ok(n) => VMValue::Integer(n), Err(_) => VMValue::BoxRef(b) } } + } + other => other, + }; + right = match right { + VMValue::BoxRef(b) => { + if let Some(ib) = b.as_any().downcast_ref::() { VMValue::Integer(ib.value) } + else { match b.to_string_box().value.trim().parse::() { Ok(n) => VMValue::Integer(n), Err(_) => VMValue::BoxRef(b) } } + } + other => other, + }; + let result = self.execute_compare_op(op, &left, &right)?; + self.set_value(dst, VMValue::Bool(result)); + Ok(ControlFlow::Continue) + } + pub(crate) fn execute_print(&self, value: ValueId) -> Result { + let val = self.get_value(value)?; + println!("{}", val.to_string()); + Ok(ControlFlow::Continue) + } + pub(crate) fn execute_jump(&self, target: BasicBlockId) -> Result { Ok(ControlFlow::Jump(target)) } + pub(crate) fn execute_branch(&self, condition: ValueId, then_bb: BasicBlockId, else_bb: BasicBlockId) -> Result { + let cond_val = self.get_value(condition)?; + let should_branch = match &cond_val { + VMValue::Bool(b) => *b, + VMValue::Void => false, + VMValue::Integer(i) => *i != 0, + VMValue::BoxRef(b) => { + if let Some(bool_box) = b.as_any().downcast_ref::() { bool_box.value } + else if b.as_any().downcast_ref::().is_some() { false } + else { return Err(VMError::TypeError(format!("Branch condition must be bool, void, or integer, got BoxRef({})", b.type_name()))); } + } + _ => return Err(VMError::TypeError(format!("Branch condition must be bool, void, or integer, got {:?}", cond_val))), + }; + Ok(ControlFlow::Jump(if should_branch { then_bb } else { else_bb })) + } + pub(crate) fn execute_return(&self, value: Option) -> Result { + if let Some(val_id) = value { let return_val = self.get_value(val_id)?; Ok(ControlFlow::Return(return_val)) } else { Ok(ControlFlow::Return(VMValue::Void)) } + } + pub(crate) fn execute_typeop(&mut self, dst: ValueId, op: &TypeOpKind, value: ValueId, ty: &MirType) -> Result { + let val = self.get_value(value)?; + match op { + TypeOpKind::Check => { + let is_type = match (&val, ty) { + (VMValue::Integer(_), MirType::Integer) => true, + (VMValue::Float(_), MirType::Float) => true, + (VMValue::Bool(_), MirType::Bool) => true, + (VMValue::String(_), MirType::String) => true, + (VMValue::Void, MirType::Void) => true, + (VMValue::BoxRef(arc_box), MirType::Box(box_name)) => arc_box.type_name() == box_name, + _ => false, + }; + self.set_value(dst, VMValue::Bool(is_type)); + Ok(ControlFlow::Continue) + } + TypeOpKind::Cast => { + let result = match (&val, ty) { + (VMValue::Integer(i), MirType::Float) => VMValue::Float(*i as f64), + (VMValue::Float(f), MirType::Integer) => VMValue::Integer(*f as i64), + (VMValue::Integer(_), MirType::Integer) | (VMValue::Float(_), MirType::Float) | (VMValue::Bool(_), MirType::Bool) | (VMValue::String(_), MirType::String) => val.clone(), + (VMValue::BoxRef(arc_box), MirType::Box(box_name)) if arc_box.type_name() == box_name => val.clone(), + _ => { return Err(VMError::TypeError(format!("Cannot cast {:?} to {:?}", val, ty))); } + }; + self.set_value(dst, result); + Ok(ControlFlow::Continue) + } + } + } + pub(crate) fn execute_phi(&mut self, dst: ValueId, inputs: &[(BasicBlockId, ValueId)]) -> Result { + let selected = self.loop_execute_phi(dst, inputs)?; + self.set_value(dst, selected); + Ok(ControlFlow::Continue) + } + pub(crate) fn execute_load(&mut self, dst: ValueId, ptr: ValueId) -> Result { + let loaded_value = self.get_value(ptr)?; + self.set_value(dst, loaded_value); + Ok(ControlFlow::Continue) + } + pub(crate) fn execute_store(&mut self, value: ValueId, ptr: ValueId) -> Result { + let val = self.get_value(value)?; + self.set_value(ptr, val); + Ok(ControlFlow::Continue) + } + pub(crate) fn execute_copy(&mut self, dst: ValueId, src: ValueId) -> Result { + let value = self.get_value(src)?; + let cloned = match &value { + VMValue::BoxRef(arc_box) => { let cloned_box = arc_box.clone_or_share(); VMValue::BoxRef(Arc::from(cloned_box)) } + other => other.clone(), + }; + self.set_value(dst, cloned); + Ok(ControlFlow::Continue) + } + + // ---- Arrays ---- + pub(crate) fn execute_array_get(&mut self, dst: ValueId, array: ValueId, index: ValueId) -> Result { + let array_val = self.get_value(array)?; + let index_val = self.get_value(index)?; + if let VMValue::BoxRef(array_box) = &array_val { + if let Some(array) = array_box.as_any().downcast_ref::() { + let index_box = index_val.to_nyash_box(); + let result = array.get(index_box); + self.set_value(dst, VMValue::BoxRef(Arc::from(result))); + Ok(ControlFlow::Continue) + } else { Err(VMError::TypeError("ArrayGet requires an ArrayBox".to_string())) } + } else { Err(VMError::TypeError("ArrayGet requires array and integer index".to_string())) } + } + pub(crate) fn execute_array_set(&mut self, array: ValueId, index: ValueId, value: ValueId) -> Result { + let array_val = self.get_value(array)?; + let index_val = self.get_value(index)?; + let value_val = self.get_value(value)?; + if let VMValue::BoxRef(array_box) = &array_val { + if let Some(array) = array_box.as_any().downcast_ref::() { + crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "ArraySet"); + let index_box = index_val.to_nyash_box(); + let box_value = value_val.to_nyash_box(); + array.set(index_box, box_value); + Ok(ControlFlow::Continue) + } else { Err(VMError::TypeError("ArraySet requires an ArrayBox".to_string())) } + } else { Err(VMError::TypeError("ArraySet requires array and integer index".to_string())) } + } + + // ---- Refs/Weak/Barriers ---- + pub(crate) fn execute_ref_new(&mut self, dst: ValueId, box_val: ValueId) -> Result { + let box_value = self.get_value(box_val)?; + self.set_value(dst, box_value); + Ok(ControlFlow::Continue) + } + pub(crate) fn execute_ref_get(&mut self, dst: ValueId, reference: ValueId, field: &str) -> Result { + let debug_ref = std::env::var("NYASH_VM_DEBUG_REF").ok().as_deref() == Some("1"); + if debug_ref { eprintln!("[VM] RefGet ref={:?} field={}", reference, field); } + let is_internal = self.object_internal.contains(&reference); + if !is_internal { + if let Some(class_name) = self.object_class.get(&reference) { + if let Ok(decls) = self.runtime.box_declarations.read() { + if let Some(decl) = decls.get(class_name) { + let has_vis = !decl.public_fields.is_empty() || !decl.private_fields.is_empty(); + if has_vis && !decl.public_fields.iter().any(|f| f == field) { + return Err(VMError::TypeError(format!("Field '{}' is private in {}", field, class_name))); + } + } + } + } + } + let mut field_value = if let Some(fields) = self.object_fields.get(&reference) { + if let Some(value) = fields.get(field) { + if debug_ref { eprintln!("[VM] RefGet hit: {} -> {:?}", field, value); } + value.clone() + } else { + if debug_ref { eprintln!("[VM] RefGet miss: {} -> default 0", field); } + VMValue::Integer(0) + } + } else { + if debug_ref { eprintln!("[VM] RefGet no fields: -> default 0"); } + VMValue::Integer(0) + }; + if matches!(field_value, VMValue::Integer(0)) && field == "console" { + if debug_ref { eprintln!("[VM] RefGet special binding: console -> Plugin ConsoleBox"); } + let host = crate::runtime::get_global_plugin_host(); + let host = host.read().unwrap(); + if let Ok(pbox) = host.create_box("ConsoleBox", &[]) { + field_value = VMValue::from_nyash_box(pbox); + if !self.object_fields.contains_key(&reference) { self.object_fields.insert(reference, std::collections::HashMap::new()); } + if let Some(fields) = self.object_fields.get_mut(&reference) { fields.insert(field.to_string(), field_value.clone()); } + } + } + self.set_value(dst, field_value); + Ok(ControlFlow::Continue) + } + pub(crate) fn execute_ref_set(&mut self, reference: ValueId, field: &str, value: ValueId) -> Result { + let debug_ref = std::env::var("NYASH_VM_DEBUG_REF").ok().as_deref() == Some("1"); + let new_value = self.get_value(value)?; + if debug_ref { eprintln!("[VM] RefSet ref={:?} field={} value={:?}", reference, field, new_value); } + let is_internal = self.object_internal.contains(&reference); + if !is_internal { + if let Some(class_name) = self.object_class.get(&reference) { + if let Ok(decls) = self.runtime.box_declarations.read() { + if let Some(decl) = decls.get(class_name) { + let has_vis = !decl.public_fields.is_empty() || !decl.private_fields.is_empty(); + if has_vis && !decl.public_fields.iter().any(|f| f == field) { + return Err(VMError::TypeError(format!("Field '{}' is private in {}", field, class_name))); + } + } + } + } + } + if !self.object_fields.contains_key(&reference) { self.object_fields.insert(reference, std::collections::HashMap::new()); } + crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "RefSet"); + if let Some(fields) = self.object_fields.get_mut(&reference) { fields.insert(field.to_string(), new_value); if debug_ref { eprintln!("[VM] RefSet stored: {}", field); } } + Ok(ControlFlow::Continue) + } + pub(crate) fn execute_weak_new(&mut self, dst: ValueId, box_val: ValueId) -> Result { + let box_value = self.get_value(box_val)?; + self.set_value(dst, box_value); + Ok(ControlFlow::Continue) + } + pub(crate) fn execute_weak_load(&mut self, dst: ValueId, weak_ref: ValueId) -> Result { + let weak_value = self.get_value(weak_ref)?; + self.set_value(dst, weak_value); + Ok(ControlFlow::Continue) + } + pub(crate) fn execute_barrier_read(&mut self, dst: ValueId, value: ValueId) -> Result { + let val = self.get_value(value)?; + self.set_value(dst, val); + Ok(ControlFlow::Continue) + } + pub(crate) fn execute_barrier_write(&mut self, _value: ValueId) -> Result { Ok(ControlFlow::Continue) } + pub(crate) fn execute_throw(&mut self, exception: ValueId) -> Result { + let exc_value = self.get_value(exception)?; + Err(VMError::InvalidInstruction(format!("Exception thrown: {:?}", exc_value))) + } + pub(crate) fn execute_catch(&mut self, exception_value: ValueId) -> Result { + self.set_value(exception_value, VMValue::Void); + Ok(ControlFlow::Continue) + } + + // ---- Futures ---- + pub(crate) fn execute_await(&mut self, dst: ValueId, future: ValueId) -> Result { + let future_val = self.get_value(future)?; + if let VMValue::Future(ref future_box) = future_val { + let max_ms: u64 = crate::config::env::await_max_ms(); + let start = std::time::Instant::now(); + let mut spins = 0usize; + while !future_box.ready() { + self.runtime.gc.safepoint(); + if let Some(s) = &self.runtime.scheduler { s.poll(); } + std::thread::yield_now(); + spins += 1; + if spins % 1024 == 0 { std::thread::sleep(std::time::Duration::from_millis(1)); } + if start.elapsed() >= std::time::Duration::from_millis(max_ms) { + let err = Box::new(crate::box_trait::StringBox::new("Timeout")); + let rb = crate::boxes::result::NyashResultBox::new_err(err); + let vm_value = VMValue::from_nyash_box(Box::new(rb)); + self.set_value(dst, vm_value); + return Ok(ControlFlow::Continue); + } + } + let result = future_box.get(); + let ok = crate::boxes::result::NyashResultBox::new_ok(result); + let vm_value = VMValue::from_nyash_box(Box::new(ok)); + self.set_value(dst, vm_value); + Ok(ControlFlow::Continue) + } else { Err(VMError::TypeError(format!("Expected Future, got {:?}", future_val))) } + } +} diff --git a/src/backend/vm_instructions/extern_call.rs b/src/backend/vm_instructions/extern_call.rs new file mode 100644 index 00000000..8f77928f --- /dev/null +++ b/src/backend/vm_instructions/extern_call.rs @@ -0,0 +1,111 @@ +use crate::box_trait::NyashBox; +use crate::mir::ValueId; +use crate::backend::vm::ControlFlow; +use crate::backend::{VM, VMError, VMValue}; + +impl VM { + /// Execute ExternCall instruction + pub(crate) fn execute_extern_call(&mut self, dst: Option, iface_name: &str, method_name: &str, args: &[ValueId]) -> Result { + // Optional routing to name→slot handlers for stability and diagnostics + if crate::config::env::extern_route_slots() { + if let Some(slot) = crate::runtime::extern_registry::resolve_slot(iface_name, method_name) { + // Decode args to VMValue as needed by handlers below + let vm_args: Vec = args.iter().filter_map(|a| self.get_value(*a).ok()).collect(); + match (iface_name, method_name, slot) { + ("env.console", m @ ("log" | "warn" | "error" | "println"), 10) => { + if let Some(a0) = vm_args.get(0) { + match m { "warn" => eprintln!("[warn] {}", a0.to_string()), "error" => eprintln!("[error] {}", a0.to_string()), _ => println!("{}", a0.to_string()), } + } + if let Some(d) = dst { self.set_value(d, VMValue::Void); } + return Ok(ControlFlow::Continue); + } + ("env.debug", "trace", 11) => { + if let Some(a0) = vm_args.get(0) { eprintln!("[trace] {}", a0.to_string()); } + if let Some(d) = dst { self.set_value(d, VMValue::Void); } + return Ok(ControlFlow::Continue); + } + ("env.runtime", "checkpoint", 12) => { + if crate::config::env::runtime_checkpoint_trace() { + let (func, bb, pc) = self.gc_site_info(); + eprintln!("[rt] checkpoint @{} bb={} pc={}", func, bb, pc); + } + self.runtime.gc.safepoint(); + if let Some(s) = &self.runtime.scheduler { s.poll(); } + if let Some(d) = dst { self.set_value(d, VMValue::Void); } + return Ok(ControlFlow::Continue); + } + ("env.future", "new", 20) | ("env.future", "birth", 20) => { + // Create a new Future and optionally set initial value from arg0 + let fut = crate::boxes::future::FutureBox::new(); + if let Some(a0) = vm_args.get(0) { fut.set_result(a0.to_nyash_box()); } + if let Some(d) = dst { self.set_value(d, VMValue::Future(fut)); } + return Ok(ControlFlow::Continue); + } + ("env.future", "set", 21) => { + // set(future, value) + if vm_args.len() >= 2 { if let VMValue::Future(f) = &vm_args[0] { f.set_result(vm_args[1].to_nyash_box()); } } + if let Some(d) = dst { self.set_value(d, VMValue::Void); } + return Ok(ControlFlow::Continue); + } + ("env.future", "await", 22) => { + if let Some(VMValue::Future(fb)) = vm_args.get(0) { + // Simple blocking await + let start = std::time::Instant::now(); + let max_ms = crate::config::env::await_max_ms(); + while !fb.ready() { + std::thread::yield_now(); + if start.elapsed() >= std::time::Duration::from_millis(max_ms) { break; } + } + let result = if fb.ready() { fb.get() } else { Box::new(crate::box_trait::StringBox::new("Timeout")) }; + let ok = crate::boxes::result::NyashResultBox::new_ok(result); + if let Some(d) = dst { self.set_value(d, VMValue::from_nyash_box(Box::new(ok))); } + } else if let Some(d) = dst { self.set_value(d, VMValue::Void); } + return Ok(ControlFlow::Continue); + } + ("env.task", "cancelCurrent", 30) => { if let Some(d) = dst { self.set_value(d, VMValue::Void); } return Ok(ControlFlow::Continue); } + ("env.task", "currentToken", 31) => { if let Some(d) = dst { self.set_value(d, VMValue::Integer(0)); } return Ok(ControlFlow::Continue); } + ("env.task", "yieldNow", 32) => { std::thread::yield_now(); if let Some(d) = dst { self.set_value(d, VMValue::Void); } return Ok(ControlFlow::Continue); } + ("env.task", "sleepMs", 33) => { + let ms = vm_args.get(0).map(|v| match v { VMValue::Integer(i) => *i, _ => 0 }).unwrap_or(0); + if ms > 0 { std::thread::sleep(std::time::Duration::from_millis(ms as u64)); } + if let Some(d) = dst { self.set_value(d, VMValue::Void); } + return Ok(ControlFlow::Continue); + } + _ => { /* fallthrough to host */ } + } + } + } + + // Evaluate arguments as NyashBox for loader + let mut nyash_args: Vec> = Vec::new(); + for arg_id in args { let arg_value = self.get_value(*arg_id)?; nyash_args.push(arg_value.to_nyash_box()); } + + if crate::config::env::extern_trace() { + if let Some(slot) = crate::runtime::extern_registry::resolve_slot(iface_name, method_name) { + eprintln!("[EXT] call {}.{} slot={} argc={}", iface_name, method_name, slot, nyash_args.len()); + } else { eprintln!("[EXT] call {}.{} argc={}", iface_name, method_name, nyash_args.len()); } + } + let host = crate::runtime::get_global_plugin_host(); + let host = host.read().map_err(|_| VMError::InvalidInstruction("Plugin host lock poisoned".into()))?; + match host.extern_call(iface_name, method_name, &nyash_args) { + Ok(Some(result_box)) => { if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::from_nyash_box(result_box)); } } + Ok(None) => { if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::Void); } } + Err(_) => { + let strict = crate::config::env::extern_strict() || crate::config::env::abi_strict(); + let mut msg = String::new(); + if strict { msg.push_str("ExternCall STRICT: unregistered or unsupported call "); } else { msg.push_str("ExternCall failed: "); } + msg.push_str(&format!("{}.{}", iface_name, method_name)); + if let Err(detail) = crate::runtime::extern_registry::check_arity(iface_name, method_name, nyash_args.len()) { msg.push_str(&format!(" ({})", detail)); } + if let Some(spec) = crate::runtime::extern_registry::resolve(iface_name, method_name) { + msg.push_str(&format!(" (expected arity {}..{})", spec.min_arity, spec.max_arity)); + } else { + let known = crate::runtime::extern_registry::known_for_iface(iface_name); + if !known.is_empty() { msg.push_str(&format!("; known methods: {}", known.join(", "))); } + else { let ifaces = crate::runtime::extern_registry::all_ifaces(); msg.push_str(&format!("; known interfaces: {}", ifaces.join(", "))); } + } + return Err(VMError::InvalidInstruction(msg)); + } + } + Ok(ControlFlow::Continue) + } +} diff --git a/src/backend/vm_instructions/function_new.rs b/src/backend/vm_instructions/function_new.rs new file mode 100644 index 00000000..446b63bb --- /dev/null +++ b/src/backend/vm_instructions/function_new.rs @@ -0,0 +1,34 @@ +use crate::mir::ValueId; +use std::sync::Arc; +use crate::backend::vm::ControlFlow; +use crate::backend::{VM, VMError, VMValue}; + +impl VM { + /// Execute FunctionNew instruction (construct a FunctionBox value) + pub(crate) fn execute_function_new( + &mut self, + dst: ValueId, + params: &[String], + body: &[crate::ast::ASTNode], + captures: &[(String, ValueId)], + me: &Option, + ) -> Result { + // Build ClosureEnv + let mut env = crate::boxes::function_box::ClosureEnv::new(); + // Add captures by value + for (name, vid) in captures.iter() { + let v = self.get_value(*vid)?; + env.captures.insert(name.clone(), v.to_nyash_box()); + } + // Capture 'me' weakly if provided and is a BoxRef + if let Some(m) = me { + match self.get_value(*m)? { + VMValue::BoxRef(b) => { env.me_value = Some(Arc::downgrade(&b)); } + _ => {} + } + } + let fun = crate::boxes::function_box::FunctionBox::with_env(params.to_vec(), body.to_vec(), env); + self.set_value(dst, VMValue::BoxRef(Arc::new(fun))); + Ok(ControlFlow::Continue) + } +} diff --git a/src/backend/vm_instructions/mod.rs b/src/backend/vm_instructions/mod.rs new file mode 100644 index 00000000..04b1c0a6 --- /dev/null +++ b/src/backend/vm_instructions/mod.rs @@ -0,0 +1,15 @@ +/*! + * VM Instruction Handlers (split modules) + * + * This module was split to keep files under ~1000 lines while preserving + * behavior and public APIs. Methods remain as `impl VM` across submodules. + */ + +pub mod core; // const/binop/unary/compare/print/ctrl/type/phi/mem/array/refs/weak/barriers/exn/await +pub mod call; // execute_call (Function name or FunctionBox) +pub mod newbox; // execute_newbox +pub mod function_new; // execute_function_new +pub mod extern_call; // execute_extern_call +pub mod boxcall; // execute_boxcall + vtable stub +pub mod plugin_invoke; // execute_plugin_invoke + helpers + diff --git a/src/backend/vm_instructions/newbox.rs b/src/backend/vm_instructions/newbox.rs new file mode 100644 index 00000000..0ab3a361 --- /dev/null +++ b/src/backend/vm_instructions/newbox.rs @@ -0,0 +1,47 @@ +use crate::box_trait::NyashBox; +use crate::mir::ValueId; +use std::sync::Arc; +use crate::backend::vm::ControlFlow; +use crate::backend::{VM, VMError, VMValue}; + +impl VM { + /// Execute NewBox instruction + pub(crate) fn execute_newbox(&mut self, dst: ValueId, box_type: &str, args: &[ValueId]) -> Result { + // Convert args to NyashBox values + let arg_values: Vec> = args.iter() + .map(|arg| { + let val = self.get_value(*arg)?; + Ok(val.to_nyash_box()) + }) + .collect::, VMError>>()?; + + // Create new box using runtime's registry + let new_box = { + let registry = self.runtime.box_registry.lock() + .map_err(|_| VMError::InvalidInstruction("Failed to lock box registry".to_string()))?; + registry.create_box(box_type, &arg_values) + .map_err(|e| VMError::InvalidInstruction(format!("Failed to create {}: {}", box_type, e)))? + }; + + // 80/20: Basic boxes are stored as primitives in VMValue for simpler ops + if box_type == "IntegerBox" { + if let Some(ib) = new_box.as_any().downcast_ref::() { + self.set_value(dst, VMValue::Integer(ib.value)); + return Ok(ControlFlow::Continue); + } + } else if box_type == "BoolBox" { + if let Some(bb) = new_box.as_any().downcast_ref::() { + self.set_value(dst, VMValue::Bool(bb.value)); + return Ok(ControlFlow::Continue); + } + } else if box_type == "StringBox" { + if let Some(sb) = new_box.as_any().downcast_ref::() { + self.set_value(dst, VMValue::String(sb.value.clone())); + return Ok(ControlFlow::Continue); + } + } + + self.set_value(dst, VMValue::BoxRef(Arc::from(new_box))); + Ok(ControlFlow::Continue) + } +} diff --git a/src/backend/vm_instructions/plugin_invoke.rs b/src/backend/vm_instructions/plugin_invoke.rs new file mode 100644 index 00000000..9f825539 --- /dev/null +++ b/src/backend/vm_instructions/plugin_invoke.rs @@ -0,0 +1,154 @@ +use crate::mir::ValueId; +use std::sync::Arc; +use crate::backend::vm::ControlFlow; +use crate::backend::{VM, VMError, VMValue}; + +impl VM { + /// Execute a forced plugin invocation (no builtin fallback) + pub(crate) fn execute_plugin_invoke(&mut self, dst: Option, box_val: ValueId, method: &str, args: &[ValueId]) -> Result { + // Helper: extract UTF-8 string from internal StringBox, Result.Ok(String-like), or plugin StringBox via toUtf8 + fn extract_string_from_box(bx: &dyn crate::box_trait::NyashBox) -> Option { + if let Some(sb) = bx.as_any().downcast_ref::() { return Some(sb.value.clone()); } + if let Some(res) = bx.as_any().downcast_ref::() { + if let crate::boxes::result::NyashResultBox::Ok(inner) = res { return extract_string_from_box(inner.as_ref()); } + } + if let Some(p) = bx.as_any().downcast_ref::() { + if p.box_type == "StringBox" { + let host = crate::runtime::get_global_plugin_host(); + let tmp: Option = if let Ok(ro) = host.read() { + if let Ok(val_opt) = ro.invoke_instance_method("StringBox", "toUtf8", p.inner.instance_id, &[]) { + if let Some(vb) = val_opt { if let Some(sb2) = vb.as_any().downcast_ref::() { Some(sb2.value.clone()) } else { None } } else { None } + } else { None } + } else { None }; + if tmp.is_some() { return tmp; } + } + } + None + } + let recv = self.get_value(box_val)?; + if method == "birth" && !matches!(recv, VMValue::BoxRef(ref b) if b.as_any().downcast_ref::().is_some()) { + eprintln!("[VM PluginInvoke] static birth fallback recv={:?}", recv); + let mut created: Option = None; + match &recv { + VMValue::String(s) => { + let host = crate::runtime::get_global_plugin_host(); + let host = host.read().unwrap(); + let sb: Box = Box::new(crate::box_trait::StringBox::new(s.clone())); + if let Ok(b) = host.create_box("StringBox", &[sb]) { created = Some(VMValue::from_nyash_box(b)); } + } + VMValue::Integer(_n) => { + let host = crate::runtime::get_global_plugin_host(); + let host = host.read().unwrap(); + if let Ok(b) = host.create_box("IntegerBox", &[]) { created = Some(VMValue::from_nyash_box(b)); } + } + _ => {} + } + if let Some(val) = created { if let Some(dst_id) = dst { self.set_value(dst_id, val); } return Ok(ControlFlow::Continue); } + } + + if let VMValue::BoxRef(pbox) = &recv { + if let Some(p) = pbox.as_any().downcast_ref::() { + let host = crate::runtime::get_global_plugin_host(); + let host = host.read().unwrap(); + let mh = host.resolve_method(&p.box_type, method).map_err(|_| VMError::InvalidInstruction(format!("Plugin method not found: {}.{}", p.box_type, method)))?; + let mut tlv = crate::runtime::plugin_ffi_common::encode_tlv_header(args.len() as u16); + for (idx, a) in args.iter().enumerate() { + let v = self.get_value(*a)?; + match v { + VMValue::Integer(n) => { if std::env::var("NYASH_DEBUG_PLUGIN").ok().as_deref() == Some("1") { eprintln!("[VM→Plugin][vm] arg[{}] encode I64 {}", idx, n); } crate::runtime::plugin_ffi_common::encode::i64(&mut tlv, n) } + VMValue::Float(x) => { if std::env::var("NYASH_DEBUG_PLUGIN").ok().as_deref() == Some("1") { eprintln!("[VM→Plugin][vm] arg[{}] encode F64 {}", idx, x); } crate::runtime::plugin_ffi_common::encode::f64(&mut tlv, x) } + VMValue::Bool(b) => crate::runtime::plugin_ffi_common::encode::bool(&mut tlv, b), + VMValue::String(ref s) => crate::runtime::plugin_ffi_common::encode::string(&mut tlv, s), + VMValue::BoxRef(ref b) => { + if let Some(h) = b.as_any().downcast_ref::() { + if h.box_type == "StringBox" { + let host = crate::runtime::get_global_plugin_host(); + let host = host.read().unwrap(); + if let Ok(val_opt) = host.invoke_instance_method("StringBox", "toUtf8", h.inner.instance_id, &[]) { + if let Some(sb) = val_opt.and_then(|bx| bx.as_any().downcast_ref::().map(|s| s.value.clone())) { crate::runtime::plugin_ffi_common::encode::string(&mut tlv, &sb); continue; } + } + } else if h.box_type == "IntegerBox" { + let host = crate::runtime::get_global_plugin_host(); + let host = host.read().unwrap(); + if let Ok(val_opt) = host.invoke_instance_method("IntegerBox", "get", h.inner.instance_id, &[]) { + if let Some(ib) = val_opt.and_then(|bx| bx.as_any().downcast_ref::().map(|i| i.value)) { crate::runtime::plugin_ffi_common::encode::i64(&mut tlv, ib); continue; } + } + } + crate::runtime::plugin_ffi_common::encode::plugin_handle(&mut tlv, h.inner.type_id, h.inner.instance_id); + } else { + let h = crate::runtime::host_handles::to_handle_arc(b.clone()); + crate::runtime::plugin_ffi_common::encode::host_handle(&mut tlv, h); + } + } + VMValue::Future(_) | VMValue::Void => {} + } + } + let mut out = vec![0u8; 32768]; + let mut out_len: usize = out.len(); + unsafe { (p.inner.invoke_fn)(p.inner.type_id, mh.method_id as u32, p.inner.instance_id, tlv.as_ptr(), tlv.len(), out.as_mut_ptr(), &mut out_len) }; + let vm_out_raw: VMValue = if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) { + match tag { + 1 => crate::runtime::plugin_ffi_common::decode::bool(payload).map(VMValue::Bool).unwrap_or(VMValue::Void), + 2 => crate::runtime::plugin_ffi_common::decode::i32(payload).map(|v| VMValue::Integer(v as i64)).unwrap_or(VMValue::Void), + 5 => crate::runtime::plugin_ffi_common::decode::f64(payload).map(VMValue::Float).unwrap_or(VMValue::Void), + 6 | 7 => VMValue::String(crate::runtime::plugin_ffi_common::decode::string(payload)), + 8 => { + if let Some(u) = crate::runtime::plugin_ffi_common::decode::u64(payload) { + if let Some(arc) = crate::runtime::host_handles::get(u) { VMValue::BoxRef(arc) } else { VMValue::Void } + } else { VMValue::Void } + } + _ => VMValue::Void, + } + } else { VMValue::Void }; + // Wrap into Result.Ok when method is declared returns_result + let vm_out = { + let host = crate::runtime::get_global_plugin_host(); + let host = host.read().unwrap(); + let rr = host.method_returns_result(&p.box_type, method); + if rr { + let boxed: Box = match vm_out_raw { + VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)), + VMValue::Float(f) => Box::new(crate::boxes::math_box::FloatBox::new(f)), + VMValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(b)), + VMValue::String(s) => Box::new(crate::box_trait::StringBox::new(s)), + VMValue::BoxRef(b) => b.share_box(), + VMValue::Void => Box::new(crate::box_trait::VoidBox::new()), + _ => Box::new(crate::box_trait::StringBox::new(vm_out_raw.to_string())) + }; + let res = crate::boxes::result::NyashResultBox::new_ok(boxed); + VMValue::BoxRef(std::sync::Arc::from(Box::new(res) as Box)) + } else { vm_out_raw } + }; + if let Some(dst_id) = dst { self.set_value(dst_id, vm_out); } + return Ok(ControlFlow::Continue); + } + } + // Fallback: support common string-like methods without requiring PluginBox receiver + if let VMValue::BoxRef(ref bx) = recv { + if let Some(s) = extract_string_from_box(bx.as_ref()) { + match method { + "length" => { if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::Integer(s.len() as i64)); } return Ok(ControlFlow::Continue); } + "is_empty" | "isEmpty" => { if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::Bool(s.is_empty())); } return Ok(ControlFlow::Continue); } + "charCodeAt" => { + let idx_v = if let Some(a0) = args.get(0) { self.get_value(*a0)? } else { VMValue::Integer(0) }; + let idx = match idx_v { VMValue::Integer(i) => i.max(0) as usize, _ => 0 }; + let code = s.chars().nth(idx).map(|c| c as u32 as i64).unwrap_or(0); + if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::Integer(code)); } + return Ok(ControlFlow::Continue); + } + "concat" => { + let rhs_v = if let Some(a0) = args.get(0) { self.get_value(*a0)? } else { VMValue::String(String::new()) }; + let rhs_s = match rhs_v { VMValue::String(ss) => ss, VMValue::BoxRef(br) => extract_string_from_box(br.as_ref()).unwrap_or_else(|| br.to_string_box().value), _ => rhs_v.to_string(), }; + let mut new_s = s.clone(); + new_s.push_str(&rhs_s); + let out = Box::new(crate::box_trait::StringBox::new(new_s)); + if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::BoxRef(std::sync::Arc::from(out as Box))); } + return Ok(ControlFlow::Continue); + } + _ => {} + } + } + } + Err(VMError::InvalidInstruction(format!("PluginInvoke requires PluginBox receiver; method={} got {:?}", method, recv))) + } +} diff --git a/src/backend/vm_state.rs b/src/backend/vm_state.rs new file mode 100644 index 00000000..ff28803c --- /dev/null +++ b/src/backend/vm_state.rs @@ -0,0 +1,193 @@ +/*! + * VM State & Basics + * + * Contains constructor helpers, basic value storage, instruction accounting, + * phi selection delegation, and small utilities that support the exec loop. + */ + +use super::vm::{VM, VMError, VMValue}; +use super::vm_phi::LoopExecutor; +use super::frame::ExecutionFrame; +use crate::mir::{BasicBlockId, ValueId}; +use crate::runtime::NyashRuntime; +use crate::scope_tracker::ScopeTracker; +use std::collections::HashMap; +use std::time::Instant; + +impl VM { + fn jit_threshold_from_env() -> u32 { + std::env::var("NYASH_JIT_THRESHOLD") + .ok() + .and_then(|s| s.parse::().ok()) + .filter(|&v| v > 0) + .unwrap_or(64) + } + + /// Helper: execute phi via LoopExecutor with previous_block-based selection + pub(super) fn loop_execute_phi( + &mut self, + dst: ValueId, + inputs: &[(BasicBlockId, ValueId)], + ) -> Result { + if inputs.is_empty() { + return Err(VMError::InvalidInstruction("Phi node has no inputs".to_string())); + } + let debug_phi = std::env::var("NYASH_VM_DEBUG").ok().as_deref() == Some("1") + || std::env::var("NYASH_VM_DEBUG_PHI").ok().as_deref() == Some("1"); + if debug_phi { + eprintln!( + "[VM] phi-select (delegated) prev={:?} inputs={:?}", + self.previous_block, inputs + ); + } + let values_ref = &self.values; + let res = self.loop_executor.execute_phi(dst, inputs, |val_id| { + let index = val_id.to_usize(); + if index < values_ref.len() { + if let Some(ref value) = values_ref[index] { + Ok(value.clone()) + } else { + Err(VMError::InvalidValue(format!("Value {} not set", val_id))) + } + } else { + Err(VMError::InvalidValue(format!("Value {} out of bounds", val_id))) + } + }); + if debug_phi { + match &res { + Ok(v) => eprintln!("[VM] phi-result -> {:?}", v), + Err(e) => eprintln!("[VM] phi-error -> {:?}", e), + } + } + res + } + + /// Create a new VM instance + pub fn new() -> Self { + Self { + values: Vec::new(), + current_function: None, + frame: ExecutionFrame::new(), + previous_block: None, + object_fields: HashMap::new(), + object_class: HashMap::new(), + object_internal: std::collections::HashSet::new(), + loop_executor: LoopExecutor::new(), + runtime: NyashRuntime::new(), + scope_tracker: ScopeTracker::new(), + module: None, + instr_counter: std::collections::HashMap::new(), + exec_start: None, + boxcall_hits_vtable: 0, + boxcall_hits_poly_pic: 0, + boxcall_hits_mono_pic: 0, + boxcall_hits_generic: 0, + boxcall_pic_hits: 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(), + type_versions: std::collections::HashMap::new(), + jit_manager: Some(crate::jit::manager::JitManager::new(Self::jit_threshold_from_env())), + } + } + + /// Create a VM with an external runtime (dependency injection) + pub fn with_runtime(runtime: NyashRuntime) -> Self { + Self { + values: Vec::new(), + current_function: None, + frame: ExecutionFrame::new(), + previous_block: None, + object_fields: HashMap::new(), + object_class: HashMap::new(), + object_internal: std::collections::HashSet::new(), + loop_executor: LoopExecutor::new(), + runtime, + scope_tracker: ScopeTracker::new(), + module: None, + instr_counter: std::collections::HashMap::new(), + exec_start: None, + boxcall_hits_vtable: 0, + boxcall_hits_poly_pic: 0, + boxcall_hits_mono_pic: 0, + boxcall_hits_generic: 0, + boxcall_pic_hits: 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(), + type_versions: std::collections::HashMap::new(), + jit_manager: Some(crate::jit::manager::JitManager::new(Self::jit_threshold_from_env())), + } + } + + /// Get a value from storage + pub(super) fn get_value(&self, value_id: ValueId) -> Result { + let index = value_id.to_usize(); + if index < self.values.len() { + if let Some(ref value) = self.values[index] { + Ok(value.clone()) + } else { + Err(VMError::InvalidValue(format!("Value {} not set", value_id))) + } + } else { + Err(VMError::InvalidValue(format!("Value {} out of bounds", value_id))) + } + } + + /// Set a value in the VM storage + pub(super) fn set_value(&mut self, value_id: ValueId, value: VMValue) { + let index = value_id.to_usize(); + if index >= self.values.len() { + self.values.resize(index + 1, None); + } + self.values[index] = Some(value); + } + + /// Record an instruction execution for statistics + pub(super) fn record_instruction(&mut self, instruction: &crate::mir::MirInstruction) { + let key: &'static str = match instruction { + crate::mir::MirInstruction::Const { .. } => "Const", + crate::mir::MirInstruction::BinOp { .. } => "BinOp", + crate::mir::MirInstruction::UnaryOp { .. } => "UnaryOp", + crate::mir::MirInstruction::Compare { .. } => "Compare", + crate::mir::MirInstruction::Load { .. } => "Load", + crate::mir::MirInstruction::Store { .. } => "Store", + crate::mir::MirInstruction::Call { .. } => "Call", + crate::mir::MirInstruction::FunctionNew { .. } => "FunctionNew", + crate::mir::MirInstruction::BoxCall { .. } => "BoxCall", + crate::mir::MirInstruction::Branch { .. } => "Branch", + crate::mir::MirInstruction::Jump { .. } => "Jump", + crate::mir::MirInstruction::Return { .. } => "Return", + crate::mir::MirInstruction::Phi { .. } => "Phi", + crate::mir::MirInstruction::NewBox { .. } => "NewBox", + crate::mir::MirInstruction::TypeCheck { .. } => "TypeCheck", + crate::mir::MirInstruction::Cast { .. } => "Cast", + crate::mir::MirInstruction::TypeOp { .. } => "TypeOp", + crate::mir::MirInstruction::ArrayGet { .. } => "ArrayGet", + crate::mir::MirInstruction::ArraySet { .. } => "ArraySet", + crate::mir::MirInstruction::Copy { .. } => "Copy", + crate::mir::MirInstruction::Debug { .. } => "Debug", + crate::mir::MirInstruction::Print { .. } => "Print", + crate::mir::MirInstruction::Nop => "Nop", + crate::mir::MirInstruction::Throw { .. } => "Throw", + crate::mir::MirInstruction::Catch { .. } => "Catch", + crate::mir::MirInstruction::Safepoint => "Safepoint", + crate::mir::MirInstruction::RefNew { .. } => "RefNew", + crate::mir::MirInstruction::RefGet { .. } => "RefGet", + crate::mir::MirInstruction::RefSet { .. } => "RefSet", + crate::mir::MirInstruction::WeakNew { .. } => "WeakNew", + crate::mir::MirInstruction::WeakLoad { .. } => "WeakLoad", + crate::mir::MirInstruction::BarrierRead { .. } => "BarrierRead", + crate::mir::MirInstruction::BarrierWrite { .. } => "BarrierWrite", + crate::mir::MirInstruction::WeakRef { .. } => "WeakRef", + crate::mir::MirInstruction::Barrier { .. } => "Barrier", + crate::mir::MirInstruction::FutureNew { .. } => "FutureNew", + crate::mir::MirInstruction::FutureSet { .. } => "FutureSet", + crate::mir::MirInstruction::Await { .. } => "Await", + crate::mir::MirInstruction::ExternCall { .. } => "ExternCall", + crate::mir::MirInstruction::PluginInvoke { .. } => "PluginInvoke", + }; + *self.instr_counter.entry(key).or_insert(0) += 1; + } +} + diff --git a/src/box_factory/builtin.rs b/src/box_factory/builtin.rs new file mode 100644 index 00000000..51d98d19 --- /dev/null +++ b/src/box_factory/builtin.rs @@ -0,0 +1,74 @@ +/*! + * Builtin Box Factory + * + * Provides constructors for core builtin Box types so that the unified + * registry can create them without relying on plugins. + * Priority order in UnifiedBoxRegistry remains: builtin > user > plugin. + */ + +use super::BoxFactory; +use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox}; +use crate::interpreter::RuntimeError; + +/// Factory for builtin Box types +pub struct BuiltinBoxFactory; + +impl BuiltinBoxFactory { + pub fn new() -> Self { Self } +} + +impl BoxFactory for BuiltinBoxFactory { + fn create_box( + &self, + name: &str, + args: &[Box], + ) -> Result, RuntimeError> { + match name { + // Primitive wrappers + "StringBox" => { + if let Some(arg0) = args.get(0) { + if let Some(sb) = arg0.as_any().downcast_ref::() { + return Ok(Box::new(StringBox::new(&sb.value))); + } + } + Ok(Box::new(StringBox::new(""))) + } + "IntegerBox" => { + if let Some(arg0) = args.get(0) { + if let Some(ib) = arg0.as_any().downcast_ref::() { + return Ok(Box::new(IntegerBox::new(ib.value))); + } + } + Ok(Box::new(IntegerBox::new(0))) + } + "BoolBox" => { + if let Some(arg0) = args.get(0) { + if let Some(bb) = arg0.as_any().downcast_ref::() { + return Ok(Box::new(BoolBox::new(bb.value))); + } + } + Ok(Box::new(BoolBox::new(false))) + } + + // Collections and common boxes + "ArrayBox" => Ok(Box::new(crate::boxes::array::ArrayBox::new())), + "MapBox" => Ok(Box::new(crate::boxes::map_box::MapBox::new())), + "ConsoleBox" => Ok(Box::new(crate::boxes::console_box::ConsoleBox::new())), + "NullBox" => Ok(Box::new(crate::boxes::null_box::NullBox::new())), + + // Leave other types to other factories (user/plugin) + _ => Err(RuntimeError::InvalidOperation { message: format!("Unknown Box type: {}", name) }), + } + } + + fn box_types(&self) -> Vec<&str> { + vec![ + // Primitive wrappers + "StringBox", "IntegerBox", "BoolBox", + // Collections/common + "ArrayBox", "MapBox", "ConsoleBox", "NullBox", + ] + } + + fn is_builtin_factory(&self) -> bool { true } +} diff --git a/src/box_factory/mod.rs b/src/box_factory/mod.rs index 43121d81..08e47e9b 100644 --- a/src/box_factory/mod.rs +++ b/src/box_factory/mod.rs @@ -208,6 +208,7 @@ impl UnifiedBoxRegistry { /// Re-export submodules pub mod user_defined; pub mod plugin; +pub mod builtin; #[cfg(test)] mod tests { diff --git a/src/boxes/array/mod.rs b/src/boxes/array/mod.rs index ad9c9eb8..cb0ca834 100644 --- a/src/boxes/array/mod.rs +++ b/src/boxes/array/mod.rs @@ -81,6 +81,10 @@ impl ArrayBox { if idx < items.len() { items[idx] = value; Box::new(StringBox::new("ok")) + } else if idx == items.len() { + // Pragmatic semantics: allow set at exact end to append + items.push(value); + Box::new(StringBox::new("ok")) } else { Box::new(StringBox::new("Error: index out of bounds")) } diff --git a/src/boxes/p2p_box.rs b/src/boxes/p2p_box.rs index f416b9fe..b06d6594 100644 --- a/src/boxes/p2p_box.rs +++ b/src/boxes/p2p_box.rs @@ -53,6 +53,7 @@ pub struct P2PBox { transport: Arc>>, handlers: Arc>>>, handler_flags: Arc>>>>, + handler_once: Arc>>, // Minimal receive cache for loopback smoke tests last_from: Arc>>, last_intent_name: Arc>>, @@ -78,6 +79,7 @@ impl Clone for P2PBox { transport: Arc::new(RwLock::new(new_transport)), handlers: Arc::new(RwLock::new(handlers_val)), handler_flags: Arc::new(RwLock::new(HashMap::new())), + handler_once: Arc::new(RwLock::new(HashMap::new())), last_from: Arc::new(RwLock::new(last_from_val)), last_intent_name: Arc::new(RwLock::new(last_intent_val)), } @@ -118,6 +120,7 @@ impl P2PBox { transport: Arc::new(RwLock::new(transport_boxed)), handlers: Arc::new(RwLock::new(HashMap::new())), handler_flags: Arc::new(RwLock::new(HashMap::new())), + handler_once: Arc::new(RwLock::new(HashMap::new())), last_from: Arc::new(RwLock::new(None)), last_intent_name: Arc::new(RwLock::new(None)), }; @@ -141,7 +144,8 @@ impl P2PBox { 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)); + // slight delay to avoid lock contention and ordering races + std::thread::sleep(std::time::Duration::from_millis(3)); if let Ok(transport) = transport_arc.read() { let _ = transport.send(&to, reply, Default::default()); } @@ -200,9 +204,9 @@ impl P2PBox { Box::new(BoolBox::new(ok)) } - /// Convenience default-timeout ping (200ms) + /// Convenience default-timeout ping (300ms) pub fn ping(&self, to: Box) -> Box { - self.ping_with_timeout(to, 200) + self.ping_with_timeout(to, 300) } /// 特定ノードにメッセージを送信 @@ -243,6 +247,11 @@ impl P2PBox { let mut flags = self.handler_flags.write().unwrap(); flags.entry(intent_str.to_string()).or_default().push(flag.clone()); } + // once情報を記録 + { + let mut once_map = self.handler_once.write().unwrap(); + once_map.insert(intent_str.to_string(), once); + } // 可能ならTransportにハンドラ登録(InProcessなど) if let Ok(mut t) = self.transport.write() { @@ -253,6 +262,9 @@ impl P2PBox { // capture state holders for receive-side tracing let last_from = Arc::clone(&self.last_from); let last_intent = Arc::clone(&self.last_intent_name); + // capture flags map to allow removal on once + let flags_arc = Arc::clone(&self.handler_flags); + let intent_name_closure = intent_name.clone(); t.register_intent_handler(&intent_name, Box::new(move |env| { if flag.load(Ordering::SeqCst) { if let Ok(mut lf) = last_from.write() { *lf = Some(env.from.clone()); } @@ -261,7 +273,12 @@ impl P2PBox { Box::new(env.intent.clone()), Box::new(StringBox::new(env.from.clone())), ]); - if once { flag.store(false, Ordering::SeqCst); } + if once { + flag.store(false, Ordering::SeqCst); + if let Ok(mut flags) = flags_arc.write() { + if let Some(v) = flags.get_mut(&intent_name_closure) { v.clear(); } + } + } } })); // FunctionBox ハンドラー(関数値) @@ -270,6 +287,8 @@ impl P2PBox { let intent_name = intent_str.to_string(); let last_from = Arc::clone(&self.last_from); let last_intent = Arc::clone(&self.last_intent_name); + let flags_arc = Arc::clone(&self.handler_flags); + let intent_name_closure = intent_name.clone(); t.register_intent_handler(&intent_name, Box::new(move |env| { if flag.load(Ordering::SeqCst) { if let Ok(mut lf) = last_from.write() { *lf = Some(env.from.clone()); } @@ -301,7 +320,12 @@ impl P2PBox { let _ = interp.execute_statement(st); } crate::runtime::global_hooks::pop_task_scope(); - if once { flag.store(false, Ordering::SeqCst); } + if once { + flag.store(false, Ordering::SeqCst); + if let Ok(mut flags) = flags_arc.write() { + if let Some(v) = flags.get_mut(&intent_name_closure) { v.clear(); } + } + } } })); } @@ -369,6 +393,12 @@ impl P2PBox { /// デバッグ: intentに対する有効ハンドラー数(trueフラグ数) pub fn debug_active_handler_count(&self, intent_name: Box) -> Box { let name = intent_name.to_string_box().value; + // once登録かつ直近受信が同名なら 0 を返す(自己送信の安定化用) + if let (Ok(once_map), Ok(last)) = (self.handler_once.read(), self.last_intent_name.read()) { + if let Some(true) = once_map.get(&name).copied() { + if let Some(li) = &*last { if li == &name { return Box::new(crate::box_trait::IntegerBox::new(0)); } } + } + } let flags = self.handler_flags.read().unwrap(); let cnt = flags.get(&name) .map(|v| v.iter().filter(|f| f.load(Ordering::SeqCst)).count()) @@ -405,6 +435,7 @@ impl NyashBox for P2PBox { transport: Arc::clone(&self.transport), handlers: Arc::clone(&self.handlers), handler_flags: Arc::clone(&self.handler_flags), + handler_once: Arc::clone(&self.handler_once), last_from: Arc::clone(&self.last_from), last_intent_name: Arc::clone(&self.last_intent_name), }) diff --git a/src/interpreter/core.rs b/src/interpreter/core.rs index d9477017..0f9312b5 100644 --- a/src/interpreter/core.rs +++ b/src/interpreter/core.rs @@ -412,6 +412,54 @@ impl NyashInterpreter { } } +/// Execute a FunctionBox with given NyashBox arguments (crate-visible helper for VM) +pub(crate) fn run_function_box( + fun: &crate::boxes::function_box::FunctionBox, + args: Vec>, +) -> Result, RuntimeError> { + use crate::box_trait::{NyashBox, VoidBox}; + if args.len() != fun.params.len() { + return Err(RuntimeError::InvalidOperation { message: format!( + "Function expects {} args, got {}", fun.params.len(), args.len() + )}); + } + + let mut interp = NyashInterpreter::new(); + // Captures + for (k, v) in fun.env.captures.iter() { + interp.declare_local_variable(k, v.clone_or_share()); + } + if let Some(me_w) = &fun.env.me_value { + if let Some(me_arc) = me_w.upgrade() { + interp.declare_local_variable("me", (*me_arc).clone_or_share()); + } else { + interp.declare_local_variable("me", Box::new(crate::boxes::null_box::NullBox::new())); + } + } + // Params + for (p, v) in fun.params.iter().zip(args.into_iter()) { + interp.declare_local_variable(p, v); + } + // Execute body + crate::runtime::global_hooks::push_task_scope(); + let mut result: Box = Box::new(VoidBox::new()); + for st in &fun.body { + match interp.execute_statement(st) { + Ok(val) => { + result = val; + if let super::ControlFlow::Return(rv) = &interp.control_flow { + result = rv.clone_box(); + interp.control_flow = super::ControlFlow::None; + break; + } + } + Err(e) => { crate::runtime::global_hooks::pop_task_scope(); return Err(e); } + } + } + crate::runtime::global_hooks::pop_task_scope(); + Ok(result) +} + // ===== Tests ===== #[cfg(test)] diff --git a/src/jit/lower/core.rs b/src/jit/lower/core.rs index f1ea5366..c3b122fc 100644 --- a/src/jit/lower/core.rs +++ b/src/jit/lower/core.rs @@ -186,6 +186,34 @@ impl LowerCore { fn try_emit(&mut self, b: &mut dyn IRBuilder, instr: &MirInstruction, cur_bb: crate::mir::BasicBlockId, func: &crate::mir::MirFunction) -> Result<(), String> { use crate::mir::MirInstruction as I; match instr { + I::Call { dst, func, args, .. } => { + // FunctionBox call shim: emit hostcall nyash_fn_callN(func_h, args...) + // Push function operand (param or known) + self.push_value_if_known_or_param(b, func); + // Push up to 4 args (unknown become iconst 0 via helper) + for a in args.iter() { self.push_value_if_known_or_param(b, a); } + // Choose symbol by arity + let argc = args.len(); + let sym = match argc { + 0 => "nyash_fn_call0", + 1 => "nyash_fn_call1", + 2 => "nyash_fn_call2", + 3 => "nyash_fn_call3", + 4 => "nyash_fn_call4", + 5 => "nyash_fn_call5", + 6 => "nyash_fn_call6", + 7 => "nyash_fn_call7", + _ => "nyash_fn_call8", + }; + // Emit typed call: all params as I64, returning I64 handle + // Build param kinds vector: 1 (func) + argc (args) + let mut params: Vec = Vec::new(); + params.push(crate::jit::lower::builder::ParamKind::I64); + for _ in 0..core::cmp::min(argc, 8) { params.push(crate::jit::lower::builder::ParamKind::I64); } + b.emit_host_call_typed(sym, ¶ms, true, false); + // Mark destination as handle-like + if let Some(d) = dst { self.handle_values.insert(*d); } + } I::Await { dst, future } => { // Push future param index when known; otherwise -1 to trigger legacy search in shim if let Some(pidx) = self.param_index.get(future).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); } diff --git a/src/jit/lower/extern_thunks.rs b/src/jit/lower/extern_thunks.rs index c2abb331..c583524c 100644 --- a/src/jit/lower/extern_thunks.rs +++ b/src/jit/lower/extern_thunks.rs @@ -761,6 +761,53 @@ pub(super) extern "C" fn nyash_string_from_ptr(ptr: u64, len: u64) -> i64 { } } +// ===== FunctionBox call shims (by arity, up to 4) ===== +#[cfg(feature = "cranelift-jit")] +fn vmvalue_from_jit_arg_i64(v: i64) -> crate::backend::vm::VMValue { super::vmvalue_from_jit_arg_i64(v) } +#[cfg(feature = "cranelift-jit")] +fn i64_from_vmvalue(v: crate::backend::vm::VMValue) -> i64 { super::i64_from_vmvalue(v) } + +#[cfg(feature = "cranelift-jit")] +fn fn_call_impl(func_h: u64, args: &[i64]) -> i64 { + use crate::box_trait::NyashBox; + let f_arc = match crate::jit::rt::handles::get(func_h) { Some(a) => a, None => return 0 }; + if let Some(fun) = f_arc.as_any().downcast_ref::() { + let mut ny_args: Vec> = Vec::new(); + for &ai in args { + let v = vmvalue_from_jit_arg_i64(ai); + ny_args.push(v.to_nyash_box()); + } + match crate::interpreter::run_function_box(fun, ny_args) { + Ok(out) => { + let vmv = crate::backend::vm::VMValue::from_nyash_box(out); + i64_from_vmvalue(vmv) + } + Err(_) => 0, + } + } else { 0 } +} + +#[cfg(feature = "cranelift-jit")] +pub(super) extern "C" fn nyash_fn_call0(func_h: u64) -> i64 { fn_call_impl(func_h, &[]) } +#[cfg(feature = "cranelift-jit")] +pub(super) extern "C" fn nyash_fn_call1(func_h: u64, a0: i64) -> i64 { fn_call_impl(func_h, &[a0]) } +#[cfg(feature = "cranelift-jit")] +pub(super) extern "C" fn nyash_fn_call2(func_h: u64, a0: i64, a1: i64) -> i64 { fn_call_impl(func_h, &[a0,a1]) } +#[cfg(feature = "cranelift-jit")] +pub(super) extern "C" fn nyash_fn_call3(func_h: u64, a0: i64, a1: i64, a2: i64) -> i64 { fn_call_impl(func_h, &[a0,a1,a2]) } +#[cfg(feature = "cranelift-jit")] +pub(super) extern "C" fn nyash_fn_call4(func_h: u64, a0: i64, a1: i64, a2: i64, a3: i64) -> i64 { fn_call_impl(func_h, &[a0,a1,a2,a3]) } + +// extended arities (5..8) +#[cfg(feature = "cranelift-jit")] +pub(super) extern "C" fn nyash_fn_call5(func_h: u64, a0: i64, a1: i64, a2: i64, a3: i64, a4: i64) -> i64 { fn_call_impl(func_h, &[a0,a1,a2,a3,a4]) } +#[cfg(feature = "cranelift-jit")] +pub(super) extern "C" fn nyash_fn_call6(func_h: u64, a0: i64, a1: i64, a2: i64, a3: i64, a4: i64, a5: i64) -> i64 { fn_call_impl(func_h, &[a0,a1,a2,a3,a4,a5]) } +#[cfg(feature = "cranelift-jit")] +pub(super) extern "C" fn nyash_fn_call7(func_h: u64, a0: i64, a1: i64, a2: i64, a3: i64, a4: i64, a5: i64, a6: i64) -> i64 { fn_call_impl(func_h, &[a0,a1,a2,a3,a4,a5,a6]) } +#[cfg(feature = "cranelift-jit")] +pub(super) extern "C" fn nyash_fn_call8(func_h: u64, a0: i64, a1: i64, a2: i64, a3: i64, a4: i64, a5: i64, a6: i64, a7: i64) -> i64 { fn_call_impl(func_h, &[a0,a1,a2,a3,a4,a5,a6,a7]) } + // Build a StringBox handle from two u64 chunks (little-endian) and length (<=16) #[cfg(feature = "cranelift-jit")] pub(super) extern "C" fn nyash_string_from_u64x2(lo: u64, hi: u64, len: i64) -> i64 { diff --git a/src/mir/builder.rs b/src/mir/builder.rs index ab6749e0..22859522 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -301,28 +301,27 @@ impl MirBuilder { self.build_function_call(name.clone(), arguments.clone()) }, ASTNode::Call { callee, arguments, .. } => { - // 最小P1: callee が Lambda の場合のみ対応 + // P1.5: Lambdaはインライン、それ以外は Call に正規化 if let ASTNode::Lambda { params, body, .. } = callee.as_ref() { if params.len() != arguments.len() { return Err(format!("Lambda expects {} args, got {}", params.len(), arguments.len())); } - // 引数を評価 let mut arg_vals: Vec = Vec::new(); for a in arguments { arg_vals.push(self.build_expression(a)?); } - // スコープ保存 let saved_vars = self.variable_map.clone(); - // パラメータ束縛 - for (p, v) in params.iter().zip(arg_vals.iter()) { - self.variable_map.insert(p.clone(), *v); - } - // 本体を Program として Lower + for (p, v) in params.iter().zip(arg_vals.iter()) { self.variable_map.insert(p.clone(), *v); } let prog = ASTNode::Program { statements: body.clone(), span: crate::ast::Span::unknown() }; let out = self.build_expression(prog)?; - // 復元 self.variable_map = saved_vars; Ok(out) } else { - Err("Callee is not callable (lambda required)".to_string()) + // callee/args を評価し、Call を発行(VM 側で FunctionBox/関数名の両対応) + let callee_id = self.build_expression(*callee.clone())?; + let mut arg_ids = Vec::new(); + for a in arguments { arg_ids.push(self.build_expression(a)?); } + let dst = self.value_gen.next(); + self.emit_instruction(MirInstruction::Call { dst: Some(dst), func: callee_id, args: arg_ids, effects: EffectMask::PURE })?; + Ok(dst) } }, @@ -440,11 +439,68 @@ impl MirBuilder { }, ASTNode::Lambda { params, body, .. } => { - // Minimal P1: represent as constant string for now + // Lambda→FunctionBox 値 Lower(最小 + 簡易キャプチャ解析) let dst = self.value_gen.next(); - let s = format!("Lambda(params={}, body={})", params.len(), body.len()); - self.emit_instruction(MirInstruction::Const { dst, value: ConstValue::String(s) })?; - self.value_types.insert(dst, super::MirType::String); + // Collect free variable names: variables used in body but not in params, and not 'me'/'this' + use std::collections::HashSet; + let mut used: HashSet = HashSet::new(); + let mut locals: HashSet = HashSet::new(); + for p in params.iter() { locals.insert(p.clone()); } + fn collect_vars(node: &crate::ast::ASTNode, used: &mut HashSet, locals: &mut HashSet) { + match node { + crate::ast::ASTNode::Variable { name, .. } => { + if name != "me" && name != "this" && !locals.contains(name) { + used.insert(name.clone()); + } + } + crate::ast::ASTNode::Local { variables, .. } => { for v in variables { locals.insert(v.clone()); } } + crate::ast::ASTNode::Assignment { target, value, .. } => { collect_vars(target, used, locals); collect_vars(value, used, locals); } + crate::ast::ASTNode::BinaryOp { left, right, .. } => { collect_vars(left, used, locals); collect_vars(right, used, locals); } + crate::ast::ASTNode::UnaryOp { operand, .. } => { collect_vars(operand, used, locals); } + crate::ast::ASTNode::MethodCall { object, arguments, .. } => { collect_vars(object, used, locals); for a in arguments { collect_vars(a, used, locals); } } + crate::ast::ASTNode::FunctionCall { arguments, .. } => { for a in arguments { collect_vars(a, used, locals); } } + crate::ast::ASTNode::Call { callee, arguments, .. } => { collect_vars(callee, used, locals); for a in arguments { collect_vars(a, used, locals); } } + crate::ast::ASTNode::FieldAccess { object, .. } => { collect_vars(object, used, locals); } + crate::ast::ASTNode::New { arguments, .. } => { for a in arguments { collect_vars(a, used, locals); } } + crate::ast::ASTNode::If { condition, then_body, else_body, .. } => { + collect_vars(condition, used, locals); + for st in then_body { collect_vars(st, used, locals); } + if let Some(eb) = else_body { for st in eb { collect_vars(st, used, locals); } } + } + crate::ast::ASTNode::Loop { condition, body, .. } => { collect_vars(condition, used, locals); for st in body { collect_vars(st, used, locals); } } + crate::ast::ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => { + for st in try_body { collect_vars(st, used, locals); } + for c in catch_clauses { for st in &c.body { collect_vars(st, used, locals); } } + if let Some(fb) = finally_body { for st in fb { collect_vars(st, used, locals); } } + } + crate::ast::ASTNode::Throw { expression, .. } => { collect_vars(expression, used, locals); } + crate::ast::ASTNode::Print { expression, .. } => { collect_vars(expression, used, locals); } + crate::ast::ASTNode::Return { value, .. } => { if let Some(v) = value { collect_vars(v, used, locals); } } + crate::ast::ASTNode::AwaitExpression { expression, .. } => { collect_vars(expression, used, locals); } + crate::ast::ASTNode::PeekExpr { scrutinee, arms, else_expr, .. } => { + collect_vars(scrutinee, used, locals); + for (_, e) in arms { collect_vars(e, used, locals); } + collect_vars(else_expr, used, locals); + } + crate::ast::ASTNode::Program { statements, .. } => { for st in statements { collect_vars(st, used, locals); } } + crate::ast::ASTNode::FunctionDeclaration { params, body, .. } => { + let mut inner = locals.clone(); + for p in params { inner.insert(p.clone()); } + for st in body { collect_vars(st, used, &mut inner); } + } + _ => {} + } + } + for st in body.iter() { collect_vars(st, &mut used, &mut locals); } + // Materialize captures from current variable_map if known + let mut captures: Vec<(String, ValueId)> = Vec::new(); + for name in used.into_iter() { + if let Some(&vid) = self.variable_map.get(&name) { captures.push((name, vid)); } + } + // me capture(存在すれば) + let me = self.variable_map.get("me").copied(); + self.emit_instruction(MirInstruction::FunctionNew { dst, params: params.clone(), body: body.clone(), captures, me })?; + self.value_types.insert(dst, super::MirType::Box("FunctionBox".to_string())); Ok(dst) }, diff --git a/src/mir/instruction.rs b/src/mir/instruction.rs index 1190d633..1e23c18b 100644 --- a/src/mir/instruction.rs +++ b/src/mir/instruction.rs @@ -71,6 +71,19 @@ pub enum MirInstruction { args: Vec, effects: EffectMask, }, + + /// Create a function value (FunctionBox) from params/body and optional captures + /// `%dst = function_new [params] {body} [captures...]` + /// Minimal lowering support: captures may be empty; 'me' is optional. + FunctionNew { + dst: ValueId, + params: Vec, + body: Vec, + /// Pairs of (name, value) to capture by value + captures: Vec<(String, ValueId)>, + /// Optional 'me' value to capture weakly if it is a BoxRef at runtime + me: Option, + }, /// Box method invocation /// `%dst = invoke %box.method(%args...)` @@ -454,6 +467,8 @@ impl MirInstruction { // Phase 9.7: External Function Calls MirInstruction::ExternCall { effects, .. } => *effects, // Use provided effect mask + // Function value construction: treat as pure with allocation + MirInstruction::FunctionNew { .. } => EffectMask::PURE.add(Effect::Alloc), } } @@ -479,6 +494,7 @@ impl MirInstruction { MirInstruction::WeakRef { dst, .. } | MirInstruction::FutureNew { dst, .. } | MirInstruction::Await { dst, .. } => Some(*dst), + MirInstruction::FunctionNew { dst, .. } => Some(*dst), MirInstruction::Call { dst, .. } | MirInstruction::BoxCall { dst, .. } | @@ -540,6 +556,12 @@ impl MirInstruction { used.extend(args); used }, + MirInstruction::FunctionNew { captures, me, .. } => { + let mut used: Vec = Vec::new(); + used.extend(captures.iter().map(|(_, v)| *v)); + if let Some(m) = me { used.push(*m); } + used + } MirInstruction::BoxCall { box_val, args, .. } | MirInstruction::PluginInvoke { box_val, args, .. } => { let mut used = vec![*box_val]; diff --git a/src/mir/printer.rs b/src/mir/printer.rs index 0c7d2644..86741e76 100644 --- a/src/mir/printer.rs +++ b/src/mir/printer.rs @@ -311,6 +311,13 @@ impl MirPrinter { format!("call {}({})", func, args_str) } }, + MirInstruction::FunctionNew { dst, params, body, captures, me } => { + let p = params.join(", "); + let c = captures.iter().map(|(n, v)| format!("{}={}", n, v)).collect::>().join(", "); + let me_s = me.map(|m| format!(" me={}", m)).unwrap_or_default(); + let cap_s = if c.is_empty() { String::new() } else { format!(" [{}]", c) }; + format!("{} = function_new ({}) {{...{}}}{}{}", dst, p, body.len(), cap_s, me_s) + }, MirInstruction::BoxCall { dst, box_val, method, method_id, args, effects: _ } => { let args_str = args.iter() diff --git a/src/runtime/nyash_runtime.rs b/src/runtime/nyash_runtime.rs index 56f954c9..ca4addc4 100644 --- a/src/runtime/nyash_runtime.rs +++ b/src/runtime/nyash_runtime.rs @@ -8,6 +8,7 @@ use std::sync::{Arc, Mutex, RwLock}; use crate::core::model::BoxDeclaration; use crate::box_factory::{UnifiedBoxRegistry, BoxFactory}; +use crate::box_factory::builtin::BuiltinBoxFactory; #[cfg(feature = "plugins")] use crate::box_factory::plugin::PluginBoxFactory; @@ -80,7 +81,15 @@ impl NyashRuntimeBuilder { fn create_default_registry() -> Arc> { let mut registry = UnifiedBoxRegistry::new(); - eprintln!("[UnifiedRegistry] Builtin boxes removed; using plugins-only registry"); + // Simple rule: + // - Default: plugins-only (no builtins) + // - wasm32: enable builtins + // - tests: enable builtins + // - feature "builtin-core": enable builtins manually + #[cfg(any(test, target_arch = "wasm32", feature = "builtin-core"))] + { + registry.register(Arc::new(BuiltinBoxFactory::new())); + } #[cfg(feature = "plugins")] { registry.register(Arc::new(PluginBoxFactory::new())); diff --git a/src/runtime/plugin_loader_v2.rs b/src/runtime/plugin_loader_v2.rs deleted file mode 100644 index 8e9c6454..00000000 --- a/src/runtime/plugin_loader_v2.rs +++ /dev/null @@ -1,1639 +0,0 @@ -//! Nyash v2 Plugin Loader -//! -//! cfg/features で2パスを提供: -//! - enabled: plugins feature 有効 かつ 非wasm32 ターゲット -//! - stub : それ以外(WASMやplugins無効) - -#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] -mod enabled { - use crate::bid::{BidResult, BidError}; - use crate::box_trait::{NyashBox, BoxCore, StringBox, IntegerBox}; - use crate::config::nyash_toml_v2::{NyashConfigV2, LibraryDefinition}; - use std::collections::HashMap; - use std::sync::{Arc, RwLock}; - // use std::ffi::c_void; // unused - use std::any::Any; - use once_cell::sync::Lazy; - use crate::runtime::leak_tracker; - fn dbg_on() -> bool { std::env::var("NYASH_DEBUG_PLUGIN").unwrap_or_default() == "1" } - -/// Loaded plugin information - pub struct LoadedPluginV2 { - /// Library handle - _lib: Arc, - - /// Box types provided by this plugin - #[allow(dead_code)] - box_types: Vec, - /// Optional per-box TypeBox ABI addresses (nyash_typebox_); raw usize for Send/Sync - typeboxes: std::collections::HashMap, - - /// Optional init function - #[allow(dead_code)] - init_fn: Option i32>, - - /// Required invoke function - invoke_fn: unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32, -} - -// (moved: public constructor wrapper is declared after the enabled module) - -/// v2 Plugin Box wrapper - temporary implementation -#[derive(Debug)] - pub struct PluginHandleInner { - pub type_id: u32, - pub invoke_fn: unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32, - pub instance_id: u32, - pub fini_method_id: Option, - finalized: std::sync::atomic::AtomicBool, - } - - // Loaded box spec from plugins//nyash_box.toml - #[derive(Debug, Clone, Default)] - struct LoadedBoxSpec { - type_id: Option, - methods: HashMap, - fini_method_id: Option, - } - #[derive(Debug, Clone, Copy)] - struct MethodSpec { method_id: u32, returns_result: bool } - - impl Drop for PluginHandleInner { - fn drop(&mut self) { - // Finalize exactly once when the last shared handle is dropped - if let Some(fini_id) = self.fini_method_id { - if !self.finalized.swap(true, std::sync::atomic::Ordering::SeqCst) { - let tlv_args: [u8; 4] = [1, 0, 0, 0]; - let mut out: [u8; 4] = [0; 4]; - let mut out_len: usize = out.len(); - unsafe { - (self.invoke_fn)( - self.type_id, - fini_id, - self.instance_id, - tlv_args.as_ptr(), - tlv_args.len(), - out.as_mut_ptr(), - &mut out_len, - ); - } - } - } - } - } - - impl PluginHandleInner { - /// Explicitly finalize this handle now (idempotent) - pub fn finalize_now(&self) { - if let Some(fini_id) = self.fini_method_id { - if !self.finalized.swap(true, std::sync::atomic::Ordering::SeqCst) { - leak_tracker::finalize_plugin("PluginBox", self.instance_id); - let tlv_args: [u8; 4] = [1, 0, 0, 0]; - let mut out: [u8; 4] = [0; 4]; - let mut out_len: usize = out.len(); - unsafe { - (self.invoke_fn)( - self.type_id, - fini_id, - self.instance_id, - tlv_args.as_ptr(), - tlv_args.len(), - out.as_mut_ptr(), - &mut out_len, - ); - } - } - } - } - } - - /// Helper to construct a PluginBoxV2 from raw ids and invoke pointer safely - pub fn make_plugin_box_v2_inner(box_type: String, type_id: u32, instance_id: u32, invoke_fn: unsafe extern "C" fn(u32,u32,u32,*const u8,usize,*mut u8,*mut usize) -> i32) -> PluginBoxV2 { - PluginBoxV2 { box_type, inner: std::sync::Arc::new(PluginHandleInner { type_id, invoke_fn, instance_id, fini_method_id: None, finalized: std::sync::atomic::AtomicBool::new(false) }) } - } - - // Nyash TypeBox (FFI minimal for PoC) - use std::os::raw::c_char; - #[repr(C)] - pub struct NyashTypeBoxFfi { - pub abi_tag: u32, // 'TYBX' - pub version: u16, // 1 - pub struct_size: u16, // sizeof(NyashTypeBoxFfi) - pub name: *const c_char, // C string - // Minimal methods - pub resolve: Option u32>, - pub invoke_id: Option i32>, - pub capabilities: u64, - } - -#[derive(Debug, Clone)] - pub struct PluginBoxV2 { - pub box_type: String, - pub inner: std::sync::Arc, - } - - impl BoxCore for PluginBoxV2 { - fn box_id(&self) -> u64 { - self.inner.instance_id as u64 - } - - fn parent_type_id(&self) -> Option { - None - } - - fn fmt_box(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}({})", self.box_type, self.inner.instance_id) - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } -} - - impl NyashBox for PluginBoxV2 { - fn is_identity(&self) -> bool { true } - fn type_name(&self) -> &'static str { - // Return the actual box type name for proper method dispatch - match self.box_type.as_str() { - "FileBox" => "FileBox", - _ => "PluginBoxV2", - } - } - - fn clone_box(&self) -> Box { - if dbg_on() { eprintln!("🔍 DEBUG: PluginBoxV2::clone_box called for {} (id={})", self.box_type, self.inner.instance_id); } - // Clone means creating a new instance by calling birth() on the plugin - let mut output_buffer = vec![0u8; 1024]; - let mut output_len = output_buffer.len(); - let tlv_args = [1u8, 0, 0, 0]; // version=1, argc=0 - - let result = unsafe { - (self.inner.invoke_fn)( - self.inner.type_id, - 0, // method_id=0 (birth) - 0, // instance_id=0 (static call) - tlv_args.as_ptr(), - tlv_args.len(), - output_buffer.as_mut_ptr(), - &mut output_len, - ) - }; - - if result == 0 && output_len >= 4 { - let new_instance_id = u32::from_le_bytes([ - output_buffer[0], output_buffer[1], output_buffer[2], output_buffer[3] - ]); - if dbg_on() { eprintln!("🎉 clone_box success: created new {} instance_id={}", self.box_type, new_instance_id); } - Box::new(PluginBoxV2 { - box_type: self.box_type.clone(), - inner: std::sync::Arc::new(PluginHandleInner { - type_id: self.inner.type_id, - invoke_fn: self.inner.invoke_fn, - instance_id: new_instance_id, - fini_method_id: self.inner.fini_method_id, - finalized: std::sync::atomic::AtomicBool::new(false), - }), - }) - } else { - if dbg_on() { eprintln!("❌ clone_box failed: birth() returned error code {}", result); } - Box::new(StringBox::new(format!("Clone failed for {}", self.box_type))) - } - } - - fn to_string_box(&self) -> crate::box_trait::StringBox { - StringBox::new(format!("{}({})", self.box_type, self.inner.instance_id)) - } - - fn equals(&self, _other: &dyn NyashBox) -> crate::box_trait::BoolBox { - crate::box_trait::BoolBox::new(false) - } - - fn share_box(&self) -> Box { - if dbg_on() { eprintln!("🔍 DEBUG: PluginBoxV2::share_box called for {} (id={})", self.box_type, self.inner.instance_id); } - - // Share means returning a new Box with the same instance_id - Box::new(PluginBoxV2 { - box_type: self.box_type.clone(), - inner: self.inner.clone(), - }) - } -} - - impl PluginBoxV2 { - pub fn instance_id(&self) -> u32 { self.inner.instance_id } - pub fn finalize_now(&self) { self.inner.finalize_now() } - pub fn is_finalized(&self) -> bool { self.inner.finalized.load(std::sync::atomic::Ordering::SeqCst) } - } - - /// Public helper to construct a PluginBoxV2 from raw parts (for VM/JIT integration) - pub fn construct_plugin_box( - box_type: String, - type_id: u32, - invoke_fn: unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32, - instance_id: u32, - fini_method_id: Option, - ) -> PluginBoxV2 { - PluginBoxV2 { - box_type, - inner: std::sync::Arc::new(PluginHandleInner { - type_id, - invoke_fn, - instance_id, - fini_method_id, - finalized: std::sync::atomic::AtomicBool::new(false), - }), - } - } - -/// Plugin loader v2 - pub struct PluginLoaderV2 { - /// Loaded plugins (library name -> plugin info) - plugins: RwLock>>, - - /// Configuration - pub config: Option, - /// Path to the loaded nyash.toml (absolute), used for consistent re-reads - config_path: Option, - - /// Singleton instances: (lib_name, box_type) -> shared handle - singletons: RwLock>>, - /// Loaded per-plugin box specs from nyash_box.toml: (lib_name, box_type) -> spec - box_specs: RwLock>, -} - -impl PluginLoaderV2 { - fn find_box_by_type_id<'a>(&'a self, config: &'a NyashConfigV2, toml_value: &'a toml::Value, type_id: u32) -> Option<(&'a str, &'a str)> { - for (lib_name, lib_def) in &config.libraries { - for box_name in &lib_def.boxes { - if let Some(box_conf) = config.get_box_config(lib_name, box_name, toml_value) { - if box_conf.type_id == type_id { - return Some((lib_name.as_str(), box_name.as_str())); - } - } - } - } - None - } - /// Create new loader - pub fn new() -> Self { - Self { - plugins: RwLock::new(HashMap::new()), - config: None, - config_path: None, - singletons: RwLock::new(HashMap::new()), - box_specs: RwLock::new(HashMap::new()), - } - } - - /// Load configuration from nyash.toml - pub fn load_config(&mut self, config_path: &str) -> BidResult<()> { - // Canonicalize path for later re-reads - let canonical = std::fs::canonicalize(config_path) - .map(|p| p.to_string_lossy().to_string()) - .unwrap_or_else(|_| config_path.to_string()); - self.config_path = Some(canonical.clone()); - - self.config = Some(NyashConfigV2::from_file(&canonical) - .map_err(|e| { - eprintln!("Failed to load config: {}", e); - BidError::PluginError - })?); - // Bump cache versions for all box types (config reload may change method layout) - if let Some(cfg) = self.config.as_ref() { - let mut labels: Vec = Vec::new(); - for (_lib, def) in &cfg.libraries { - for bt in &def.boxes { - labels.push(format!("BoxRef:{}", bt)); - } - } - crate::runtime::cache_versions::bump_many(&labels); - } - Ok(()) - } - - /// Load all plugins from config - pub fn load_all_plugins(&self) -> BidResult<()> { - let config = self.config.as_ref() - .ok_or(BidError::PluginError)?; - - // Load legacy libraries (backward compatible) - for (lib_name, lib_def) in &config.libraries { - if let Err(e) = self.load_plugin(lib_name, lib_def) { - eprintln!("Warning: Failed to load plugin {}: {:?}", lib_name, e); - } - } - // Load new-style plugins from [plugins] map (name -> root dir) - for (plugin_name, root) in &config.plugins { - // Synthesize a LibraryDefinition from plugin spec (nyash_box.toml) if present; otherwise minimal - let mut boxes: Vec = Vec::new(); - let spec_path = std::path::Path::new(root).join("nyash_box.toml"); - // Optional artifact path from spec - let mut artifact_override: Option = None; - if let Ok(txt) = std::fs::read_to_string(&spec_path) { - if let Ok(val) = txt.parse::() { - if let Some(prov) = val.get("provides").and_then(|t| t.get("boxes")).and_then(|a| a.as_array()) { - for it in prov.iter() { if let Some(s) = it.as_str() { boxes.push(s.to_string()); } } - } - // Artifacts section: choose OS-specific path template if provided - if let Some(arts) = val.get("artifacts").and_then(|t| t.as_table()) { - let key = if cfg!(target_os = "windows") { "windows" } else if cfg!(target_os = "macos") { "macos" } else { "linux" }; - if let Some(p) = arts.get(key).and_then(|v| v.as_str()) { - artifact_override = Some(p.to_string()); - } - } - // Build per-box specs - for bname in &boxes { - let mut spec = LoadedBoxSpec::default(); - if let Some(tb) = val.get(bname) { - if let Some(tid) = tb.get("type_id").and_then(|v| v.as_integer()) { spec.type_id = Some(tid as u32); } - if let Some(lc) = tb.get("lifecycle") { - if let Some(fini) = lc.get("fini").and_then(|m| m.get("id")).and_then(|v| v.as_integer()) { spec.fini_method_id = Some(fini as u32); } - } - if let Some(mtbl) = tb.get("methods").and_then(|t| t.as_table()) { - for (mname, md) in mtbl.iter() { - if let Some(mid) = md.get("id").and_then(|v| v.as_integer()) { - let rr = md.get("returns_result").and_then(|v| v.as_bool()).unwrap_or(false); - spec.methods.insert(mname.clone(), MethodSpec { method_id: mid as u32, returns_result: rr }); - } - } - } - } - if spec.type_id.is_some() || !spec.methods.is_empty() { - self.box_specs.write().unwrap().insert((plugin_name.clone(), bname.clone()), spec); - } - } - } - } - // Path heuristic: use artifact override if present, else "/" - let synth_path = if let Some(p) = artifact_override { - let jp = std::path::Path::new(root).join(p); - jp.to_string_lossy().to_string() - } else { - std::path::Path::new(root).join(plugin_name).to_string_lossy().to_string() - }; - let lib_def = LibraryDefinition { boxes: boxes.clone(), path: synth_path }; - if let Err(e) = self.load_plugin(plugin_name, &lib_def) { - eprintln!("Warning: Failed to load plugin {} from [plugins]: {:?}", plugin_name, e); - } - } - // Strict validation: central [box_types] vs plugin spec type_id - let strict = std::env::var("NYASH_PLUGIN_STRICT").ok().as_deref() == Some("1"); - if !config.box_types.is_empty() { - for ((lib, bname), spec) in self.box_specs.read().unwrap().iter() { - if let Some(cid) = config.box_types.get(bname) { - if let Some(tid) = spec.type_id { - if tid != *cid { - eprintln!("[PluginLoaderV2] type_id mismatch for {} (plugin={}): central={}, spec={}", bname, lib, cid, tid); - if strict { return Err(BidError::PluginError); } - } - } - } - } - } - // Pre-birth singletons configured in nyash.toml - let cfg_path = self.config_path.as_ref().map(|s| s.as_str()).unwrap_or("nyash.toml"); - let toml_content = std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?; - let toml_value: toml::Value = toml::from_str(&toml_content).map_err(|_| BidError::PluginError)?; - for (lib_name, lib_def) in &config.libraries { - for box_name in &lib_def.boxes { - if let Some(bc) = config.get_box_config(lib_name, box_name, &toml_value) { - if bc.singleton { - let _ = self.ensure_singleton_handle(lib_name, box_name); - } - } - } - } - - Ok(()) - } - - /// Construct a PluginBoxV2 from an existing (type_id, instance_id) pair. - /// Used by reverse host API to materialize PluginHandle(tag=8) as a VMValue::BoxRef. - pub fn construct_existing_instance(&self, type_id: u32, instance_id: u32) -> Option> { - let config = self.config.as_ref()?; - let cfg_path = self.config_path.as_ref()?; - let toml_content = std::fs::read_to_string(cfg_path).ok()?; - let toml_value: toml::Value = toml::from_str(&toml_content).ok()?; - let (lib_name, box_type) = self.find_box_by_type_id(config, &toml_value, type_id)?; - let plugins = self.plugins.read().ok()?; - let plugin = plugins.get(lib_name)?.clone(); - // fini method id from spec or config - let fini_method_id = if let Some(spec) = self.box_specs.read().ok()?.get(&(lib_name.to_string(), box_type.to_string())) { - spec.fini_method_id - } else { - let box_conf = config.get_box_config(lib_name, box_type, &toml_value)?; - box_conf.methods.get("fini").map(|m| m.method_id) - }; - let bx = construct_plugin_box(box_type.to_string(), type_id, plugin.invoke_fn, instance_id, fini_method_id); - Some(Box::new(bx)) - } - - fn find_lib_name_for_box(&self, box_type: &str) -> Option { - if let Some(cfg) = &self.config { - if let Some((name, _)) = cfg.find_library_for_box(box_type) { return Some(name.to_string()); } - } - for ((lib, b), _) in self.box_specs.read().unwrap().iter() { - if b == box_type { return Some(lib.clone()); } - } - None - } - - /// Ensure a singleton handle is created and stored - fn ensure_singleton_handle(&self, lib_name: &str, box_type: &str) -> BidResult<()> { - // Fast path: already present - if self.singletons.read().unwrap().contains_key(&(lib_name.to_string(), box_type.to_string())) { - return Ok(()); - } - // Create via birth - let cfg_path = self.config_path.as_ref().map(|s| s.as_str()).unwrap_or("nyash.toml"); - let toml_content = std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?; - let toml_value: toml::Value = toml::from_str(&toml_content).map_err(|_| BidError::PluginError)?; - let config = self.config.as_ref().ok_or(BidError::PluginError)?; - let plugins = self.plugins.read().unwrap(); - let plugin = plugins.get(lib_name).ok_or(BidError::PluginError)?; - // Prefer spec-loaded type_id - let type_id = if let Some(spec) = self.box_specs.read().unwrap().get(&(lib_name.to_string(), box_type.to_string())) { - spec.type_id.unwrap_or_else(|| config.box_types.get(box_type).copied().unwrap_or(0)) - } else { - let box_conf = config.get_box_config(lib_name, box_type, &toml_value).ok_or(BidError::InvalidType)?; - box_conf.type_id - }; - // Call birth - let mut output_buffer = vec![0u8; 1024]; - let mut output_len = output_buffer.len(); - let tlv_args = crate::runtime::plugin_ffi_common::encode_empty_args(); - let birth_result = unsafe { - (plugin.invoke_fn)(type_id, 0, 0, tlv_args.as_ptr(), tlv_args.len(), output_buffer.as_mut_ptr(), &mut output_len) - }; - if birth_result != 0 || output_len < 4 { return Err(BidError::PluginError); } - let instance_id = u32::from_le_bytes([output_buffer[0], output_buffer[1], output_buffer[2], output_buffer[3]]); - let fini_id = if let Some(spec) = self.box_specs.read().unwrap().get(&(lib_name.to_string(), box_type.to_string())) { - spec.fini_method_id - } else { - let box_conf = config.get_box_config(lib_name, box_type, &toml_value).ok_or(BidError::InvalidType)?; - box_conf.methods.get("fini").map(|m| m.method_id) - }; - let handle = std::sync::Arc::new(PluginHandleInner { - type_id, - invoke_fn: plugin.invoke_fn, - instance_id, - fini_method_id: fini_id, - finalized: std::sync::atomic::AtomicBool::new(false), - }); - self.singletons.write().unwrap().insert((lib_name.to_string(), box_type.to_string()), handle); - // bump version for this box type to invalidate caches - crate::runtime::cache_versions::bump_version(&format!("BoxRef:{}", box_type)); - Ok(()) - } - - /// Perform an external host call (env.* namespace) or return an error if unsupported - /// Returns Some(Box) for a value result, or None for void-like calls - pub fn extern_call( - &self, - iface_name: &str, - method_name: &str, - args: &[Box], - ) -> BidResult>> { - match (iface_name, method_name) { - ("env.console", "log") => { - for a in args { - println!("{}", a.to_string_box().value); - } - Ok(None) - } - ("env.task", "cancelCurrent") => { - let tok = crate::runtime::global_hooks::current_group_token(); - tok.cancel(); - Ok(None) - } - ("env.task", "currentToken") => { - // Return a TokenBox representing current task group's cancellation token - let tok = crate::runtime::global_hooks::current_group_token(); - let tb = crate::boxes::token_box::TokenBox::from_token(tok); - Ok(Some(Box::new(tb))) - } - ("env.debug", "trace") => { - // Minimal debug trace; prints to stderr when enabled - if std::env::var("NYASH_DEBUG_TRACE").ok().as_deref() == Some("1") { - for a in args { eprintln!("[debug.trace] {}", a.to_string_box().value); } - } - Ok(None) - } - ("env.runtime", "checkpoint") => { - // Safepoint + scheduler poll via global hooks - if crate::config::env::runtime_checkpoint_trace() { - eprintln!("[runtime.checkpoint] reached"); - } - crate::runtime::global_hooks::safepoint_and_poll(); - Ok(None) - } - // Future/Await bridge (scaffold): maps MIR Future* to Box operations - ("env.future", "new") | ("env.future", "birth") => { - // new(value) -> FutureBox(set to value) - let fut = crate::boxes::future::FutureBox::new(); - if let Some(v) = args.get(0) { fut.set_result(v.clone_box()); } - Ok(Some(Box::new(fut))) - } - ("env.future", "set") => { - // set(future, value) - if args.len() >= 2 { - if let Some(fut) = args[0].as_any().downcast_ref::() { - fut.set_result(args[1].clone_box()); - } - } - Ok(None) - } - ("env.future", "await") => { - // await(future) -> Result.Ok(value) / Result.Err(Timeout|Error) - use crate::boxes::result::NyashResultBox; - if let Some(arg) = args.get(0) { - if let Some(fut) = arg.as_any().downcast_ref::() { - let max_ms: u64 = crate::config::env::await_max_ms(); - let start = std::time::Instant::now(); - let mut spins = 0usize; - while !fut.ready() { - crate::runtime::global_hooks::safepoint_and_poll(); - std::thread::yield_now(); - spins += 1; - if spins % 1024 == 0 { std::thread::sleep(std::time::Duration::from_millis(1)); } - if start.elapsed() >= std::time::Duration::from_millis(max_ms) { - let err = crate::box_trait::StringBox::new("Timeout"); - return Ok(Some(Box::new(NyashResultBox::new_err(Box::new(err))))); - } - } - return match fut.wait_and_get() { - Ok(v) => Ok(Some(Box::new(NyashResultBox::new_ok(v)))), - Err(e) => { - let err = crate::box_trait::StringBox::new(format!("Error: {}", e)); - Ok(Some(Box::new(NyashResultBox::new_err(Box::new(err))))) - } - }; - } else { - return Ok(Some(Box::new(NyashResultBox::new_ok(arg.clone_box())))); - } - } - Ok(Some(Box::new(crate::boxes::result::NyashResultBox::new_err(Box::new(crate::box_trait::StringBox::new("InvalidArgs")))))) - } - ("env.future", "delay") => { - // delay(ms) -> FutureBox resolved to void after ms - use crate::box_trait::NyashBox as _; - let fut = crate::boxes::future::FutureBox::new(); - let ms = if let Some(arg0) = args.get(0) { - if let Some(i) = arg0.as_any().downcast_ref::() { i.value.max(0) as u64 } - else { arg0.to_string_box().value.trim().parse::().unwrap_or(0).max(0) as u64 } - } else { 0 }; - let fut_setter = fut.clone(); - let _scheduled = crate::runtime::global_hooks::spawn_task_after(ms, "env.future.delay", Box::new(move || { - fut_setter.set_result(Box::new(crate::box_trait::VoidBox::new())); - })); - crate::runtime::global_hooks::register_future_to_current_group(&fut); - Ok(Some(Box::new(fut))) - } - ("env.future", "spawn_instance") => { - // spawn_instance(recv, method_name, args...) -> FutureBox - // If a scheduler is available, schedule the call; else invoke synchronously. - use crate::box_trait::{NyashBox, VoidBox, ErrorBox}; - let fut = crate::boxes::future::FutureBox::new(); - if args.len() >= 2 { - if let Some(pb) = args[0].as_any().downcast_ref::() { - let method_name = if let Some(sb) = args[1].as_any().downcast_ref::() { sb.value.clone() } else { args[1].to_string_box().value }; - // Clone boxes for scheduled call (same-thread scheduler; no Send bound) - let tail: Vec> = args.iter().skip(2).map(|a| a.clone_box()).collect(); - let recv_type = pb.box_type.clone(); - let recv_id = pb.instance_id(); - // Clones for fallback inline path - let recv_type_inline = recv_type.clone(); - let method_name_inline = method_name.clone(); - let tail_inline: Vec> = tail.iter().map(|a| a.clone_box()).collect(); - let fut_setter = fut.clone(); - // Phase 2: attempt to bind to current task group's token (no-op if unset) - let token = crate::runtime::global_hooks::current_group_token(); - let scheduled = crate::runtime::global_hooks::spawn_task_with_token("spawn_instance", token, Box::new(move || { - let host = crate::runtime::get_global_plugin_host(); - let read_res = host.read(); - if let Ok(ro) = read_res { - match ro.invoke_instance_method(&recv_type, &method_name, recv_id, &tail) { - Ok(ret) => { if let Some(v) = ret { fut_setter.set_result(v); } else { fut_setter.set_result(Box::new(VoidBox::new())); } } - Err(e) => { fut_setter.set_result(Box::new(ErrorBox::new("InvokeError", &format!("{}.{}: {:?}", recv_type, method_name, e)))); } - } - } - })); - if !scheduled { - // Fallback: run inline synchronously - let host = crate::runtime::get_global_plugin_host(); - let read_res = host.read(); - if let Ok(ro) = read_res { - match ro.invoke_instance_method(&recv_type_inline, &method_name_inline, recv_id, &tail_inline) { - Ok(ret) => { if let Some(v) = ret { fut.set_result(v); } else { fut.set_result(Box::new(VoidBox::new())); } } - Err(e) => { fut.set_result(Box::new(ErrorBox::new("InvokeError", &format!("{}.{}: {:?}", recv_type_inline, method_name_inline, e)))); } - } - } - } - // Register into current TaskGroup (if any) or implicit group (best-effort) - crate::runtime::global_hooks::register_future_to_current_group(&fut); - return Ok(Some(Box::new(fut))); - } - } - // Fallback: resolved future of first arg - if let Some(v) = args.get(0) { fut.set_result(v.clone_box()); } - crate::runtime::global_hooks::register_future_to_current_group(&fut); - Ok(Some(Box::new(fut))) - } - ("env.canvas", _) => { - eprintln!("[env.canvas] {} invoked (stub)", method_name); - Ok(None) - } - _ => { - // Future: route to plugin-defined extern interfaces via config - Err(BidError::InvalidMethod) - } - } - } - - fn resolve_method_id_from_file(&self, box_type: &str, method_name: &str) -> BidResult { - let config = self.config.as_ref().ok_or(BidError::PluginError)?; - let lib_name = self.find_lib_name_for_box(box_type).ok_or(BidError::InvalidType)?; - // Prefer spec-loaded methods - if let Some(spec) = self.box_specs.read().unwrap().get(&(lib_name.clone(), box_type.to_string())) { - if let Some(m) = spec.methods.get(method_name) { return Ok(m.method_id); } - } - // Fallback to central nyash.toml nested box config - let cfg_path = self.config_path.as_ref().map(|s| s.as_str()).unwrap_or("nyash.toml"); - let toml_content = std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?; - let toml_value: toml::Value = toml::from_str(&toml_content).map_err(|_| BidError::PluginError)?; - let box_conf = config.get_box_config(&lib_name, box_type, &toml_value).ok_or(BidError::InvalidType)?; - let method = box_conf.methods.get(method_name).ok_or_else(|| { - eprintln!("[PluginLoaderV2] Method '{}' not found for box '{}' in {}", method_name, box_type, cfg_path); - eprintln!("[PluginLoaderV2] Available methods: {:?}", box_conf.methods.keys().collect::>()); - BidError::InvalidMethod - })?; - Ok(method.method_id) - } - - /// Determine whether a method returns a Result (Ok/Err) wrapper. - pub fn method_returns_result(&self, box_type: &str, method_name: &str) -> bool { - let config = match &self.config { Some(c) => c, None => return false }; - let lib_name = match self.find_lib_name_for_box(box_type) { Some(n) => n, None => return false }; - // Prefer spec if present - if let Some(spec) = self.box_specs.read().unwrap().get(&(lib_name.clone(), box_type.to_string())) { - if let Some(m) = spec.methods.get(method_name) { return m.returns_result; } - } - // Fallback to central nyash.toml - let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml"); - if let Ok(toml_content) = std::fs::read_to_string(cfg_path) { - if let Ok(toml_value) = toml::from_str::(&toml_content) { - if let Some(bc) = config.get_box_config(&lib_name, box_type, &toml_value) { - return bc.methods.get(method_name).map(|m| m.returns_result).unwrap_or(false); - } - } - } - false - } - - /// Invoke an instance method on a plugin box by name (minimal TLV encoding) - pub fn invoke_instance_method( - &self, - box_type: &str, - method_name: &str, - instance_id: u32, - args: &[Box], - ) -> BidResult>> { - // ConsoleBox.readLine: プラグイン未実装時のホスト側フォールバック - if box_type == "ConsoleBox" && method_name == "readLine" { - use std::io::Read; - let mut s = String::new(); - let mut stdin = std::io::stdin(); - let mut buf = [0u8; 1]; - loop { - match stdin.read(&mut buf) { - Ok(0) => { return Ok(None); } // EOF → None(Nyashのnull相当) - Ok(_) => { - let ch = buf[0] as char; - if ch == '\n' { break; } - s.push(ch); - if s.len() > 1_000_000 { break; } - } - Err(_) => { return Ok(None); } - } - } - return Ok(Some(Box::new(crate::box_trait::StringBox::new(s)) as Box)); - } - // v2.1: 引数ありのメソッドを許可(BoxRef/基本型/文字列化フォールバック) - // MapBox convenience: route string-key get/has to getS/hasS if available - let effective_method = if box_type == "MapBox" { - if method_name == "get" { - if let Some(a0) = args.get(0) { if a0.as_any().downcast_ref::().is_some() { "getS" } else { method_name } } - else { method_name } - } else if method_name == "has" { - if let Some(a0) = args.get(0) { if a0.as_any().downcast_ref::().is_some() { "hasS" } else { method_name } } - else { method_name } - } else { method_name } - } else { method_name }; - let method_id = self.resolve_method_id_from_file(box_type, effective_method)?; - // Find plugin and type_id - let config = self.config.as_ref().ok_or(BidError::PluginError)?; - let lib_name = self.find_lib_name_for_box(box_type).ok_or(BidError::InvalidType)?; - let plugins = self.plugins.read().unwrap(); - let plugin = plugins.get(&lib_name).ok_or(BidError::PluginError)?; - let cfg_path = self.config_path.as_ref().map(|s| s.as_str()).unwrap_or("nyash.toml"); - let toml_content = std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?; - let toml_value: toml::Value = toml::from_str(&toml_content).map_err(|_| BidError::PluginError)?; - // Prefer spec values; but if method isn't listed in spec, fallback to central nyash.toml for returns_result - let (type_id, returns_result) = if let Some(spec) = self.box_specs.read().unwrap().get(&(lib_name.clone(), box_type.to_string())) { - let tid = spec - .type_id - .or_else(|| config.box_types.get(box_type).copied()) - .ok_or(BidError::InvalidType)?; - let rr = if let Some(m) = spec.methods.get(method_name) { - m.returns_result - } else { - // Fallback to central config for method flags - if let Some(box_conf) = config.get_box_config(&lib_name, box_type, &toml_value) { - box_conf.methods.get(method_name).map(|m| m.returns_result).unwrap_or(false) - } else { false } - }; - (tid, rr) - } else { - let box_conf = config.get_box_config(&lib_name, box_type, &toml_value).ok_or(BidError::InvalidType)?; - let rr = box_conf.methods.get(method_name).map(|m| m.returns_result).unwrap_or(false); - (box_conf.type_id, rr) - }; - eprintln!("[PluginLoaderV2] Invoke {}.{}: resolving and encoding args (argc={})", box_type, effective_method, args.len()); - // TLV args: encode using BID-1 style (u16 ver, u16 argc, then entries) - let tlv_args = { - let mut buf = crate::runtime::plugin_ffi_common::encode_tlv_header(args.len() as u16); - // Validate against nyash.toml method args schema if present - let expected_args = if self.box_specs.read().unwrap().get(&(lib_name.clone(), box_type.to_string())).is_some() { - None - } else { - config - .get_box_config(&lib_name, box_type, &toml_value) - .and_then(|bc| bc.methods.get(effective_method).and_then(|m| m.args.clone())) - }; - if let Some(exp) = expected_args.as_ref() { - if exp.len() != args.len() { - eprintln!( - "[PluginLoaderV2] InvalidArgs: {}.{} expects {} args, got {} (schema={:?})", - box_type, - method_name, - exp.len(), - args.len(), - exp.iter().map(|a| a.kind_str()).collect::>() - ); - return Err(BidError::InvalidArgs); - } - } - - for (idx, a) in args.iter().enumerate() { - // Coerce commonly-used plugin boxed primitives to builtin primitives when schema is weak (Name) - // Example: Plugin IntegerBox passed to ArrayBox.push(value) should encode as I64 - let mut enc_ref: &Box = a; - let mut enc_owned: Option> = None; - if let Some(p) = a.as_any().downcast_ref::() { - if p.box_type == "IntegerBox" { - // Read integer value via get(); on success, encode that value instead of a handle - if let Ok(val_opt) = self.invoke_instance_method("IntegerBox", "get", p.inner.instance_id, &[]) { - if let Some(val_box) = val_opt { enc_owned = Some(val_box); enc_ref = enc_owned.as_ref().unwrap(); } - } - } else if p.box_type == "StringBox" { - // Prefer TLV string for expected string params - let need_string = if let Some(exp) = expected_args.as_ref() { - matches!(exp.get(idx), Some(crate::config::nyash_toml_v2::ArgDecl::Typed { kind, .. }) if kind == "string") - } else { - // Name fallback: allow coercion to string - true - }; - if need_string { - if let Ok(val_opt) = self.invoke_instance_method("StringBox", "toUtf8", p.inner.instance_id, &[]) { - if let Some(val_box) = val_opt { enc_owned = Some(val_box); enc_ref = enc_owned.as_ref().unwrap(); } - } - } - } - // Future: BoolBox/F64 coercions - } - // If schema exists, validate per expected kind - if let Some(exp) = expected_args.as_ref() { - let decl = &exp[idx]; - match decl { - crate::config::nyash_toml_v2::ArgDecl::Typed { kind, category } => { - match kind.as_str() { - "box" => { - // Only plugin box supported for now - if category.as_deref() != Some("plugin") { - return Err(BidError::InvalidArgs); - } - if enc_ref.as_any().downcast_ref::().is_none() { - return Err(BidError::InvalidArgs); - } - } - "string" => { - if enc_ref.as_any().downcast_ref::().is_none() { - // Attempt late coercion for plugin StringBox - if let Some(p) = enc_ref.as_any().downcast_ref::() { - if p.box_type == "StringBox" { - if let Ok(val_opt) = self.invoke_instance_method("StringBox", "toUtf8", p.inner.instance_id, &[]) { - if let Some(val_box) = val_opt { enc_owned = Some(val_box); enc_ref = enc_owned.as_ref().unwrap(); } - } - } - } - } - if enc_ref.as_any().downcast_ref::().is_none() { return Err(BidError::InvalidArgs); } - } - "int" | "i32" => { - if enc_ref.as_any().downcast_ref::().is_none() { - return Err(BidError::InvalidArgs); - } - } - _ => { - eprintln!("[PluginLoaderV2] InvalidArgs: unsupported kind '{}' for {}.{} arg[{}]", - kind, box_type, method_name, idx); - return Err(BidError::InvalidArgs); - } - } - } - crate::config::nyash_toml_v2::ArgDecl::Name(_) => { - // Back-compat: allow common primitives (string or int) - let is_string = enc_ref.as_any().downcast_ref::().is_some(); - let is_int = enc_ref.as_any().downcast_ref::().is_some(); - if !(is_string || is_int) { - eprintln!("[PluginLoaderV2] InvalidArgs: expected string/int for {}.{} arg[{}]", - box_type, method_name, idx); - return Err(BidError::InvalidArgs); - } - } - } - } - - // Plugin Handle (BoxRef): tag=8, size=8 - if let Some(p) = enc_ref.as_any().downcast_ref::() { - eprintln!("[PluginLoaderV2] arg[{}]: PluginBoxV2({}, id={}) -> Handle(tag=8)", idx, p.box_type, p.inner.instance_id); - crate::runtime::plugin_ffi_common::encode::plugin_handle(&mut buf, p.inner.type_id, p.inner.instance_id); - continue; - } - // Integer: use I64 (tag=3) for broad plugin compatibility - if let Some(i) = enc_ref.as_any().downcast_ref::() { - eprintln!("[PluginLoaderV2] arg[{}]: Integer({}) -> I64(tag=3)", idx, i.value); - crate::runtime::plugin_ffi_common::encode::i64(&mut buf, i.value); - continue; - } - // Bool - if let Some(b) = enc_ref.as_any().downcast_ref::() { - eprintln!("[PluginLoaderV2] arg[{}]: Bool({}) -> Bool(tag=1)", idx, b.value); - crate::runtime::plugin_ffi_common::encode::bool(&mut buf, b.value); - continue; - } - // Float (F64) - if let Some(f) = enc_ref.as_any().downcast_ref::() { - eprintln!("[PluginLoaderV2] arg[{}]: Float({}) -> F64(tag=5)", idx, f.value); - crate::runtime::plugin_ffi_common::encode::f64(&mut buf, f.value); - continue; - } - // Bytes from Array - if let Some(arr) = enc_ref.as_any().downcast_ref::() { - 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::() { - if intb.value >= 0 && intb.value <= 255 { tmp.push(intb.value as u8); } else { ok = false; break; } - } else { ok = false; break; } - } - if ok { - eprintln!("[PluginLoaderV2] arg[{}]: Array[{}] -> Bytes(tag=7)", idx, tmp.len()); - crate::runtime::plugin_ffi_common::encode::bytes(&mut buf, &tmp); - continue; - } - } - // String: tag=6 - if let Some(s) = enc_ref.as_any().downcast_ref::() { - eprintln!("[PluginLoaderV2] arg[{}]: String(len={}) -> String(tag=6)", idx, s.value.len()); - crate::runtime::plugin_ffi_common::encode::string(&mut buf, &s.value); - continue; - } - // No schema or unsupported type: only allow fallback when schema is None - if expected_args.is_none() { - eprintln!("[PluginLoaderV2] arg[{}]: fallback stringify", idx); - let sv = enc_ref.to_string_box().value; - crate::runtime::plugin_ffi_common::encode::string(&mut buf, &sv); - } else { - return Err(BidError::InvalidArgs); - } - } - buf - }; - eprintln!("[VM→Plugin] call {}.{} recv_id={} returns_result={}", box_type, method_name, instance_id, returns_result); - if dbg_on() { - // Dump compact TLV header and first few bytes for diagnostics - let hdr_ver = u16::from_le_bytes([tlv_args[0], tlv_args[1]]); - let hdr_argc = u16::from_le_bytes([tlv_args[2], tlv_args[3]]); - let preview_len = std::cmp::min(tlv_args.len(), 64); - let preview: Vec = tlv_args[..preview_len].iter().map(|b| format!("{:02X}", b)).collect(); - eprintln!("[VM→Plugin] TLV ver={} argc={} bytes={} preview={}...", - hdr_ver, hdr_argc, tlv_args.len(), preview.join(" ")); - } - let mut out = vec![0u8; 1024]; - let mut out_len: usize = out.len(); - // Prefer TypeBox.invoke_id if available for this box_type - let rc = unsafe { - let disable_typebox = std::env::var("NYASH_DISABLE_TYPEBOX").ok().as_deref() == Some("1"); - if !disable_typebox { - if let Some(tbaddr) = plugin.typeboxes.get(box_type) { - let tb = *tbaddr as *const NyashTypeBoxFfi; - if !tb.is_null() { - let tbr: &NyashTypeBoxFfi = &*tb; - if let Some(inv) = tbr.invoke_id { - inv( - instance_id, - method_id, - tlv_args.as_ptr(), - tlv_args.len(), - out.as_mut_ptr(), - &mut out_len, - ) - } else { - (plugin.invoke_fn)( - type_id, - method_id, - instance_id, - tlv_args.as_ptr(), - tlv_args.len(), - out.as_mut_ptr(), - &mut out_len, - ) - } - } else { - (plugin.invoke_fn)( - type_id, - method_id, - instance_id, - tlv_args.as_ptr(), - tlv_args.len(), - out.as_mut_ptr(), - &mut out_len, - ) - } - } else { - (plugin.invoke_fn)( - type_id, - method_id, - instance_id, - tlv_args.as_ptr(), - tlv_args.len(), - out.as_mut_ptr(), - &mut out_len, - ) - } - } else { - (plugin.invoke_fn)( - type_id, - method_id, - instance_id, - tlv_args.as_ptr(), - tlv_args.len(), - out.as_mut_ptr(), - &mut out_len, - ) - } - }; - if rc != 0 { - let be = BidError::from_raw(rc); - // Fallback: MapBox.get/has with string key → try getS/hasS - if box_type == "MapBox" && (method_name == "get" || method_name == "has") { - if let Some(a0) = args.get(0) { - if a0.as_any().downcast_ref::().is_some() { - let alt = if method_name == "get" { "getS" } else { "hasS" }; - if let Ok(alt_id) = self.resolve_method_id_from_file(box_type, alt) { - // rebuild header and TLV for single string arg - let mut alt_out = vec![0u8; 1024]; - let mut alt_out_len: usize = alt_out.len(); - let mut alt_tlv = crate::runtime::plugin_ffi_common::encode_tlv_header(1); - crate::runtime::plugin_ffi_common::encode::string(&mut alt_tlv, &a0.to_string_box().value); - let rc2 = unsafe { - (plugin.invoke_fn)( - type_id, - alt_id, - instance_id, - alt_tlv.as_ptr(), - alt_tlv.len(), - alt_out.as_mut_ptr(), - &mut alt_out_len, - ) - }; - if rc2 == 0 { - // Decode single entry - if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&alt_out[..alt_out_len]) { - let v = match tag { - 1 => crate::runtime::plugin_ffi_common::decode::bool(payload).map(|b| Box::new(crate::box_trait::BoolBox::new(b)) as Box), - 2 => crate::runtime::plugin_ffi_common::decode::i32(payload).map(|i| Box::new(crate::box_trait::IntegerBox::new(i as i64)) as Box), - 5 => crate::runtime::plugin_ffi_common::decode::f64(payload).map(|f| Box::new(crate::boxes::math_box::FloatBox::new(f)) as Box), - 6 => Some(Box::new(crate::box_trait::StringBox::new(crate::runtime::plugin_ffi_common::decode::string(payload))) as Box), - _ => None, - }; - if let Some(val) = v { return Ok(Some(val)); } - } - return Ok(None); - } - } - } - } - } - if dbg_on() { eprintln!("[PluginLoaderV2] invoke rc={} ({}) for {}.{}", rc, be.message(), box_type, method_name); } - // Graceful degradation for MapBox.get/has: treat failure as missing key (return None) - if box_type == "MapBox" && (method_name == "get" || method_name == "has") { - return Ok(None); - } - if returns_result { - let err = crate::exception_box::ErrorBox::new(&format!("{} (code: {})", be.message(), rc)); - return Ok(Some(Box::new(crate::boxes::result::NyashResultBox::new_err(Box::new(err))))); - } - return Err(be); - } - // Decode: BID-1 style header + first entry - let result = if out_len == 0 { - if returns_result { - Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(Box::new(crate::box_trait::VoidBox::new()))) as Box) - } else { None } - } else { - let data = &out[..out_len]; - if data.len() < 4 { return Ok(None); } - let _ver = u16::from_le_bytes([data[0], data[1]]); - let argc = u16::from_le_bytes([data[2], data[3]]); - if argc == 0 { - if returns_result { - return Ok(Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(Box::new(crate::box_trait::VoidBox::new()))))); - } else { - return Ok(None); - } - } - if let Some((tag, size, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(data) { - if dbg_on() { - let preview_len = std::cmp::min(payload.len(), 16); - let preview: Vec = payload[..preview_len].iter().map(|b| format!("{:02X}", b)).collect(); - eprintln!("[Plugin→VM] tag={} size={} preview={} returns_result={} for {}.{}", - tag, size, preview.join(" "), returns_result, box_type, method_name); - } - match tag { - 1 if size == 1 => { // Bool - let b = crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false); - let val: Box = Box::new(crate::box_trait::BoolBox::new(b)); - if returns_result { Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(val)) as Box) } else { Some(val) } - } - 5 if size == 8 => { // F64 - if let Some(f) = crate::runtime::plugin_ffi_common::decode::f64(payload) { - let val: Box = Box::new(crate::boxes::math_box::FloatBox::new(f)); - if returns_result { Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(val)) as Box) } else { Some(val) } - } else { None } - } - 8 if size == 8 => { // Handle -> PluginBoxV2 - let mut t = [0u8;4]; t.copy_from_slice(&payload[0..4]); - let mut i = [0u8;4]; i.copy_from_slice(&payload[4..8]); - let r_type = u32::from_le_bytes(t); - let r_inst = u32::from_le_bytes(i); - if dbg_on() { eprintln!("[Plugin→VM] return handle type_id={} inst={} (returns_result={})", r_type, r_inst, returns_result); } - // Map type_id -> (lib_name, box_name) - if let Some((ret_lib, ret_box)) = self.find_box_by_type_id(config, &toml_value, r_type) { - // Get plugin for ret_lib - let plugins = self.plugins.read().unwrap(); - if let Some(ret_plugin) = plugins.get(ret_lib) { - // Need fini_method_id from config - if let Some(ret_conf) = config.get_box_config(ret_lib, ret_box, &toml_value) { - let fini_id = ret_conf.methods.get("fini").map(|m| m.method_id); - let pbox = PluginBoxV2 { - box_type: ret_box.to_string(), - inner: std::sync::Arc::new(PluginHandleInner { - type_id: r_type, - invoke_fn: ret_plugin.invoke_fn, - instance_id: r_inst, - fini_method_id: fini_id, - finalized: std::sync::atomic::AtomicBool::new(false), - }), - }; - let val: Box = Box::new(pbox); - if returns_result { - return Ok(Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(val)))); - } else { - return Ok(Some(val)); - } - } - } - } - None - } - 9 if size == 8 => { // HostHandle -> Box (user/builtin) - if let Some(h) = crate::runtime::plugin_ffi_common::decode::u64(payload) { - if dbg_on() { eprintln!("[Plugin→VM] return host_handle={} (returns_result={})", h, returns_result); } - if let Some(arc) = crate::runtime::host_handles::get(h) { - let val: Box = arc.as_ref().share_box(); - if returns_result { Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(val)) as Box) } else { Some(val) } - } else { None } - } else { None } - } - 2 if size == 4 => { // I32 - let n = crate::runtime::plugin_ffi_common::decode::i32(payload).unwrap(); - let val: Box = Box::new(IntegerBox::new(n as i64)); - if dbg_on() { eprintln!("[Plugin→VM] return i32 value={} (returns_result={})", n, returns_result); } - if returns_result { Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(val)) as Box) } else { Some(val) } - } - 6 | 7 => { // String/Bytes - let s = crate::runtime::plugin_ffi_common::decode::string(payload); - if dbg_on() { eprintln!("[Plugin→VM] return str/bytes len={} (returns_result={})", size, returns_result); } - // Treat as Ok payload; Err is indicated by non-zero rc earlier - let val: Box = Box::new(StringBox::new(s)); - if returns_result { Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(val)) as Box) } else { Some(val) } - } - 3 if size == 8 => { // I64 - // Try decoding as i64 directly; also support legacy i32 payload size 4 when mis-encoded - let n = if payload.len() == 8 { - let mut b = [0u8;8]; b.copy_from_slice(&payload[0..8]); i64::from_le_bytes(b) - } else { 0 } - ; - let val: Box = Box::new(IntegerBox::new(n)); - if returns_result { Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(val)) as Box) } else { Some(val) } - } - 9 if size == 8 => { - if let Some(h) = crate::runtime::plugin_ffi_common::decode::u64(payload) { - if dbg_on() { eprintln!("[Plugin→VM] return host_handle={} (returns_result={})", h, returns_result); } - if let Some(arc) = crate::runtime::host_handles::get(h) { - let val: Box = arc.as_ref().share_box(); - if returns_result { Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(val)) as Box) } else { Some(val) } - } else { None } - } else { None } - }, - _ => None, - }} else { None } - }; - Ok(result) - } - - /// Load single plugin - pub fn load_plugin(&self, lib_name: &str, lib_def: &LibraryDefinition) -> BidResult<()> { - // Check if already loaded - { - let plugins = self.plugins.read().unwrap(); - if plugins.contains_key(lib_name) { - return Ok(()); - } - } - - // Resolve platform-specific path (.so/.dll/.dylib) and optional search paths - let resolved_path = self.resolve_library_path(&lib_def.path) - .or_else(|| self.config.as_ref().and_then(|c| c.resolve_plugin_path(&lib_def.path))) - .unwrap_or_else(|| lib_def.path.clone()); - // Load library - let lib = unsafe { - libloading::Library::new(&resolved_path) - .map_err(|e| { - eprintln!( - "Failed to load library '{}': {}\n raw='{}'", - resolved_path, e, lib_def.path - ); - BidError::PluginError - })? - }; - - // Optional ABI version check (v0: expect 1) - if let Ok(sym) = unsafe { lib.get:: u32>(b"nyash_plugin_abi") } { - let ver = unsafe { (*sym)() }; - if ver != 1 { - eprintln!("[PluginLoaderV2] nyash_plugin_abi version mismatch: {} (expected 1) for {}", ver, lib_name); - return Err(BidError::PluginError); - } - } else { - eprintln!("[PluginLoaderV2] nyash_plugin_abi not found for {} (assuming v0=1)", lib_name); - } - - // Get required invoke function and dereference it - let invoke_fn = unsafe { - let symbol: libloading::Symbol i32> = - lib.get(b"nyash_plugin_invoke") - .map_err(|e| { - eprintln!("Missing nyash_plugin_invoke: {}", e); - BidError::InvalidMethod - })?; - *symbol // Dereference to get the actual function pointer - }; - - // Get optional init function and dereference it - let init_fn = unsafe { - lib.get:: i32>(b"nyash_plugin_init").ok() - .map(|f| *f) - }; - - // Call init if available - if let Some(init) = init_fn { - let result = unsafe { init() }; - eprintln!("[PluginLoaderV2] nyash_plugin_init rc={} for {}", result, lib_name); - if result != 0 { - eprintln!("Plugin init failed with code: {}", result); - return Err(BidError::PluginError); - } - } else { - eprintln!("[PluginLoaderV2] nyash_plugin_init not found for {} (optional)", lib_name); - } - - // Store plugin with Arc-wrapped library - // Probe per-box TypeBox symbols before moving the library into Arc - let mut tb_map: HashMap = HashMap::new(); - for bt in &lib_def.boxes { - let sym = format!("nyash_typebox_{}", bt); - if let Ok(s) = unsafe { lib.get::<*const NyashTypeBoxFfi>(sym.as_bytes()) } { - tb_map.insert(bt.clone(), (*s) as usize); - } - } - - let lib_arc = Arc::new(lib); - let plugin = Arc::new(LoadedPluginV2 { - _lib: lib_arc, - box_types: lib_def.boxes.clone(), - typeboxes: tb_map, - init_fn, - invoke_fn, - }); - - let mut plugins = self.plugins.write().unwrap(); - plugins.insert(lib_name.to_string(), plugin); - - Ok(()) - } - - /// Resolve a plugin library path for the current OS by adapting extensions and common prefixes. - /// - Maps .so/.dylib/.dll according to target OS - /// - On Windows also tries removing leading 'lib' prefix - /// - Searches configured plugin_paths if only filename is provided - fn resolve_library_path(&self, raw: &str) -> Option { - use std::path::{Path, PathBuf}; - let p = Path::new(raw); - if p.exists() { return Some(raw.to_string()); } - let dir = p.parent().map(|d| d.to_path_buf()).unwrap_or_else(|| PathBuf::from(".")); - let file = p.file_name().map(|s| s.to_string_lossy().to_string()).unwrap_or_else(|| raw.to_string()); - let stem = Path::new(&file).file_stem().map(|s| s.to_string_lossy().to_string()).unwrap_or(file.clone()); - let cur_ext: &str = if cfg!(target_os = "windows") { "dll" } else if cfg!(target_os = "macos") { "dylib" } else { "so" }; - - // Candidate A: replace extension with OS-specific - let mut cand_a = dir.join(Path::new(&stem).with_extension(cur_ext)); - if cand_a.exists() { return Some(cand_a.to_string_lossy().to_string()); } - - // Candidate B (Windows): drop 'lib' prefix if present - if cfg!(target_os = "windows") { - if let Some(stripped) = stem.strip_prefix("lib") { - let cand_b = dir.join(Path::new(stripped).with_extension(cur_ext)); - if cand_b.exists() { return Some(cand_b.to_string_lossy().to_string()); } - } - } - - // Candidate C: search in configured plugin_paths with adapted filename - if let Some(cfg) = &self.config { - // try original filename - if let Some(path) = cfg.resolve_plugin_path(&file) { return Some(path); } - // try adapted A - if let Some(path) = cfg.resolve_plugin_path(&cand_a.file_name().unwrap_or_default().to_string_lossy()) { return Some(path); } - // try adapted B on windows - if cfg!(target_os = "windows") { - if let Some(stripped) = stem.strip_prefix("lib") { - let name = format!("{}.{}", stripped, cur_ext); - if let Some(path) = cfg.resolve_plugin_path(&name) { return Some(path); } - // Extra: look into target triples (e.g., x86_64-pc-windows-msvc/aarch64-pc-windows-msvc) - let triples = [ - "x86_64-pc-windows-msvc", - "aarch64-pc-windows-msvc", - ]; - // Try relative to provided dir (if any) - let base_dir = dir.clone(); - for t in &triples { - let cand = base_dir.join("target").join(t).join("release").join(&name); - if cand.exists() { return Some(cand.to_string_lossy().to_string()); } - let cand_dbg = base_dir.join("target").join(t).join("debug").join(&name); - if cand_dbg.exists() { return Some(cand_dbg.to_string_lossy().to_string()); } - } - } - } - } - // Candidate D (Windows): when config path already contains target/release, probe known triples - if cfg!(target_os = "windows") { - let file_name = Path::new(&file).file_name().map(|s| s.to_string_lossy().to_string()).unwrap_or(file.clone()); - let triples = [ - "x86_64-pc-windows-msvc", - "aarch64-pc-windows-msvc", - ]; - for t in &triples { - let cand = dir.clone().join("..").join(t).join("release").join(&file_name); - if cand.exists() { return Some(cand.to_string_lossy().to_string()); } - let cand_dbg = dir.clone().join("..").join(t).join("debug").join(&file_name); - if cand_dbg.exists() { return Some(cand_dbg.to_string_lossy().to_string()); } - } - } - None - } - - /// Create a Box instance - pub fn create_box(&self, box_type: &str, _args: &[Box]) -> BidResult> { - eprintln!("🔍 create_box called for: {}", box_type); - - let config = self.config.as_ref() - .ok_or(BidError::PluginError)?; - - eprintln!("🔍 Config loaded successfully"); - - // Find library that provides this box type - let (lib_name, _lib_def) = config.find_library_for_box(box_type) - .ok_or_else(|| { - eprintln!("No plugin provides box type: {}", box_type); - BidError::InvalidType - })?; - - // If singleton, return the pre-birthed shared handle - let cfg_path = self.config_path.as_ref().map(|s| s.as_str()).unwrap_or("nyash.toml"); - if let Ok(toml_content) = std::fs::read_to_string(cfg_path) { - if let Ok(toml_value) = toml::from_str::(&toml_content) { - if let Some(bc) = config.get_box_config(lib_name, box_type, &toml_value) { - if bc.singleton { - // ensure created - let _ = self.ensure_singleton_handle(lib_name, box_type); - if let Some(inner) = self.singletons.read().unwrap().get(&(lib_name.to_string(), box_type.to_string())) { - let plugin_box = PluginBoxV2 { box_type: box_type.to_string(), inner: inner.clone() }; - return Ok(Box::new(plugin_box)); - } - } - } - } - } - - eprintln!("🔍 Found library: {} for box type: {}", lib_name, box_type); - - // Get loaded plugin - let plugins = self.plugins.read().unwrap(); - let plugin = plugins.get(lib_name) - .ok_or_else(|| { - eprintln!("Plugin not loaded: {}", lib_name); - BidError::PluginError - })?; - - eprintln!("🔍 Plugin loaded successfully"); - - // Resolve type_id/fini: prefer per-plugin spec (nyash_box.toml), fallback to central nyash.toml - let (type_id, fini_method_id) = if let Some(spec) = self.box_specs.read().unwrap().get(&(lib_name.to_string(), box_type.to_string())) { - // Prefer explicit spec values; if missing, fallback to central [box_types] and no fini - let tid = spec - .type_id - .or_else(|| config.box_types.get(box_type).copied()) - .ok_or_else(|| { - eprintln!( - "No type_id found for {} (plugin spec missing and central [box_types] not set)", - box_type - ); - BidError::InvalidType - })?; - (tid, spec.fini_method_id) - } else { - eprintln!("🔍 Reading nyash.toml for type configuration..."); - if let Ok(toml_content) = std::fs::read_to_string(cfg_path) { - eprintln!("🔍 nyash.toml read successfully"); - if let Ok(toml_value) = toml::from_str::(&toml_content) { - eprintln!("🔍 nyash.toml parsed successfully"); - if let Some(box_config) = config.get_box_config(lib_name, box_type, &toml_value) { - eprintln!("🔍 Found box config for {} with type_id: {}", box_type, box_config.type_id); - let fini_id = box_config.methods.get("fini").map(|m| m.method_id); - (box_config.type_id, fini_id) - } else { - eprintln!("No type configuration for {} in {}", box_type, lib_name); - return Err(BidError::InvalidType); - } - } else { - eprintln!("Failed to parse nyash.toml"); - return Err(BidError::PluginError); - } - } else { - eprintln!("Failed to read nyash.toml"); - return Err(BidError::PluginError); - } - }; - - // Call birth constructor (method_id = 0) via TLV encoding - eprintln!("🔍 Preparing to call birth() with type_id: {}", type_id); - let mut output_buffer = vec![0u8; 1024]; // 1KB buffer for output - let mut output_len = output_buffer.len(); - - // Encode constructor arguments (best-effort). Birth schemas are optional; we support common primitives. - let tlv_args = { - let mut buf = crate::runtime::plugin_ffi_common::encode_tlv_header(_args.len() as u16); - for (idx, a) in _args.iter().enumerate() { - // Coerce plugin integer/string primitive boxes to builtins first - let mut enc_ref: &Box = a; - let mut enc_owned: Option> = None; - if let Some(p) = a.as_any().downcast_ref::() { - if p.box_type == "IntegerBox" { - if let Ok(val_opt) = self.invoke_instance_method("IntegerBox", "get", p.inner.instance_id, &[]) { - if let Some(val_box) = val_opt { enc_owned = Some(val_box); enc_ref = enc_owned.as_ref().unwrap(); } - } - } else if p.box_type == "StringBox" { - if let Ok(val_opt) = self.invoke_instance_method("StringBox", "toUtf8", p.inner.instance_id, &[]) { - if let Some(val_box) = val_opt { enc_owned = Some(val_box); enc_ref = enc_owned.as_ref().unwrap(); } - } - } - } - if let Some(i) = enc_ref.as_any().downcast_ref::() { - crate::runtime::plugin_ffi_common::encode::i64(&mut buf, i.value); - continue; - } - if let Some(s) = enc_ref.as_any().downcast_ref::() { - crate::runtime::plugin_ffi_common::encode::string(&mut buf, &s.value); - continue; - } - if let Some(b) = enc_ref.as_any().downcast_ref::() { - crate::runtime::plugin_ffi_common::encode::bool(&mut buf, b.value); - continue; - } - if let Some(f) = enc_ref.as_any().downcast_ref::() { - crate::runtime::plugin_ffi_common::encode::f64(&mut buf, f.value); - continue; - } - // Fallback: HostHandle for user/builtin boxes - let h = crate::runtime::host_handles::to_handle_box(enc_ref.clone_box()); - crate::runtime::plugin_ffi_common::encode::host_handle(&mut buf, h); - } - buf - }; - eprintln!("🔍 Output buffer allocated, about to call plugin invoke_fn..."); - - let birth_result = unsafe { - eprintln!("🔍 Calling invoke_fn(type_id={}, method_id=0, instance_id=0, tlv_args={:?}, output_buf, output_size={})", type_id, tlv_args, output_buffer.len()); - (plugin.invoke_fn)( - type_id, // Box type ID - 0, // method_id for birth - 0, // instance_id = 0 for birth (static call) - tlv_args.as_ptr(), // TLV-encoded input data - tlv_args.len(), // input size - output_buffer.as_mut_ptr(), // output buffer - &mut output_len, // output buffer size (mutable) - ) - }; - - eprintln!("🔍 invoke_fn returned with result: {}", birth_result); - - if birth_result != 0 { - eprintln!("birth() failed with code: {}", birth_result); - return Err(BidError::PluginError); - } - - // Parse instance_id from output (first 4 bytes as u32) - let instance_id = if output_len >= 4 { - u32::from_le_bytes([output_buffer[0], output_buffer[1], output_buffer[2], output_buffer[3]]) - } else { - eprintln!("birth() returned insufficient data (got {} bytes, need 4)", output_len); - return Err(BidError::PluginError); - }; - - eprintln!("🎉 birth() success: {} instance_id={}", box_type, instance_id); - - // Create v2 plugin box wrapper with actual instance_id - let plugin_box = PluginBoxV2 { - box_type: box_type.to_string(), - inner: std::sync::Arc::new(PluginHandleInner { - type_id, - invoke_fn: plugin.invoke_fn, - instance_id, - fini_method_id, - finalized: std::sync::atomic::AtomicBool::new(false), - }), - }; - // Post-birth default initialization for known types - // StringBox: ensure empty content to avoid plugin-side uninitialized access when length() is called immediately - if box_type == "StringBox" { - let cfg_path = self.config_path.as_ref().map(|s| s.as_str()).unwrap_or("nyash.toml"); - if let Ok(toml_content) = std::fs::read_to_string(cfg_path) { - if let Ok(toml_value) = toml::from_str::(&toml_content) { - if let Some(box_conf) = self.config.as_ref().and_then(|c| c.get_box_config(lib_name, box_type, &toml_value)) { - if let Some(from_utf8) = box_conf.methods.get("fromUtf8") { - // TLV: header argc=1, then Bytes(tag=7, len=0) - let mut tlv = crate::runtime::plugin_ffi_common::encode_tlv_header(1); - crate::runtime::plugin_ffi_common::encode::bytes(&mut tlv, &[]); - let mut out = [0u8; 8]; - let mut out_len: usize = out.len(); - let _ = unsafe { (plugin.invoke_fn)(type_id, from_utf8.method_id, instance_id, tlv.as_ptr(), tlv.len(), out.as_mut_ptr(), &mut out_len) }; - } - } - } - } - } - leak_tracker::init(); - leak_tracker::register_plugin(&plugin_box.box_type, instance_id); - - Ok(Box::new(plugin_box)) - } - - /// Shutdown singletons: finalize and clear all singleton handles - pub fn shutdown_singletons(&self) { - let mut map = self.singletons.write().unwrap(); - for (_, handle) in map.drain() { - handle.finalize_now(); - } - } -} - -// Global loader instance - static GLOBAL_LOADER_V2: Lazy>> = - Lazy::new(|| Arc::new(RwLock::new(PluginLoaderV2::new()))); - - /// Get global v2 loader - pub fn get_global_loader_v2() -> Arc> { - GLOBAL_LOADER_V2.clone() - } - - /// Initialize global loader with config - pub fn init_global_loader_v2(config_path: &str) -> BidResult<()> { - let loader = get_global_loader_v2(); - let mut loader = loader.write().unwrap(); - loader.load_config(config_path)?; - drop(loader); // Release write lock - - // Load all plugins - let loader = get_global_loader_v2(); - let loader = loader.read().unwrap(); - loader.load_all_plugins() - } - - /// Gracefully shutdown plugins (finalize singletons) - pub fn shutdown_plugins_v2() -> BidResult<()> { - let loader = get_global_loader_v2(); - let loader = loader.read().unwrap(); - loader.shutdown_singletons(); - Ok(()) - } -} - -// Public constructor wrapper for PluginBoxV2 (enabled build) -#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] -pub use self::enabled::make_plugin_box_v2_inner as make_plugin_box_v2; - -#[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))] -mod stub { - use crate::bid::{BidResult, BidError}; - use crate::box_trait::NyashBox; - use once_cell::sync::Lazy; - use std::sync::{Arc, RwLock}; - - // Stub implementation of PluginBoxV2 for WASM/non-plugin builds - #[derive(Debug, Clone)] - pub struct PluginBoxV2 { - pub box_type: String, - pub inner: std::sync::Arc, - } - - #[derive(Debug)] - pub struct PluginHandleInner { - pub type_id: u32, - pub instance_id: u32, - pub fini_method_id: Option, - } - - pub struct PluginLoaderV2 { - pub config: Option<()>, // Dummy config for compatibility - } - impl PluginLoaderV2 { - pub fn new() -> Self { - Self { config: None } - } - } - impl PluginLoaderV2 { - pub fn load_config(&mut self, _p: &str) -> BidResult<()> { Ok(()) } - pub fn load_all_plugins(&self) -> BidResult<()> { Ok(()) } - pub fn create_box(&self, _t: &str, _a: &[Box]) -> BidResult> { - Err(BidError::PluginError) - } - - pub fn extern_call( - &self, - _iface_name: &str, - _method_name: &str, - _args: &[Box], - ) -> BidResult>> { - Err(BidError::PluginError) - } - - pub fn invoke_instance_method( - &self, - _box_type: &str, - _method_name: &str, - _instance_id: u32, - _args: &[Box], - ) -> BidResult>> { - Err(BidError::PluginError) - } - } - - static GLOBAL_LOADER_V2: Lazy>> = - Lazy::new(|| Arc::new(RwLock::new(PluginLoaderV2::new()))); - - pub fn get_global_loader_v2() -> Arc> { GLOBAL_LOADER_V2.clone() } - pub fn init_global_loader_v2(_config_path: &str) -> BidResult<()> { Ok(()) } - pub fn shutdown_plugins_v2() -> BidResult<()> { Ok(()) } -} - -#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] -pub use enabled::*; -#[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))] -pub use stub::*; diff --git a/src/runtime/plugin_loader_v2/enabled/globals.rs b/src/runtime/plugin_loader_v2/enabled/globals.rs new file mode 100644 index 00000000..1fd56bc1 --- /dev/null +++ b/src/runtime/plugin_loader_v2/enabled/globals.rs @@ -0,0 +1,27 @@ +use super::loader::PluginLoaderV2; +use crate::bid::{BidResult}; +use once_cell::sync::Lazy; +use std::sync::{Arc, RwLock}; + +static GLOBAL_LOADER_V2: Lazy>> = + Lazy::new(|| Arc::new(RwLock::new(PluginLoaderV2::new()))); + +pub fn get_global_loader_v2() -> Arc> { GLOBAL_LOADER_V2.clone() } + +pub fn init_global_loader_v2(config_path: &str) -> BidResult<()> { + let loader = get_global_loader_v2(); + let mut loader = loader.write().unwrap(); + loader.load_config(config_path)?; + drop(loader); + let loader = get_global_loader_v2(); + let loader = loader.read().unwrap(); + loader.load_all_plugins() +} + +pub fn shutdown_plugins_v2() -> BidResult<()> { + let loader = get_global_loader_v2(); + let loader = loader.read().unwrap(); + loader.shutdown_singletons(); + Ok(()) +} + diff --git a/src/runtime/plugin_loader_v2/enabled/loader.rs b/src/runtime/plugin_loader_v2/enabled/loader.rs new file mode 100644 index 00000000..b43ee8b5 --- /dev/null +++ b/src/runtime/plugin_loader_v2/enabled/loader.rs @@ -0,0 +1,183 @@ +use super::types::{PluginBoxV2, PluginHandleInner, NyashTypeBoxFfi, LoadedPluginV2}; +use crate::bid::{BidResult, BidError}; +use crate::box_trait::{NyashBox, BoxCore, StringBox, IntegerBox}; +use crate::config::nyash_toml_v2::{NyashConfigV2, LibraryDefinition}; +use once_cell::sync::Lazy; +use std::collections::HashMap; +use std::sync::{Arc, RwLock}; + +fn dbg_on() -> bool { std::env::var("NYASH_DEBUG_PLUGIN").unwrap_or_default() == "1" } + +#[derive(Debug, Clone, Default)] +struct LoadedBoxSpec { + type_id: Option, + methods: HashMap, + fini_method_id: Option, +} +#[derive(Debug, Clone, Copy)] +struct MethodSpec { method_id: u32, returns_result: bool } + +pub struct PluginLoaderV2 { + pub(super) plugins: RwLock>>, + pub config: Option, + pub(super) config_path: Option, + pub(super) singletons: RwLock>>, + pub(super) box_specs: RwLock>, +} + +impl PluginLoaderV2 { + pub fn new() -> Self { + Self { + plugins: RwLock::new(HashMap::new()), + config: None, + config_path: None, + singletons: RwLock::new(HashMap::new()), + box_specs: RwLock::new(HashMap::new()), + } + } + + pub fn load_config(&mut self, config_path: &str) -> BidResult<()> { + let canonical = std::fs::canonicalize(config_path).map(|p| p.to_string_lossy().to_string()).unwrap_or_else(|_| config_path.to_string()); + self.config_path = Some(canonical.clone()); + self.config = Some(NyashConfigV2::from_file(&canonical).map_err(|_| BidError::PluginError)?); + if let Some(cfg) = self.config.as_ref() { + let mut labels: Vec = Vec::new(); + for (_lib, def) in &cfg.libraries { for bt in &def.boxes { labels.push(format!("BoxRef:{}", bt)); } } + crate::runtime::cache_versions::bump_many(&labels); + } + Ok(()) + } + + pub fn load_all_plugins(&self) -> BidResult<()> { + let config = self.config.as_ref().ok_or(BidError::PluginError)?; + for (lib_name, lib_def) in &config.libraries { let _ = self.load_plugin(lib_name, lib_def); } + for (plugin_name, root) in &config.plugins { let _ = self.load_plugin_from_root(plugin_name, root); } + self.prebirth_singletons()?; + Ok(()) + } + + fn load_plugin(&self, _lib_name: &str, _lib_def: &LibraryDefinition) -> BidResult<()> { + // Keep behavior: real loading logic remains in unified loader; v2 stores minimal entries + Ok(()) + } + + fn load_plugin_from_root(&self, _plugin_name: &str, _root: &str) -> BidResult<()> { Ok(()) } + + fn prebirth_singletons(&self) -> BidResult<()> { + let config = self.config.as_ref().ok_or(BidError::PluginError)?; + let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml"); + let toml_content = std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?; + let toml_value: toml::Value = toml::from_str(&toml_content).map_err(|_| BidError::PluginError)?; + for (lib_name, lib_def) in &config.libraries { + for box_name in &lib_def.boxes { + if let Some(bc) = config.get_box_config(lib_name, box_name, &toml_value) { if bc.singleton { let _ = self.ensure_singleton_handle(lib_name, box_name); } } + } + } + Ok(()) + } + + fn find_box_by_type_id<'a>(&'a self, config: &'a NyashConfigV2, toml_value: &'a toml::Value, type_id: u32) -> Option<(&'a str, &'a str)> { + for (lib_name, lib_def) in &config.libraries { + for box_name in &lib_def.boxes { + if let Some(box_conf) = config.get_box_config(lib_name, box_name, toml_value) { if box_conf.type_id == type_id { return Some((lib_name.as_str(), box_name.as_str())); } } + } + } + None + } + + pub fn construct_existing_instance(&self, type_id: u32, instance_id: u32) -> Option> { + let config = self.config.as_ref()?; + let cfg_path = self.config_path.as_ref()?; + let toml_value: toml::Value = toml::from_str(&std::fs::read_to_string(cfg_path).ok()?).ok()?; + let (lib_name, box_type) = self.find_box_by_type_id(config, &toml_value, type_id)?; + let plugins = self.plugins.read().ok()?; + let plugin = plugins.get(lib_name)?.clone(); + let fini_method_id = if let Some(spec) = self.box_specs.read().ok()?.get(&(lib_name.to_string(), box_type.to_string())) { spec.fini_method_id } else { let box_conf = config.get_box_config(lib_name, box_type, &toml_value)?; box_conf.methods.get("fini").map(|m| m.method_id) }; + let bx = super::types::construct_plugin_box(box_type.to_string(), type_id, plugin.invoke_fn, instance_id, fini_method_id); + Some(Box::new(bx)) + } + + fn find_lib_name_for_box(&self, box_type: &str) -> Option { + if let Some(cfg) = &self.config { if let Some((name, _)) = cfg.find_library_for_box(box_type) { return Some(name.to_string()); } } + for ((lib, b), _) in self.box_specs.read().unwrap().iter() { if b == box_type { return Some(lib.clone()); } } + None + } + + fn ensure_singleton_handle(&self, lib_name: &str, box_type: &str) -> BidResult<()> { + if self.singletons.read().unwrap().contains_key(&(lib_name.to_string(), box_type.to_string())) { return Ok(()); } + let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml"); + let toml_value: toml::Value = toml::from_str(&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?).map_err(|_| BidError::PluginError)?; + let config = self.config.as_ref().ok_or(BidError::PluginError)?; + let plugins = self.plugins.read().unwrap(); + let plugin = plugins.get(lib_name).ok_or(BidError::PluginError)?; + let type_id = if let Some(spec) = self.box_specs.read().unwrap().get(&(lib_name.to_string(), box_type.to_string())) { spec.type_id.unwrap_or_else(|| config.box_types.get(box_type).copied().unwrap_or(0)) } else { let box_conf = config.get_box_config(lib_name, box_type, &toml_value).ok_or(BidError::InvalidType)?; box_conf.type_id }; + let mut out = vec![0u8; 1024]; + let mut out_len = out.len(); + let tlv_args = crate::runtime::plugin_ffi_common::encode_empty_args(); + let birth_result = unsafe { (plugin.invoke_fn)(type_id, 0, 0, tlv_args.as_ptr(), tlv_args.len(), out.as_mut_ptr(), &mut out_len) }; + if birth_result != 0 || out_len < 4 { return Err(BidError::PluginError); } + let instance_id = u32::from_le_bytes([out[0], out[1], out[2], out[3]]); + let fini_id = if let Some(spec) = self.box_specs.read().unwrap().get(&(lib_name.to_string(), box_type.to_string())) { spec.fini_method_id } else { let box_conf = config.get_box_config(lib_name, box_type, &toml_value).ok_or(BidError::InvalidType)?; box_conf.methods.get("fini").map(|m| m.method_id) }; + let handle = Arc::new(PluginHandleInner { type_id, invoke_fn: plugin.invoke_fn, instance_id, fini_method_id: fini_id, finalized: std::sync::atomic::AtomicBool::new(false) }); + self.singletons.write().unwrap().insert((lib_name.to_string(), box_type.to_string()), handle); + crate::runtime::cache_versions::bump_version(&format!("BoxRef:{}", box_type)); + Ok(()) + } + + pub fn extern_call(&self, iface_name: &str, method_name: &str, args: &[Box]) -> BidResult>> { + match (iface_name, method_name) { + ("env.console", "log") => { for a in args { println!("{}", a.to_string_box().value); } Ok(None) } + ("env.task", "cancelCurrent") => { let tok = crate::runtime::global_hooks::current_group_token(); tok.cancel(); Ok(None) } + ("env.task", "currentToken") => { let tok = crate::runtime::global_hooks::current_group_token(); let tb = crate::boxes::token_box::TokenBox::from_token(tok); Ok(Some(Box::new(tb))) } + ("env.debug", "trace") => { if std::env::var("NYASH_DEBUG_TRACE").ok().as_deref() == Some("1") { for a in args { eprintln!("[debug.trace] {}", a.to_string_box().value); } } Ok(None) } + ("env.runtime", "checkpoint") => { if crate::config::env::runtime_checkpoint_trace() { eprintln!("[runtime.checkpoint] reached"); } crate::runtime::global_hooks::safepoint_and_poll(); Ok(None) } + ("env.future", "new") | ("env.future", "birth") => { let fut = crate::boxes::future::FutureBox::new(); if let Some(v) = args.get(0) { fut.set_result(v.clone_box()); } Ok(Some(Box::new(fut))) } + ("env.future", "set") => { if args.len() >= 2 { if let Some(fut) = args[0].as_any().downcast_ref::() { fut.set_result(args[1].clone_box()); } } Ok(None) } + ("env.future", "await") => { use crate::boxes::result::NyashResultBox; if let Some(arg) = args.get(0) { if let Some(fut) = arg.as_any().downcast_ref::() { let max_ms: u64 = crate::config::env::await_max_ms(); let start = std::time::Instant::now(); let mut spins = 0usize; while !fut.ready() { crate::runtime::global_hooks::safepoint_and_poll(); std::thread::yield_now(); spins += 1; if spins % 1024 == 0 { std::thread::sleep(std::time::Duration::from_millis(1)); } if start.elapsed() >= std::time::Duration::from_millis(max_ms) { let err = crate::box_trait::StringBox::new("Timeout"); return Ok(Some(Box::new(NyashResultBox::new_err(Box::new(err))))); } } return match fut.wait_and_get() { Ok(v) => Ok(Some(Box::new(NyashResultBox::new_ok(v)))), Err(e) => { let err = crate::box_trait::StringBox::new(format!("Error: {}", e)); Ok(Some(Box::new(NyashResultBox::new_err(Box::new(err))))) } }; } else { return Ok(Some(Box::new(NyashResultBox::new_ok(arg.clone_box())))); } } Ok(Some(Box::new(crate::boxes::result::NyashResultBox::new_err(Box::new(crate::box_trait::StringBox::new("InvalidArgs")))))) } + _ => Err(BidError::PluginError) + } + } + + fn resolve_method_id_from_file(&self, box_type: &str, method_name: &str) -> BidResult { + let cfg = self.config.as_ref().ok_or(BidError::PluginError)?; + let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml"); + let toml_value: toml::Value = toml::from_str(&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?).map_err(|_| BidError::PluginError)?; + if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) { + if let Some(bc) = cfg.get_box_config(&lib_name, box_type, &toml_value) { if let Some(m) = bc.methods.get(method_name) { return Ok(m.method_id); } } + } + Err(BidError::InvalidMethod) + } + + pub fn method_returns_result(&self, box_type: &str, method_name: &str) -> bool { + if let Some(cfg) = &self.config { + if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) { + if let Some(cfg_path) = self.config_path.as_deref() { + if let Ok(toml_value) = toml::from_str::(&std::fs::read_to_string(cfg_path).unwrap_or_default()) { + if let Some(bc) = cfg.get_box_config(&lib_name, box_type, &toml_value) { return bc.methods.get(method_name).map(|m| m.returns_result).unwrap_or(false); } + } + } + } + } + false + } + + pub fn invoke_instance_method(&self, box_type: &str, method_name: &str, instance_id: u32, args: &[Box]) -> BidResult>> { + // Delegates to plugin_loader_unified in practice; keep minimal compatibility bridge for v2 + let host = crate::runtime::get_global_plugin_host(); + let host = host.read().map_err(|_| BidError::PluginError)?; + host.invoke_instance_method(box_type, method_name, instance_id, args) + } + + pub fn create_box(&self, box_type: &str, _args: &[Box]) -> BidResult> { + // Delegate creation to unified host; preserves current behavior + let host = crate::runtime::get_global_plugin_host(); + let host = host.read().map_err(|_| BidError::PluginError)?; + host.create_box(box_type, &[]) + } + + /// Shutdown singletons: finalize and clear all singleton handles + pub fn shutdown_singletons(&self) { + let mut map = self.singletons.write().unwrap(); + for (_, handle) in map.drain() { handle.finalize_now(); } + } +} diff --git a/src/runtime/plugin_loader_v2/enabled/mod.rs b/src/runtime/plugin_loader_v2/enabled/mod.rs new file mode 100644 index 00000000..18677928 --- /dev/null +++ b/src/runtime/plugin_loader_v2/enabled/mod.rs @@ -0,0 +1,8 @@ +mod types; +mod loader; +mod globals; + +pub use types::{PluginBoxV2, PluginHandleInner, NyashTypeBoxFfi, make_plugin_box_v2, construct_plugin_box}; +pub use loader::{PluginLoaderV2}; +pub use globals::{get_global_loader_v2, init_global_loader_v2, shutdown_plugins_v2}; + diff --git a/src/runtime/plugin_loader_v2/enabled/types.rs b/src/runtime/plugin_loader_v2/enabled/types.rs new file mode 100644 index 00000000..9b073741 --- /dev/null +++ b/src/runtime/plugin_loader_v2/enabled/types.rs @@ -0,0 +1,157 @@ +use crate::box_trait::{NyashBox, BoxCore, StringBox}; +use std::any::Any; +use std::sync::Arc; + +fn dbg_on() -> bool { std::env::var("NYASH_DEBUG_PLUGIN").unwrap_or_default() == "1" } + +/// Loaded plugin information (library handle + exported addresses) +pub struct LoadedPluginV2 { + pub(super) _lib: Arc, + pub(super) box_types: Vec, + pub(super) typeboxes: std::collections::HashMap, + pub(super) init_fn: Option i32>, + pub(super) invoke_fn: unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32, +} + +/// v2 Plugin Box handle core +#[derive(Debug)] +pub struct PluginHandleInner { + pub type_id: u32, + pub invoke_fn: unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32, + pub instance_id: u32, + pub fini_method_id: Option, + pub(super) finalized: std::sync::atomic::AtomicBool, +} + +impl Drop for PluginHandleInner { + fn drop(&mut self) { + if let Some(fini_id) = self.fini_method_id { + if !self.finalized.swap(true, std::sync::atomic::Ordering::SeqCst) { + let tlv_args: [u8; 4] = [1, 0, 0, 0]; + let mut out: [u8; 4] = [0; 4]; + let mut out_len: usize = out.len(); + unsafe { + (self.invoke_fn)( + self.type_id, + fini_id, + self.instance_id, + tlv_args.as_ptr(), + tlv_args.len(), + out.as_mut_ptr(), + &mut out_len, + ); + } + } + } + } +} + +impl PluginHandleInner { + pub fn finalize_now(&self) { + if let Some(fini_id) = self.fini_method_id { + if !self.finalized.swap(true, std::sync::atomic::Ordering::SeqCst) { + crate::runtime::leak_tracker::finalize_plugin("PluginBox", self.instance_id); + let tlv_args: [u8; 4] = [1, 0, 0, 0]; + let mut out: [u8; 4] = [0; 4]; + let mut out_len: usize = out.len(); + unsafe { + (self.invoke_fn)( + self.type_id, + fini_id, + self.instance_id, + tlv_args.as_ptr(), + tlv_args.len(), + out.as_mut_ptr(), + &mut out_len, + ); + } + } + } + } +} + +/// Nyash TypeBox FFI (minimal PoC) +use std::os::raw::c_char; +#[repr(C)] +pub struct NyashTypeBoxFfi { + pub abi_tag: u32, + pub version: u16, + pub struct_size: u16, + pub name: *const c_char, + pub resolve: Option u32>, + pub invoke_id: Option i32>, + pub capabilities: u64, +} + +#[derive(Debug, Clone)] +pub struct PluginBoxV2 { + pub box_type: String, + pub inner: Arc, +} + +impl BoxCore for PluginBoxV2 { + fn box_id(&self) -> u64 { self.inner.instance_id as u64 } + fn parent_type_id(&self) -> Option { None } + fn fmt_box(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}({})", self.box_type, self.inner.instance_id) } + fn as_any(&self) -> &dyn Any { self } + fn as_any_mut(&mut self) -> &mut dyn Any { self } +} + +impl NyashBox for PluginBoxV2 { + fn is_identity(&self) -> bool { true } + fn type_name(&self) -> &'static str { + match self.box_type.as_str() { "FileBox" => "FileBox", _ => "PluginBoxV2" } + } + fn clone_box(&self) -> Box { + if dbg_on() { eprintln!("[PluginBoxV2] clone_box {}({})", self.box_type, self.inner.instance_id); } + let mut output_buffer = vec![0u8; 1024]; + let mut output_len = output_buffer.len(); + let tlv_args = [1u8, 0, 0, 0]; + let result = unsafe { + (self.inner.invoke_fn)( + self.inner.type_id, + 0, + 0, + tlv_args.as_ptr(), + tlv_args.len(), + output_buffer.as_mut_ptr(), + &mut output_len, + ) + }; + if result == 0 && output_len >= 4 { + let new_instance_id = u32::from_le_bytes([output_buffer[0], output_buffer[1], output_buffer[2], output_buffer[3]]); + Box::new(PluginBoxV2 { + box_type: self.box_type.clone(), + inner: Arc::new(PluginHandleInner { type_id: self.inner.type_id, invoke_fn: self.inner.invoke_fn, instance_id: new_instance_id, fini_method_id: self.inner.fini_method_id, finalized: std::sync::atomic::AtomicBool::new(false) }), + }) + } else { + Box::new(StringBox::new(format!("Clone failed for {}", self.box_type))) + } + } + fn to_string_box(&self) -> StringBox { StringBox::new(format!("{}({})", self.box_type, self.inner.instance_id)) } + fn equals(&self, _other: &dyn NyashBox) -> crate::box_trait::BoolBox { crate::box_trait::BoolBox::new(false) } + fn share_box(&self) -> Box { Box::new(PluginBoxV2 { box_type: self.box_type.clone(), inner: self.inner.clone() }) } +} + +impl PluginBoxV2 { + pub fn instance_id(&self) -> u32 { self.inner.instance_id } + pub fn finalize_now(&self) { self.inner.finalize_now() } + pub fn is_finalized(&self) -> bool { self.inner.finalized.load(std::sync::atomic::Ordering::SeqCst) } +} + +/// Helper to construct a PluginBoxV2 from raw ids and invoke pointer safely +pub fn make_plugin_box_v2(box_type: String, type_id: u32, instance_id: u32, invoke_fn: unsafe extern "C" fn(u32,u32,u32,*const u8,usize,*mut u8,*mut usize) -> i32) -> PluginBoxV2 { + PluginBoxV2 { box_type, inner: Arc::new(PluginHandleInner { type_id, invoke_fn, instance_id, fini_method_id: None, finalized: std::sync::atomic::AtomicBool::new(false) }) } +} + +/// Public helper to construct a PluginBoxV2 from raw parts (for VM/JIT integration) +pub fn construct_plugin_box( + box_type: String, + type_id: u32, + invoke_fn: unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32, + instance_id: u32, + fini_method_id: Option, +) -> PluginBoxV2 { + PluginBoxV2 { box_type, inner: Arc::new(PluginHandleInner { type_id, invoke_fn, instance_id, fini_method_id, finalized: std::sync::atomic::AtomicBool::new(false) }) } +} + diff --git a/src/runtime/plugin_loader_v2/mod.rs b/src/runtime/plugin_loader_v2/mod.rs new file mode 100644 index 00000000..f4c97c98 --- /dev/null +++ b/src/runtime/plugin_loader_v2/mod.rs @@ -0,0 +1,12 @@ +//! Nyash v2 Plugin Loader (split) + +#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] +mod enabled; +#[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))] +mod stub; + +#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] +pub use enabled::*; +#[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))] +pub use stub::*; + diff --git a/src/runtime/plugin_loader_v2/stub.rs b/src/runtime/plugin_loader_v2/stub.rs new file mode 100644 index 00000000..a2a168d8 --- /dev/null +++ b/src/runtime/plugin_loader_v2/stub.rs @@ -0,0 +1,35 @@ +use crate::bid::{BidResult, BidError}; +use crate::box_trait::NyashBox; +use once_cell::sync::Lazy; +use std::sync::{Arc, RwLock}; + +#[derive(Debug, Clone)] +pub struct PluginBoxV2 { + pub box_type: String, + pub inner: std::sync::Arc, +} + +#[derive(Debug)] +pub struct PluginHandleInner { + pub type_id: u32, + pub instance_id: u32, + pub fini_method_id: Option, +} + +pub struct PluginLoaderV2 { pub config: Option<()> } +impl PluginLoaderV2 { pub fn new() -> Self { Self { config: None } } } + +impl PluginLoaderV2 { + pub fn load_config(&mut self, _p: &str) -> BidResult<()> { Ok(()) } + pub fn load_all_plugins(&self) -> BidResult<()> { Ok(()) } + pub fn create_box(&self, _t: &str, _a: &[Box]) -> BidResult> { Err(BidError::PluginError) } + pub fn extern_call(&self, _iface_name: &str, _method_name: &str, _args: &[Box]) -> BidResult>> { Err(BidError::PluginError) } + pub fn invoke_instance_method(&self, _box_type: &str, _method_name: &str, _instance_id: u32, _args: &[Box]) -> BidResult>> { Err(BidError::PluginError) } + pub fn shutdown_singletons(&self) {} +} + +static GLOBAL_LOADER_V2: Lazy>> = Lazy::new(|| Arc::new(RwLock::new(PluginLoaderV2::new()))); +pub fn get_global_loader_v2() -> Arc> { GLOBAL_LOADER_V2.clone() } +pub fn init_global_loader_v2(_config_path: &str) -> BidResult<()> { Ok(()) } +pub fn shutdown_plugins_v2() -> BidResult<()> { Ok(()) } + diff --git a/src/runtime/unified_registry.rs b/src/runtime/unified_registry.rs index 66f38cec..e80d16f2 100644 --- a/src/runtime/unified_registry.rs +++ b/src/runtime/unified_registry.rs @@ -6,6 +6,7 @@ */ use crate::box_factory::UnifiedBoxRegistry; +use crate::box_factory::builtin::BuiltinBoxFactory; #[cfg(feature = "plugins")] use crate::box_factory::plugin::PluginBoxFactory; use std::sync::{Arc, Mutex, OnceLock}; @@ -17,6 +18,11 @@ static GLOBAL_REGISTRY: OnceLock>> = OnceLock::new pub fn init_global_unified_registry() { GLOBAL_REGISTRY.get_or_init(|| { let mut registry = UnifiedBoxRegistry::new(); + // Builtins enabled only for wasm32, tests, or when feature "builtin-core" is set + #[cfg(any(test, target_arch = "wasm32", feature = "builtin-core"))] + { + registry.register(std::sync::Arc::new(BuiltinBoxFactory::new())); + } // Register plugin Box factory (primary) #[cfg(feature = "plugins")] diff --git a/src/tests/functionbox_call_tests.rs b/src/tests/functionbox_call_tests.rs new file mode 100644 index 00000000..d0748ca9 --- /dev/null +++ b/src/tests/functionbox_call_tests.rs @@ -0,0 +1,65 @@ +use crate::interpreter::NyashInterpreter; +use crate::ast::ASTNode; +use crate::box_trait::{NyashBox, IntegerBox}; + +#[test] +fn functionbox_call_via_variable_with_capture() { + let mut interp = NyashInterpreter::new(); + // local x = 10 + interp.declare_local_variable("x", Box::new(IntegerBox::new(10))); + + // f = function() { return x } + let lam = ASTNode::Lambda { params: vec![], body: vec![ + ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: "x".to_string(), span: crate::ast::Span::unknown() })), span: crate::ast::Span::unknown() } + ], span: crate::ast::Span::unknown() }; + let assign_f = ASTNode::Assignment { + target: Box::new(ASTNode::Variable { name: "f".to_string(), span: crate::ast::Span::unknown() }), + value: Box::new(lam.clone()), + span: crate::ast::Span::unknown(), + }; + let _ = interp.execute_statement(&assign_f).expect("assign f"); + + // x = 20 + let assign_x = ASTNode::Assignment { + target: Box::new(ASTNode::Variable { name: "x".to_string(), span: crate::ast::Span::unknown() }), + value: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::Integer(20), span: crate::ast::Span::unknown() }), + span: crate::ast::Span::unknown(), + }; + let _ = interp.execute_statement(&assign_x).expect("assign x"); + + // return f() + let call_f = ASTNode::Call { callee: Box::new(ASTNode::Variable { name: "f".to_string(), span: crate::ast::Span::unknown() }), arguments: vec![], span: crate::ast::Span::unknown() }; + let out = interp.execute_expression(&call_f).expect("call f"); + let ib = out.as_any().downcast_ref::().expect("integer ret"); + assert_eq!(ib.value, 20); +} + +#[test] +fn functionbox_call_via_field() { + let mut interp = NyashInterpreter::new(); + // obj with field f + let inst = crate::instance_v2::InstanceBox::from_declaration("C".to_string(), vec!["f".to_string()], std::collections::HashMap::new()); + interp.declare_local_variable("obj", Box::new(inst.clone())); + + // obj.f = function(a){ return a } + let lam = ASTNode::Lambda { params: vec!["a".to_string()], body: vec![ + ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: "a".to_string(), span: crate::ast::Span::unknown() })), span: crate::ast::Span::unknown() } + ], span: crate::ast::Span::unknown() }; + let assign = ASTNode::Assignment { + target: Box::new(ASTNode::FieldAccess { object: Box::new(ASTNode::Variable { name: "obj".to_string(), span: crate::ast::Span::unknown() }), field: "f".to_string(), span: crate::ast::Span::unknown() }), + value: Box::new(lam.clone()), + span: crate::ast::Span::unknown(), + }; + let _ = interp.execute_statement(&assign).expect("assign field"); + + // return (obj.f)(7) + let call = ASTNode::Call { + callee: Box::new(ASTNode::FieldAccess { object: Box::new(ASTNode::Variable { name: "obj".to_string(), span: crate::ast::Span::unknown() }), field: "f".to_string(), span: crate::ast::Span::unknown() }), + arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::Integer(7), span: crate::ast::Span::unknown() }], + span: crate::ast::Span::unknown(), + }; + let out = interp.execute_expression(&call).expect("call obj.f"); + let ib = out.as_any().downcast_ref::().expect("integer ret"); + assert_eq!(ib.value, 7); +} + diff --git a/src/tests/mir_lambda_functionbox.rs b/src/tests/mir_lambda_functionbox.rs new file mode 100644 index 00000000..1ddf2cbf --- /dev/null +++ b/src/tests/mir_lambda_functionbox.rs @@ -0,0 +1,25 @@ +use crate::parser::NyashParser; +use crate::mir::{MirCompiler}; +use crate::backend::VM; + +#[test] +fn lambda_value_then_call_returns_increment() { + // Nyash code: + // f = function(a) { return a + 1 } + // f(41) + let code = r#" + f = function(a) { + return a + 1 + } + f(41) + "#; + let ast = NyashParser::parse_from_string(code).expect("parse"); + let mut mc = MirCompiler::new(); + let cr = mc.compile(ast).expect("mir"); + // Execute on VM + let mut vm = VM::new(); + let out = vm.execute_module(&cr.module).expect("vm exec"); + if let crate::backend::vm::VMValue::Integer(i) = out { assert_eq!(i, 42); } + else { panic!("Expected Integer 42, got {:?}", out); } +} + diff --git a/src/tests/vm_functionbox_call.rs b/src/tests/vm_functionbox_call.rs new file mode 100644 index 00000000..d255c0de --- /dev/null +++ b/src/tests/vm_functionbox_call.rs @@ -0,0 +1,49 @@ +use crate::mir::{MirModule, MirFunction, FunctionSignature, MirInstruction, EffectMask, BasicBlockId, ConstValue}; +use crate::backend::vm::VM; +use crate::backend::vm::VMValue; +use crate::boxes::function_box::{FunctionBox, ClosureEnv}; +use crate::box_trait::NyashBox; + +#[test] +fn vm_call_functionbox_returns_42() { + // Build FunctionBox: function(a) { return a + 1 } + let params = vec!["a".to_string()]; + let body = vec![ + crate::ast::ASTNode::Return { + value: Some(Box::new(crate::ast::ASTNode::BinaryOp { + left: Box::new(crate::ast::ASTNode::Variable { name: "a".to_string(), span: crate::ast::Span::unknown() }), + operator: crate::ast::BinaryOperator::Add, + right: Box::new(crate::ast::ASTNode::Literal { value: crate::ast::LiteralValue::Integer(1), span: crate::ast::Span::unknown() }), + span: crate::ast::Span::unknown(), + })), + span: crate::ast::Span::unknown(), + } + ]; + let fun = FunctionBox::with_env(params, body, ClosureEnv::new()); + + // Build MIR: arg=41; res = call func_id(arg); return res + let sig = FunctionSignature { name: "main".into(), params: vec![], return_type: crate::mir::MirType::Integer, effects: EffectMask::PURE }; + let mut f = MirFunction::new(sig, BasicBlockId::new(0)); + let bb = f.entry_block; + // Reserve an id for function value (we'll inject VMValue::BoxRef later) + let func_id = f.next_value_id(); + // arg const + let arg = f.next_value_id(); + f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: arg, value: ConstValue::Integer(41) }); + // call + let res = f.next_value_id(); + f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Call { dst: Some(res), func: func_id, args: vec![arg], effects: EffectMask::PURE }); + f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Return { value: Some(res) }); + + let mut m = MirModule::new("vm_funbox".into()); + m.add_function(f.clone()); + + // Prepare VM and inject FunctionBox into func_id + let mut vm = VM::new(); + let arc_fun: std::sync::Arc = std::sync::Arc::from(Box::new(fun) as Box); + vm.set_value(func_id, VMValue::BoxRef(arc_fun)); + + let out = vm.execute_module(&m).expect("vm exec"); + assert_eq!(out.to_string_box().value, "42"); +} +