diff --git a/docs/development/roadmap/phases/phase-15/INDEX.md b/docs/development/roadmap/phases/phase-15/INDEX.md deleted file mode 100644 index 7525778e..00000000 --- a/docs/development/roadmap/phases/phase-15/INDEX.md +++ /dev/null @@ -1,29 +0,0 @@ -# Phase 15 — Self‑Hosting Doc Index - -このインデックスは Phase 15(セルフホスティング)の計画・実装ドキュメントへの入口を1箇所にまとめます。状況に応じて随時更新します(正本)。 - -## 要点(すぐ見る) -- 現在タスク(正本): ../../../../CURRENT_TASK.md -- 概要と目的: README.md -- 実行計画(常時更新のチェックリスト): ROADMAP.md -- 推奨シーケンス(手順書): recommended-sequence.txt -- 詳細計画(長文): self-hosting-plan.txt -- lld戦略(AOT/リンク統合): self-hosting-lld-strategy.md - -## 設計とインターフェース -- Cranelift AOT 設計: ../../../backend-cranelift-aot-design.md -- Boxインターフェース案(Cranelift): ../../../../interfaces/cranelift-aot-box.md -- LinkerBox 仕様案: ../../../../interfaces/linker-box.md - -## ツール・スモーク -- AOTスモーク雛形: tools/aot_smoke_cranelift.sh / .ps1 -- JITスモーク: tools/jit_smoke.sh -- ラウンドトリップ: tools/ny_roundtrip_smoke.sh -- using/namespace E2E: tools/using_e2e_smoke.sh - -## 運用メモ/引き継ぎ -- ハンドオフ: ../../handoff/phase-15-handoff.md - -注意: -- Phase 15関連の分散した文書は本インデックスから辿れるよう整理しています。新規文書を追加した場合は必ずここに追記してください。 - diff --git a/docs/development/roadmap/phases/phase-15/README.md b/docs/development/roadmap/phases/phase-15/README.md index 5d9d8309..39ae44c4 100644 --- a/docs/development/roadmap/phases/phase-15/README.md +++ b/docs/development/roadmap/phases/phase-15/README.md @@ -168,17 +168,27 @@ ny_mir_jit_entry(mir_bin) -> exit_code ny_free_buf(buffer) ``` -詳細は[**自己ホスティングlld戦略**](self-hosting-lld-strategy.md)を参照。 +詳細は[**自己ホスティングlld戦略**](implementation/lld-strategy.md)を参照。 ## 🔗 関連ドキュメント -- [🚀 自己ホスティングlld戦略](self-hosting-lld-strategy.md) **← NEW!** -- [🧱 箱積み上げ準備メモ](box-stacking-preparation.txt) **← NEW!** -- [セルフホスティング詳細計画](self-hosting-plan.txt) -- [技術的実装詳細](technical-details.md) +### 📂 実装関連(implementationフォルダ) +- [🚀 自己ホスティングlld戦略](implementation/lld-strategy.md) +- [🧱 箱積み上げ準備メモ](implementation/box-stacking.md) +- [🏗️ アーキテクチャ詳細](implementation/architecture.md) + +### 📅 計画関連(planningフォルダ) +- [📋 推奨実装順序](planning/sequence.md) +- [🔧 準備作業まとめ](planning/preparation.md) + +### 🔧 実行チェックリスト +- [ROADMAP.md](ROADMAP.md) - 進捗管理用チェックリスト + +### 📚 関連フェーズ - [Phase 10: Cranelift JIT](../phase-10/) - [Phase 12.5: 最適化戦略](../phase-12.5/) - [Phase 12.7: ANCP圧縮](../phase-12.7/) +- [Phase 15.1: AOT計画](phase-15.1/) ## 📅 実施時期 diff --git a/docs/development/roadmap/phases/phase-15/self-hosting-plan.txt b/docs/development/roadmap/phases/phase-15/archive/self-hosting-plan.txt similarity index 100% rename from docs/development/roadmap/phases/phase-15/self-hosting-plan.txt rename to docs/development/roadmap/phases/phase-15/archive/self-hosting-plan.txt diff --git a/docs/development/roadmap/phases/phase-15/box-stacking-preparation.txt b/docs/development/roadmap/phases/phase-15/box-stacking-preparation.txt deleted file mode 100644 index 201a71f3..00000000 --- a/docs/development/roadmap/phases/phase-15/box-stacking-preparation.txt +++ /dev/null @@ -1,149 +0,0 @@ -Phase 15 自己ホスティング準備メモ - 箱積み上げ戦略 -================================================= -Date: 2025-09-03 -Author: Claude + にゃ - -■ 基本理念:箱を下にして積み重ねる -===================================== - -フォールバック不要、Rust依存なし(EXEライブラリのみ) -一度置いた箱は動かさない -下の箱が安定なら、上も安定 -複雑さは敵、シンプルさは友 - -■ 基礎Box群の階層構造 -====================== - -第1層(最下層):これがないと何も始まらない -├── MemoryBox // メモリ管理 -├── StringBox // 文字列処理 -├── ArrayBox // 配列操作 -├── MapBox // 連想配列 -└── ErrorBox // エラー処理 - -第2層:言語処理の基礎 -├── TokenBox // 字句解析 -├── ASTBox // 構文木 -├── SymbolBox // シンボルテーブル -└── ScopeBox // スコープ管理 - -第3層:コンパイラコア -├── ParserBox // パーサー -├── MIRBox // 中間表現(13命令) -├── OptimizerBox // 最適化 -└── GeneratorBox // コード生成 - -第4層:実行系 -├── InterpreterBox // インタープリター -├── VMBox // 仮想マシン -├── JITBox // JITコンパイラ(Cranelift) -└── LinkerBox // リンカー(lld) - -■ 準備期間の研究項目 -==================== - -1. 依存関係マップ作成 - - 何が何に依存? - - 最小限の依存で最大の効果 - - 循環依存の排除 - -2. インターフェース設計 - - 全Boxの共通プロトコル - - 入力/出力の標準化 - - エラー処理の統一 - -3. ビルド順序の科学 - - Day 1: 基礎Box群 - - Day 2: 言語処理層 - - Day 3: コンパイラ層 - - 完成まで一直線 - -4. 最小ブートストラップセット - - 自分自身をコンパイルできる最小構成 - - 必須機能の特定 - - 段階的な機能追加 - -■ 箱の品質保証原則 -================== - -1. 単体で完結 - 他に依存しすぎない -2. 明確な責務 - 1つの箱は1つの仕事 -3. テスト可能 - 単独で動作確認 -4. 不変インターフェース - 一度決めたら変更なし - -■ 実装戦略 -========== - -1. Rust実装の機能分解(Box単位) -2. 各Boxの仕様書作成 -3. テストケースの移植計画 -4. Box間プロトコル設計 -5. 最小実装から始める -6. 増分的に機能追加 - -■ 成功の鍵 -========== - -- 準備こそすべて -- 急いで実装より、じっくり設計 -- 箱を積み重ねれば必ず頂上に到達 -- フォールバックなし、後戻りなし、前進あるのみ - -■ 革新的アイデア -================ - -1. BoxDNA設計書 - - 各Boxの入力/出力/依存を明文化 - - 遺伝子のように継承・組み合わせ - -2. 増分コンパイル対応 - - 変更部分だけ再コンパイル - - 開発効率の向上 - -3. 可視化ツール - - 進捗の見える化 - - 依存関係の図示 - -■ 数値目標 -========== - -- 80,000行 → 20,000行(75%削減) -- MIR13命令で全機能実現 -- ビルド時間:1分以内 -- テストカバレッジ:90%以上 - -■ コンテキスト圧縮対策 -====================== - -1. ANCP活用(Phase 12.7) - - 90%圧縮でAIコンテキスト節約 - - [ANCP仕様書](../../phase-12.7/ancp-specs/ANCP-Token-Specification-v1.md) - -2. モジュール分割開発 - - 各Boxを独立して開発 - - 統合は最後に実施 - -3. AI協調開発 - - Claude:実装担当 - - ChatGPT5:リファクタリング - - Gemini:レビュー - - Codex:デバッグ支援 - -■ 関連ドキュメント -================== - -- [自己ホスティングlld戦略](self-hosting-lld-strategy.md) -- [Phase 15 README](README.md) -- [Phase 12.7 ANCP](../../phase-12.7/) -- [MIR13命令セット](../../../../reference/mir/INSTRUCTION_SET.md) - -■ 次のアクション -================ - -1. このメモをベースに詳細設計 -2. Box依存関係グラフの作成 -3. 最小実装セットの確定 -4. 各Boxの仕様書ドラフト - -===================================== -「箱を積み上げて、世界一美しいコンパイラを作るにゃ!」 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-15/technical-details.md b/docs/development/roadmap/phases/phase-15/implementation/architecture.md similarity index 100% rename from docs/development/roadmap/phases/phase-15/technical-details.md rename to docs/development/roadmap/phases/phase-15/implementation/architecture.md diff --git a/docs/development/roadmap/phases/phase-15/implementation/box-stacking.md b/docs/development/roadmap/phases/phase-15/implementation/box-stacking.md new file mode 100644 index 00000000..aefd24e1 --- /dev/null +++ b/docs/development/roadmap/phases/phase-15/implementation/box-stacking.md @@ -0,0 +1,139 @@ +# Phase 15 自己ホスティング準備メモ - 箱積み上げ戦略 + +Date: 2025-09-03 +Author: Claude + にゃ + +## 基本理念:箱を下にして積み重ねる + +- フォールバック不要、Rust依存なし(EXEライブラリのみ) +- 一度置いた箱は動かさない +- 下の箱が安定なら、上も安定 +- 複雑さは敵、シンプルさは友 + +## 基礎Box群の階層構造 + +### 第1層(最下層):これがないと何も始まらない +- **MemoryBox**: メモリ管理 +- **StringBox**: 文字列処理 +- **ArrayBox**: 配列操作 +- **MapBox**: 連想配列 +- **ErrorBox**: エラー処理 + +### 第2層:言語処理の基礎 +- **TokenBox**: 字句解析 +- **ASTBox**: 構文木 +- **SymbolBox**: シンボルテーブル +- **ScopeBox**: スコープ管理 + +### 第3層:コンパイラコア +- **ParserBox**: パーサー +- **MIRBox**: 中間表現(13命令) +- **OptimizerBox**: 最適化 +- **GeneratorBox**: コード生成 + +### 第4層:実行系 +- **InterpreterBox**: インタープリター +- **VMBox**: 仮想マシン +- **JITBox**: JITコンパイラ(Cranelift) +- **LinkerBox**: リンカー(lld) + +## 準備期間の研究項目 + +### 1. 依存関係マップ作成 +- 何が何に依存? +- 最小限の依存で最大の効果 +- 循環依存の排除 + +### 2. インターフェース設計 +- 全Boxの共通プロトコル +- 入力/出力の標準化 +- エラー処理の統一 + +### 3. ビルド順序の科学 +- Day 1: 基礎Box群 +- Day 2: 言語処理層 +- Day 3: コンパイラ層 +- 完成まで一直線 + +### 4. 最小ブートストラップセット +- 自分自身をコンパイルできる最小構成 +- 必須機能の特定 +- 段階的な機能追加 + +## 箱の品質保証原則 + +1. **単体で完結** - 他に依存しすぎない +2. **明確な責務** - 1つの箱は1つの仕事 +3. **テスト可能** - 単独で動作確認 +4. **不変インターフェース** - 一度決めたら変更なし + +## 実装戦略 + +1. Rust実装の機能分解(Box単位) +2. 各Boxの仕様書作成 +3. テストケースの移植計画 +4. Box間プロトコル設計 +5. 最小実装から始める +6. 増分的に機能追加 + +## 成功の鍵 + +- 準備こそすべて +- 急いで実装より、じっくり設計 +- 箱を積み重ねれば必ず頂上に到達 +- フォールバックなし、後戻りなし、前進あるのみ + +## 革新的アイデア + +### 1. BoxDNA設計書 +- 各Boxの入力/出力/依存を明文化 +- 遺伝子のように継承・組み合わせ + +### 2. 増分コンパイル対応 +- 変更部分だけ再コンパイル +- 開発効率の向上 + +### 3. 可視化ツール +- 進捗の見える化 +- 依存関係の図示 + +## 数値目標 + +- **80,000行 → 20,000行**(75%削減) +- **MIR13命令**で全機能実現 +- **ビルド時間**:1分以内 +- **テストカバレッジ**:90%以上 + +## コンテキスト圧縮対策 + +### 1. ANCP活用(Phase 12.7) +- 90%圧縮でAIコンテキスト節約 +- [ANCP仕様書](../../../phase-12.7/ancp-specs/ANCP-Token-Specification-v1.md) + +### 2. モジュール分割開発 +- 各Boxを独立して開発 +- 統合は最後に実施 + +### 3. AI協調開発 +- Claude:実装担当 +- ChatGPT5:リファクタリング +- Gemini:レビュー +- Codex:デバッグ支援 + +## 関連ドキュメント + +- [自己ホスティングlld戦略](./lld-strategy.md) +- [Phase 15 README](../README.md) +- [Phase 12.7 ANCP](../../../phase-12.7/) +- [MIR13命令セット](../../../../../reference/mir/INSTRUCTION_SET.md) + +## 次のアクション + +1. このメモをベースに詳細設計 +2. Box依存関係グラフの作成 +3. 最小実装セットの確定 +4. 各Boxの仕様書ドラフト + +--- + +「箱を積み上げて、世界一美しいコンパイラを作るにゃ!」 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-15/self-hosting-lld-strategy.md b/docs/development/roadmap/phases/phase-15/implementation/lld-strategy.md similarity index 100% rename from docs/development/roadmap/phases/phase-15/self-hosting-lld-strategy.md rename to docs/development/roadmap/phases/phase-15/implementation/lld-strategy.md diff --git a/docs/development/roadmap/phases/phase-15/preparation-summary.md b/docs/development/roadmap/phases/phase-15/planning/preparation.md similarity index 100% rename from docs/development/roadmap/phases/phase-15/preparation-summary.md rename to docs/development/roadmap/phases/phase-15/planning/preparation.md diff --git a/docs/development/roadmap/phases/phase-15/planning/sequence.md b/docs/development/roadmap/phases/phase-15/planning/sequence.md new file mode 100644 index 00000000..8526f5e2 --- /dev/null +++ b/docs/development/roadmap/phases/phase-15/planning/sequence.md @@ -0,0 +1,148 @@ +# Phase 15 推奨進行順(JIT優先・自己ホスティング最小) + +更新日: 2025-09-05 + +## 方針(原則) + +- JITオンリー(Cranelift)で前進。LLVM/AOT・lld系は後段にスライド。 +- 最小自己ホスト体験を早期に成立 → ドキュメント/スモーク/CIを先に固める。 +- using(名前空間)はゲート付きで段階導入。NyModulesとny_pluginsの基盤を強化。 +- tmux + codex-async を使い、常時2本並走で小粒に積み上げる。 + +## 推奨シーケンス(概要→実施要点→完了基準) + +### 1) 基盤整備(NyModules / ny_plugins / Windows正規化) + +**要点:** +- NyModules 共有レジストリ導入: env.modules.set/get(または ModulesBox) +- ny_plugins のパス→名前空間導出: ルート相対、"/"→".", 拡張子 .nyash 省略、[^a-zA-Z0-9_.]→"_" +- Windowsパス: "\\"→"/" 正規化後に上記規則を適用 +- 予約衝突: nyashstd.* の登録を明示拒否しログ出力 + +**スモーク/CI:** +- tools/modules_smoke.sh, tools/modules_winpath_smoke.sh + +**完了基準:** +- env.modules.get("acme.logger") などが取得可能、LIST_ONLY/Fail-continue維持、予約拒否ログが出る。 + +### 2) 最小コンパイラ経路(JIT) + +**要点:** +- パーサ/レクサのサブセット: ident/literals/let/call/return/if/block +- Nyash から呼べる MIR ビルダ(小さなサブセット) +- VM/JIT ブリッジを通して apps/selfhost-minimal が走る + +**スモーク/CI:** +- tools/jit_smoke.sh, tools/selfhost_vm_smoke.sh + +**完了基準:** +- ./target/release/nyash --backend vm apps/selfhost-minimal/main.nyash が安定実行し、CIでJITスモーク合格。 + +### 3) using(ゲート付き)設計・実装(15.2/15.3) + +**要点:** +- パーサフック: 'using ' を受理(--enable-using / NYASH_ENABLE_USING=1) +- リゾルバskeleton: resolve(ns) → NyModules を優先。外部/パッケージは TODO として設計のみ。 +- 実行時フック: 未解決時に提案を含む診断。セッションキャッシュを導入(ny_plugins再読込で無効化)。 +- using alias: 'using a.b as x' を設計→段階導入。 + +**スモーク/CI:** +- jit_smoke に using ケースとキャッシュケースを追加。 + +**完了基準:** +- フラグONで using 経路が動作し、未解決時の診断・キャッシュ挙動がテストで担保。 + +### 4) nyash.link ミニマルリゾルバ(15.4) + +**要点:** +- ファイル/相対解決 → 名前空間への写像、検索パス(nyash.toml と環境)、Windows正規化 +- 未解決時は候補提示、NyModules へのフォールバック +- using alias + 診断を仕上げる + +**スモーク/CI:** +- end-to-end 例(apps/)とJITスモークの拡充 + +**完了基準:** +- 小規模プロジェクトで using + nyash.link の基本導線がJITでE2E通る。 + +### 5) パフォーマンス守り(MIRマイクロ最適化 + 回帰ゲート) + +**要点:** +- const-fold(リテラル・単純四則)、DCE(unreachable return/blocks)をスコープ限定で有効化 +- 回帰時は NYASH_CLI_VERBOSE=1 で診断を落とす + +**スモーク/CI:** +- jit_smoke に閾値付きケースを追加、CI optional stage で監視 + +**完了基準:** +- 主要ケースで回帰検出が機能し、JITパリティが維持される。 + +### 6) Boxes 高レベル移植(15.5 開始) + +**要点:** +- StringBox → ArrayBox の順で表層メソッドをNyashへ移植(NyRTは最小プリミティブ維持) +- MapBox は次段で検討。ABI/churnを避けるため段階導入 + +**スモーク/CI:** +- 文字列/配列操作のJITスモークを追加 + +**完了基準:** +- 代表的な文字列/配列APIがNyash実装で安定動作し、CI常時緑。 + +### 7) インタープリターコアの段階移植(15.5/15.6) + +**要点:** +- MIR実行ループを段階的にNyash化(動的ディスパッチで13命令処理) +- ブートストラップ: c0(Rust) → c1(Nyash) → c1'(自己再コンパイル) + +**検証:** +- パリティテスト(trace_hash 等)とスモークを追加 + +**完了基準:** +- 自己再コンパイルループが成立し、差分トレースが安定。 + +### 8) YAML 自動生成(15.1 を後段にスライドして導入) + +**要点:** +- boxes.yaml / externs.yaml / semantics.yaml を定義し、build.rs でコード自動生成 +- まず externs/boxes の一部から段階導入 → 重複削減を早期に回収 + +**完了基準:** +- 重複コードが実測で大幅削減(1〜2万行級)、CI・ドキュメントに反映。 + +### 9) クローズアウト(各小節の都度) + +- README.ja.md / AGENTS.md / docs のHOWTO・旗一覧・スモーク手順を常に最新化 +- ツール類索引: tools/jit_smoke.sh, selfhost_vm_smoke.sh, modules_smoke.sh, modules_winpath_smoke.sh +- CIトグル整備: LLVM系は無効化、JIT(--features cranelift-jit)を標準経路に + +## クイックコマンド(JITオンリー) + +```bash +# ビルド +cargo build --release --features cranelift-jit + +# 実行 +./target/release/nyash --backend vm apps/selfhost-minimal/main.nyash + +# スモーク +./tools/jit_smoke.sh +./tools/selfhost_vm_smoke.sh +./tools/modules_smoke.sh ; ./tools/modules_winpath_smoke.sh +``` + +## フラグ(抜粋) + +- `--load-ny-plugins` / `NYASH_LOAD_NY_PLUGINS=1` +- `--enable-using` / `NYASH_ENABLE_USING=1` +- `NYASH_CLI_VERBOSE=1`(診断強化) + +## 運用(Codex async / tmux) + +- 2並走・重複回避: `CODEX_MAX_CONCURRENT=2 CODEX_DEDUP=1 CODEX_ASYNC_DETACH=1` +- 監視: `pgrep -af 'codex .* exec'` / `tail -f ~/.codex-async-work/logs/codex-*.log` +- Windowsパス/名前空間: "\\"→"/" 正規化 → ルール適用(/→., .nyash除去, sanitize) + +## 備考 + +本シーケンスは `docs/development/roadmap/phases/phase-15/self-hosting-plan.txt` を尊重しつつ、JIT最小体験を優先させるため順序を最適化(LLVM/lld と YAML自動生成は後段へスライド)。進捗に応じて適宜見直し、CI/スモークで常時検証する。 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-15/recommended-sequence.txt b/docs/development/roadmap/phases/phase-15/recommended-sequence.txt deleted file mode 100644 index 3950c6cb..00000000 --- a/docs/development/roadmap/phases/phase-15/recommended-sequence.txt +++ /dev/null @@ -1,123 +0,0 @@ -=============================================================================== -Phase 15 推奨進行順(JIT優先・自己ホスティング最小) -更新日: 2025-09-05 -=============================================================================== - -方針(原則) -- JITオンリー(Cranelift)で前進。LLVM/AOT・lld系は後段にスライド。 -- 最小自己ホスト体験を早期に成立 → ドキュメント/スモーク/CIを先に固める。 -- using(名前空間)はゲート付きで段階導入。NyModulesとny_pluginsの基盤を強化。 -- tmux + codex-async を使い、常時2本並走で小粒に積み上げる。 - -=============================================================================== -推奨シーケンス(概要→実施要点→完了基準) -=============================================================================== - -1) 基盤整備(NyModules / ny_plugins / Windows正規化) -- 要点: - - NyModules 共有レジストリ導入: env.modules.set/get(または ModulesBox) - - ny_plugins のパス→名前空間導出: ルート相対、"/"→".", 拡張子 .nyash 省略、[^a-zA-Z0-9_.]→"_" - - Windowsパス: "\\"→"/" 正規化後に上記規則を適用 - - 予約衝突: nyashstd.* の登録を明示拒否しログ出力 -- スモーク/CI: - - tools/modules_smoke.sh, tools/modules_winpath_smoke.sh -- 完了基準: - - env.modules.get("acme.logger") などが取得可能、LIST_ONLY/Fail-continue維持、予約拒否ログが出る。 - -2) 最小コンパイラ経路(JIT) -- 要点: - - パーサ/レクサのサブセット: ident/literals/let/call/return/if/block - - Nyash から呼べる MIR ビルダ(小さなサブセット) - - VM/JIT ブリッジを通して apps/selfhost-minimal が走る -- スモーク/CI: - - tools/jit_smoke.sh, tools/selfhost_vm_smoke.sh -- 完了基準: - - ./target/release/nyash --backend vm apps/selfhost-minimal/main.nyash が安定実行し、CIでJITスモーク合格。 - -3) using(ゲート付き)設計・実装(15.2/15.3) -- 要点: - - パーサフック: 'using ' を受理(--enable-using / NYASH_ENABLE_USING=1) - - リゾルバskeleton: resolve(ns) → NyModules を優先。外部/パッケージは TODO として設計のみ。 - - 実行時フック: 未解決時に提案を含む診断。セッションキャッシュを導入(ny_plugins再読込で無効化)。 - - using alias: 'using a.b as x' を設計→段階導入。 -- スモーク/CI: - - jit_smoke に using ケースとキャッシュケースを追加。 -- 完了基準: - - フラグONで using 経路が動作し、未解決時の診断・キャッシュ挙動がテストで担保。 - -4) nyash.link ミニマルリゾルバ(15.4) -- 要点: - - ファイル/相対解決 → 名前空間への写像、検索パス(nyash.toml と環境)、Windows正規化 - - 未解決時は候補提示、NyModules へのフォールバック - - using alias + 診断を仕上げる -- スモーク/CI: - - end-to-end 例(apps/)とJITスモークの拡充 -- 完了基準: - - 小規模プロジェクトで using + nyash.link の基本導線がJITでE2E通る。 - -5) パフォーマンス守り(MIRマイクロ最適化 + 回帰ゲート) -- 要点: - - const-fold(リテラル・単純四則)、DCE(unreachable return/blocks)をスコープ限定で有効化 - - 回帰時は NYASH_CLI_VERBOSE=1 で診断を落とす -- スモーク/CI: - - jit_smoke に閾値付きケースを追加、CI optional stage で監視 -- 完了基準: - - 主要ケースで回帰検出が機能し、JITパリティが維持される。 - -6) Boxes 高レベル移植(15.5 開始) -- 要点: - - StringBox → ArrayBox の順で表層メソッドをNyashへ移植(NyRTは最小プリミティブ維持) - - MapBox は次段で検討。ABI/churnを避けるため段階導入 -- スモーク/CI: - - 文字列/配列操作のJITスモークを追加 -- 完了基準: - - 代表的な文字列/配列APIがNyash実装で安定動作し、CI常時緑。 - -7) インタープリターコアの段階移植(15.5/15.6) -- 要点: - - MIR実行ループを段階的にNyash化(動的ディスパッチで13命令処理) - - ブートストラップ: c0(Rust) → c1(Nyash) → c1'(自己再コンパイル) -- 検証: - - パリティテスト(trace_hash 等)とスモークを追加 -- 完了基準: - - 自己再コンパイルループが成立し、差分トレースが安定。 - -8) YAML 自動生成(15.1 を後段にスライドして導入) -- 要点: - - boxes.yaml / externs.yaml / semantics.yaml を定義し、build.rs でコード自動生成 - - まず externs/boxes の一部から段階導入 → 重複削減を早期に回収 -- 完了基準: - - 重複コードが実測で大幅削減(1〜2万行級)、CI・ドキュメントに反映。 - -9) クローズアウト(各小節の都度) -- README.ja.md / AGENTS.md / docs のHOWTO・旗一覧・スモーク手順を常に最新化 -- ツール類索引: tools/jit_smoke.sh, selfhost_vm_smoke.sh, modules_smoke.sh, modules_winpath_smoke.sh -- CIトグル整備: LLVM系は無効化、JIT(--features cranelift-jit)を標準経路に - -=============================================================================== -クイックコマンド(JITオンリー) -=============================================================================== -- ビルド: cargo build --release --features cranelift-jit -- 実行: ./target/release/nyash --backend vm apps/selfhost-minimal/main.nyash -- スモーク: - - tools/jit_smoke.sh - - tools/selfhost_vm_smoke.sh - - tools/modules_smoke.sh ; tools/modules_winpath_smoke.sh - -フラグ(抜粋) -- --load-ny-plugins / NYASH_LOAD_NY_PLUGINS=1 -- --enable-using / NYASH_ENABLE_USING=1 -- NYASH_CLI_VERBOSE=1(診断強化) - -=============================================================================== -運用(Codex async / tmux) -=============================================================================== -- 2並走・重複回避: CODEX_MAX_CONCURRENT=2 CODEX_DEDUP=1 CODEX_ASYNC_DETACH=1 -- 監視: pgrep -af 'codex .* exec' / tail -f ~/.codex-async-work/logs/codex-*.log -- Windowsパス/名前空間: "\\"→"/" 正規化 → ルール適用(/→., .nyash除去, sanitize) - -備考 -- 本シーケンスは docs/development/roadmap/phases/phase-15/self-hosting-plan.txt を尊重しつつ、 - JIT最小体験を優先させるため順序を最適化(LLVM/lld と YAML自動生成は後段へスライド)。 - 進捗に応じて適宜見直し、CI/スモークで常時検証する。 - diff --git a/docs/development/roadmap/phases/phase-21/README.md b/docs/development/roadmap/phases/phase-21/README.md new file mode 100644 index 00000000..af5f5292 --- /dev/null +++ b/docs/development/roadmap/phases/phase-21/README.md @@ -0,0 +1,393 @@ +# Phase 21: データベース駆動開発(DDD: Database-Driven Development) + +## 📋 概要 + +ソースコードをファイルではなくデータベースで管理する革命的開発パラダイム。 +Box、メソッド、名前空間を構造化データとして扱い、リファクタリングを瞬時に完了させる。 +**「ファイルは1970年代の遺物。21世紀のコードは構造化データベースに住む」** + +## 🎯 背景と動機 + +### 現状の問題 +- **ファイルベース**:物理的な区切り(人間の都合) +- **Box/メソッド**:論理的な単位(プログラムの本質) +- **不一致の結果**:リファクタリングが遅い、検索が非効率、依存関係が不透明 + +### 解決策 +- コードをSQLiteデータベースで管理 +- Box、メソッド、依存関係を正規化されたテーブルで表現 +- SQLクエリでリファクタリング・検索・分析を高速化 + +## 🏗️ データベーススキーマ + +### 基本テーブル構造 + +```sql +-- Boxの定義 +CREATE TABLE boxes ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + namespace TEXT, + parent_box_id INTEGER, + box_type TEXT CHECK(box_type IN ('normal', 'static', 'abstract')), + source_code TEXT, + metadata JSON, -- 型情報、アノテーション、ドキュメント + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (parent_box_id) REFERENCES boxes(id) +); + +-- メソッド定義 +CREATE TABLE methods ( + id INTEGER PRIMARY KEY, + box_id INTEGER NOT NULL, + name TEXT NOT NULL, + visibility TEXT CHECK(visibility IN ('public', 'private', 'protected')), + params JSON, -- パラメータ情報 + return_type JSON, + body TEXT, + mir_cache BLOB, -- コンパイル済みMIRをキャッシュ + optimization_hints JSON, + FOREIGN KEY (box_id) REFERENCES boxes(id), + UNIQUE(box_id, name) -- 同一Box内でメソッド名は一意 +); + +-- フィールド定義 +CREATE TABLE fields ( + id INTEGER PRIMARY KEY, + box_id INTEGER NOT NULL, + name TEXT NOT NULL, + field_type JSON, + default_value TEXT, + metadata JSON, + FOREIGN KEY (box_id) REFERENCES boxes(id) +); + +-- 依存関係 +CREATE TABLE dependencies ( + from_type TEXT CHECK(from_type IN ('box', 'method')), + from_id INTEGER, + to_type TEXT CHECK(to_type IN ('box', 'method')), + to_id INTEGER, + dep_type TEXT CHECK(dep_type IN ('uses', 'extends', 'calls', 'implements')), + metadata JSON, -- 呼び出し位置、使用頻度など + PRIMARY KEY (from_type, from_id, to_type, to_id, dep_type) +); + +-- 名前空間 +CREATE TABLE namespaces ( + id INTEGER PRIMARY KEY, + path TEXT UNIQUE NOT NULL, + parent_id INTEGER, + metadata JSON, + FOREIGN KEY (parent_id) REFERENCES namespaces(id) +); + +-- コンパイルキャッシュ +CREATE TABLE compile_cache ( + id INTEGER PRIMARY KEY, + entity_type TEXT, + entity_id INTEGER, + mir_version INTEGER, + mir_data BLOB, + metadata JSON, -- 最適化レベル、ターゲットなど + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 全文検索用インデックス +CREATE VIRTUAL TABLE code_search USING fts5( + entity_type, + entity_id, + name, + content, + tokenize = 'porter' +); +``` + +## 🚀 革命的な機能 + +### 1. 瞬間リファクタリング + +```sql +-- 名前変更:トランザクション一発 +BEGIN TRANSACTION; +UPDATE boxes SET name = 'NewBoxName' WHERE name = 'OldBoxName'; +UPDATE code_search SET name = 'NewBoxName' + WHERE entity_type = 'box' AND name = 'OldBoxName'; +-- 依存コードも自動更新(トリガーで実装) +COMMIT; + +-- メソッド移動:Box間でメソッドを移動 +UPDATE methods SET box_id = (SELECT id FROM boxes WHERE name = 'TargetBox') + WHERE id = ? AND box_id = ?; +``` + +### 2. 高度な検索・分析 + +```sql +-- 未使用コード検出 +SELECT b.namespace || '.' || b.name AS unused_box +FROM boxes b +LEFT JOIN dependencies d ON + (d.to_type = 'box' AND d.to_id = b.id) +WHERE d.from_id IS NULL; + +-- 循環依存検出(再帰CTE) +WITH RECURSIVE dep_path AS ( + SELECT from_id, to_id, + from_id || '->' || to_id as path + FROM dependencies + WHERE from_type = 'box' AND to_type = 'box' + UNION ALL + SELECT d.from_id, dp.to_id, + dp.path || '->' || d.to_id + FROM dependencies d + JOIN dep_path dp ON d.to_id = dp.from_id + WHERE d.from_type = 'box' AND d.to_type = 'box' + AND dp.path NOT LIKE '%' || d.to_id || '%' +) +SELECT path FROM dep_path WHERE from_id = to_id; + +-- 類似コード検出(全文検索) +SELECT b1.name AS box1, m1.name AS method1, + b2.name AS box2, m2.name AS method2, + similarity_score(m1.body, m2.body) AS similarity +FROM methods m1 +JOIN methods m2 ON m1.id < m2.id +JOIN boxes b1 ON m1.box_id = b1.id +JOIN boxes b2 ON m2.box_id = b2.id +WHERE similarity_score(m1.body, m2.body) > 0.8; +``` + +### 3. インテリジェントなキャッシング + +```sql +-- 変更影響分析 +CREATE TRIGGER invalidate_cache_on_method_update +AFTER UPDATE ON methods +BEGIN + -- 直接依存するエンティティのキャッシュを無効化 + DELETE FROM compile_cache + WHERE entity_id IN ( + SELECT from_id FROM dependencies + WHERE to_type = 'method' AND to_id = NEW.id + ); +END; +``` + +### 4. バージョン管理の統合 + +```sql +-- 変更履歴 +CREATE TABLE history ( + id INTEGER PRIMARY KEY, + entity_type TEXT, + entity_id INTEGER, + version INTEGER, + change_type TEXT, + old_value TEXT, + new_value TEXT, + changed_by TEXT, + changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + commit_message TEXT +); + +-- Git風のブランチ管理 +CREATE TABLE branches ( + id INTEGER PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + base_version INTEGER, + is_active BOOLEAN DEFAULT TRUE +); +``` + +## 🎨 実装例 + +```nyash +box CodeDB { + db: SQLiteBox + cache: MapBox + + birth(dbPath) { + me.db = new SQLiteBox(dbPath) + me.cache = new MapBox() + me.initSchema() + } + + // Boxを保存 + saveBox(box) { + local tx = me.db.beginTransaction() + try { + local boxId = me.db.insert("boxes", { + name: box.name, + namespace: box.namespace, + source_code: box.toString(), + metadata: box.getMetadata() + }) + + // メソッドも保存 + for method in box.methods { + me.saveMethod(boxId, method) + } + + tx.commit() + } catch (e) { + tx.rollback() + throw e + } + } + + // リファクタリング:名前変更 + renameBox(oldName, newName) { + me.db.execute( + "UPDATE boxes SET name = ? WHERE name = ?", + [newName, oldName] + ) + + // 全文検索インデックスも更新 + me.updateSearchIndex() + + // キャッシュ無効化 + me.invalidateCache(oldName) + } + + // 未使用コード検出 + findUnusedCode() { + return me.db.query(" + SELECT b.namespace || '.' || b.name AS unused + FROM boxes b + LEFT JOIN dependencies d ON d.to_id = b.id + WHERE d.from_id IS NULL + ") + } + + // AI連携:類似コード提案 + suggestRefactoring(methodId) { + local similar = me.findSimilarMethods(methodId) + if similar.length() > 3 { + return { + suggestion: "共通Boxに抽出", + methods: similar + } + } + } +} +``` + +## 🔧 開発ツール + +### 1. CLI拡張 + +```bash +# DBクエリ実行 +nyash db query "SELECT * FROM boxes WHERE name LIKE '%Handler'" + +# リファクタリング +nyash db refactor rename-box OldName NewName + +# 依存関係グラフ生成 +nyash db deps --format=dot | dot -Tpng -o deps.png + +# 未使用コード削除 +nyash db clean --remove-unused --dry-run +``` + +### 2. VSCode拡張 + +- **DBエクスプローラー**:Box/メソッドをツリー表示 +- **リアルタイム検索**:SQLクエリで即座に検索 +- **依存関係ビュー**:グラフィカルに表示 +- **リファクタリングパレット**:右クリックで瞬間実行 + +### 3. Web UI + +```nyash +box CodeDBWebUI { + server: WebServerBox + db: CodeDB + + birth(dbPath, port) { + me.db = new CodeDB(dbPath) + me.server = new WebServerBox(port) + me.setupRoutes() + } + + setupRoutes() { + // コードグラフ表示 + me.server.get("/graph") { req, res -> + local deps = me.db.getAllDependencies() + res.json(me.buildD3Graph(deps)) + } + + // リアルタイムSQL実行 + me.server.post("/query") { req, res -> + local result = me.db.query(req.body.sql) + res.json(result) + } + } +} +``` + +## 📊 移行戦略 + +### Phase 1: ハイブリッドモード(3ヶ月) +- 既存ファイル→DB同期ツール開発 +- DB→ファイルエクスポート(Git互換性維持) +- 開発者が徐々に慣れる期間 + +### Phase 2: DB優先モード(3ヶ月) +- 新規開発はDB直接 +- ファイルは自動生成 +- リファクタリング効率を体感 + +### Phase 3: 完全DB化(3ヶ月) +- ファイルシステムは配布用のみ +- 開発は100% DB駆動 +- 新しい開発パラダイムの確立 + +## 🌟 期待される効果 + +### 開発効率 +- **リファクタリング**: 100倍高速化(秒単位→ミリ秒単位) +- **検索**: SQLによる高度な検索(正規表現、構造検索) +- **分析**: 依存関係、複雑度、類似性を瞬時に把握 + +### コード品質 +- **重複排除**: 類似コードを自動検出 +- **整合性**: DB制約で不整合を防止 +- **追跡可能性**: すべての変更を記録 + +### AI連携 +- **構造化データ**: AIが理解しやすい +- **メタデータ**: 型情報、使用頻度など豊富 +- **学習効率**: コードパターンを効率的に学習 + +## 🚀 革新性 + +### 世界初の要素 +1. **完全DB駆動言語**: ファイルシステムからの解放 +2. **構造認識エディタ**: Box/メソッド単位の編集 +3. **瞬間リファクタリング**: SQLトランザクションで完結 +4. **依存関係DB**: コンパイル時情報も含む + +### 技術的優位性 +- **SQLite**: 軽量、高速、信頼性 +- **Everything is Box**: DB表現と相性抜群 +- **MIRキャッシュ**: コンパイル高速化 + +## 📅 実施時期 + +- **開始条件**: Phase 15(セルフホスティング)完了後 +- **推定期間**: 9ヶ月 +- **優先度**: 高(開発効率の革命的向上) + +## 🔗 関連フェーズ + +- [Phase 15: セルフホスティング](../phase-15/) - 基盤技術 +- [Phase 12: 統一実行パス](../phase-12/) - MIRキャッシュ活用 +- [Phase 16: プラグインエコシステム](../phase-16/) - DB APIの公開 + +--- + +> 「コードはファイルに書くもの」という固定観念を打ち破る。 +> 21世紀の開発は、構造化データベースで行うべきだにゃ! \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-21/technical-considerations.md b/docs/development/roadmap/phases/phase-21/technical-considerations.md new file mode 100644 index 00000000..12e56048 --- /dev/null +++ b/docs/development/roadmap/phases/phase-21/technical-considerations.md @@ -0,0 +1,409 @@ +# Phase 21: 技術的考慮事項 + +## 🏗️ アーキテクチャ設計 + +### レイヤー構造 + +``` +┌─────────────────────────────┐ +│ 開発ツール層 │ (VSCode, CLI, Web UI) +├─────────────────────────────┤ +│ API層 │ (GraphQL/REST) +├─────────────────────────────┤ +│ CodeDB抽象層 │ (統一インターフェース) +├─────────────────────────────┤ +│ SQLite実装層 │ (具体的なDB操作) +├─────────────────────────────┤ +│ ストレージ層 │ (ローカル/リモート) +└─────────────────────────────┘ +``` + +## 🔐 セキュリティ考慮事項 + +### アクセス制御 +```sql +-- ユーザー権限管理 +CREATE TABLE permissions ( + user_id INTEGER, + resource_type TEXT, + resource_id INTEGER, + permission TEXT CHECK(permission IN ('read', 'write', 'admin')), + granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + granted_by INTEGER, + PRIMARY KEY (user_id, resource_type, resource_id, permission) +); + +-- 監査ログ +CREATE TABLE audit_log ( + id INTEGER PRIMARY KEY, + user_id INTEGER, + action TEXT, + resource_type TEXT, + resource_id INTEGER, + old_value TEXT, + new_value TEXT, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + ip_address TEXT, + session_id TEXT +); +``` + +### SQLインジェクション対策 +```nyash +box SecureCodeDB from CodeDB { + // パラメータ化クエリを強制 + query(sql, params) { + // SQLをパースして危険な構文をチェック + local ast = me.parseSql(sql) + if me.hasDangerousPattern(ast) { + throw new SecurityError("Dangerous SQL pattern detected") + } + + return from CodeDB.query(sql, params) + } + + // ホワイトリスト方式のテーブル名検証 + validateTableName(name) { + if not name.matches("^[a-z_]+$") { + throw new SecurityError("Invalid table name") + } + } +} +``` + +## 🚀 パフォーマンス最適化 + +### インデックス戦略 +```sql +-- 頻繁なクエリ用インデックス +CREATE INDEX idx_boxes_namespace ON boxes(namespace); +CREATE INDEX idx_methods_box_id ON methods(box_id); +CREATE INDEX idx_deps_from ON dependencies(from_type, from_id); +CREATE INDEX idx_deps_to ON dependencies(to_type, to_id); + +-- 複合インデックス +CREATE INDEX idx_box_namespace_name ON boxes(namespace, name); +CREATE INDEX idx_method_box_name ON methods(box_id, name); + +-- 部分インデックス(アクティブなものだけ) +CREATE INDEX idx_active_boxes ON boxes(name) + WHERE deleted_at IS NULL; +``` + +### クエリ最適化 +```nyash +box QueryOptimizer { + cache: MapBox + + // クエリ結果のキャッシング + cachedQuery(sql, params, ttl) { + local key = me.hash(sql + params.toString()) + + if me.cache.has(key) { + local cached = me.cache.get(key) + if cached.timestamp + ttl > now() { + return cached.result + } + } + + local result = me.db.query(sql, params) + me.cache.set(key, { + result: result, + timestamp: now() + }) + + return result + } +} +``` + +## 🔄 同期・レプリケーション + +### マルチデバイス同期 +```nyash +box CodeDBSync { + local: CodeDB + remote: RemoteCodeDB + + // 変更を追跡 + trackChanges() { + CREATE TRIGGER track_box_changes + AFTER INSERT OR UPDATE OR DELETE ON boxes + BEGIN + INSERT INTO sync_queue ( + table_name, operation, entity_id, data + ) VALUES ( + 'boxes', + CASE + WHEN OLD.id IS NULL THEN 'INSERT' + WHEN NEW.id IS NULL THEN 'DELETE' + ELSE 'UPDATE' + END, + COALESCE(NEW.id, OLD.id), + json_object('old', OLD, 'new', NEW) + ); + END; + } + + // 差分同期 + sync() { + local changes = me.local.query(" + SELECT * FROM sync_queue + WHERE synced_at IS NULL + ORDER BY created_at + ") + + for change in changes { + me.remote.applyChange(change) + me.local.markSynced(change.id) + } + } +} +``` + +## 🎯 互換性戦略 + +### ファイルシステムとの相互変換 +```nyash +box FileDBBridge { + // DB→ファイル エクスポート + exportToFiles(outputDir) { + local boxes = me.db.query("SELECT * FROM boxes") + + for box in boxes { + local path = outputDir + "/" + + box.namespace.replace(".", "/") + "/" + + box.name + ".nyash" + + local file = new FileBox(path) + file.write(me.generateFileContent(box)) + } + } + + // ファイル→DB インポート + importFromFiles(sourceDir) { + local files = FileBox.glob(sourceDir + "/**/*.nyash") + + me.db.beginTransaction() + try { + for file in files { + local ast = Parser.parse(file.read()) + me.importAST(ast, file.path) + } + me.db.commit() + } catch (e) { + me.db.rollback() + throw e + } + } +} +``` + +## 🔍 高度な分析機能 + +### コードメトリクス +```sql +-- 循環的複雑度の計算 +CREATE VIEW method_complexity AS +SELECT + m.id, + b.name || '.' || m.name as full_name, + ( + SELECT COUNT(*) + FROM json_each(m.body) + WHERE value LIKE '%if%' + OR value LIKE '%loop%' + OR value LIKE '%catch%' + ) + 1 as cyclomatic_complexity +FROM methods m +JOIN boxes b ON m.box_id = b.id; + +-- コード行数統計 +CREATE VIEW code_stats AS +SELECT + COUNT(DISTINCT b.id) as total_boxes, + COUNT(DISTINCT m.id) as total_methods, + SUM(LENGTH(m.body) - LENGTH(REPLACE(m.body, char(10), ''))) as total_lines, + AVG(LENGTH(m.body) - LENGTH(REPLACE(m.body, char(10), ''))) as avg_method_lines +FROM boxes b +LEFT JOIN methods m ON b.id = m.box_id; +``` + +### 依存関係の可視化 +```nyash +box DependencyAnalyzer { + // 影響範囲分析 + getImpactedEntities(changedEntity) { + return me.db.query(" + WITH RECURSIVE impacted AS ( + -- 直接依存 + SELECT to_type, to_id, 1 as level + FROM dependencies + WHERE from_type = ? AND from_id = ? + + UNION + + -- 推移的依存 + SELECT d.to_type, d.to_id, i.level + 1 + FROM dependencies d + JOIN impacted i ON + d.from_type = i.to_type AND + d.from_id = i.to_id + WHERE i.level < 5 -- 最大5階層まで + ) + SELECT DISTINCT * FROM impacted + ORDER BY level + ", [changedEntity.type, changedEntity.id]) + } +} +``` + +## 🌐 分散開発対応 + +### ブランチ・マージ戦略 +```sql +-- ブランチ管理 +CREATE TABLE branches ( + id INTEGER PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + base_commit_id INTEGER, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + created_by INTEGER, + is_active BOOLEAN DEFAULT TRUE +); + +-- コミット(変更セット) +CREATE TABLE commits ( + id INTEGER PRIMARY KEY, + branch_id INTEGER, + parent_commit_id INTEGER, + message TEXT, + author INTEGER, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + changes JSON, -- 変更の詳細 + FOREIGN KEY (branch_id) REFERENCES branches(id) +); +``` + +### コンフリクト解決 +```nyash +box ConflictResolver { + // 3-way マージ + merge(base, mine, theirs) { + if mine == theirs { + return mine // 変更なし or 同じ変更 + } + + if base == mine { + return theirs // 相手のみ変更 + } + + if base == theirs { + return mine // 自分のみ変更 + } + + // 両方変更 - コンフリクト + return me.resolveConflict(base, mine, theirs) + } + + resolveConflict(base, mine, theirs) { + // AST レベルでのマージを試みる + local baseAST = Parser.parse(base) + local mineAST = Parser.parse(mine) + local theirsAST = Parser.parse(theirs) + + // メソッド単位でマージ可能か確認 + if me.canMergeAtMethodLevel(baseAST, mineAST, theirsAST) { + return me.mergeASTs(baseAST, mineAST, theirsAST) + } + + // マージ不可 - ユーザーに選択させる + throw new MergeConflict(base, mine, theirs) + } +} +``` + +## 📊 メトリクス・モニタリング + +### パフォーマンス追跡 +```sql +-- クエリパフォーマンスログ +CREATE TABLE query_performance ( + id INTEGER PRIMARY KEY, + query_hash TEXT, + query_text TEXT, + execution_time_ms INTEGER, + rows_affected INTEGER, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- DB統計情報 +CREATE VIEW db_stats AS +SELECT + (SELECT COUNT(*) FROM boxes) as total_boxes, + (SELECT COUNT(*) FROM methods) as total_methods, + (SELECT COUNT(*) FROM dependencies) as total_dependencies, + (SELECT page_count * page_size FROM pragma_page_count(), pragma_page_size()) as db_size_bytes, + (SELECT COUNT(*) FROM compile_cache) as cached_compilations; +``` + +## 🔮 将来の拡張性 + +### プラグインアーキテクチャ +```nyash +box CodeDBPlugin { + // フック機能 + hooks: MapBox + + register(event, handler) { + if not me.hooks.has(event) { + me.hooks.set(event, new ArrayBox()) + } + me.hooks.get(event).push(handler) + } + + trigger(event, data) { + if me.hooks.has(event) { + for handler in me.hooks.get(event) { + handler(data) + } + } + } +} + +// 使用例:自動フォーマッター +box AutoFormatter from CodeDBPlugin { + birth() { + me.register("before_save", me.formatCode) + } + + formatCode(data) { + if data.entity_type == "method" { + data.body = Formatter.format(data.body) + } + } +} +``` + +### AI統合の準備 +```sql +-- ベクトル埋め込み保存 +CREATE TABLE embeddings ( + id INTEGER PRIMARY KEY, + entity_type TEXT, + entity_id INTEGER, + embedding BLOB, -- float配列をBLOBで保存 + model_version TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 類似性検索用の仮想テーブル(将来的にベクトル検索エンジンと統合) +CREATE VIRTUAL TABLE vector_search USING vector_index( + embedding FLOAT[768] +); +``` + +--- + +これらの技術的考慮事項を踏まえて、段階的に実装を進めることで、 +安全で高性能なデータベース駆動開発環境を実現できるにゃ! \ No newline at end of file diff --git a/src/backend/llvm/compiler/codegen/instructions/arith_ops.rs b/src/backend/llvm/compiler/codegen/instructions/arith_ops.rs new file mode 100644 index 00000000..837ae03f --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/arith_ops.rs @@ -0,0 +1,310 @@ +use std::collections::HashMap; + +use inkwell::{values::BasicValueEnum, AddressSpace}; +use crate::backend::llvm::compiler::codegen::types; + +use crate::backend::llvm::context::CodegenContext; +use crate::mir::{function::MirFunction, instruction::UnaryOp, BinaryOp, ValueId}; + +/// Lower UnaryOp and store into vmap (0-diff) +pub(in super::super) fn lower_unary<'ctx>( + codegen: &CodegenContext<'ctx>, + vmap: &mut HashMap>, + dst: ValueId, + op: &UnaryOp, + operand: &ValueId, +) -> Result<(), String> { + use crate::backend::llvm::compiler::helpers::{as_float, as_int}; + let v = *vmap.get(operand).ok_or("operand missing")?; + let out = match op { + UnaryOp::Neg => { + if let Some(iv) = as_int(v) { + codegen + .builder + .build_int_neg(iv, "ineg") + .map_err(|e| e.to_string())? + .into() + } else if let Some(fv) = as_float(v) { + codegen + .builder + .build_float_neg(fv, "fneg") + .map_err(|e| e.to_string())? + .into() + } else { + return Err("neg on non-number".to_string()); + } + } + UnaryOp::Not | UnaryOp::BitNot => { + if let Some(iv) = as_int(v) { + codegen + .builder + .build_not(iv, "inot") + .map_err(|e| e.to_string())? + .into() + } else { + return Err("not on non-int".to_string()); + } + } + }; + vmap.insert(dst, out); + Ok(()) +} + +/// Lower BinOp and store into vmap (includes concat fallback) +pub(in super::super) fn lower_binop<'ctx>( + codegen: &CodegenContext<'ctx>, + func: &MirFunction, + vmap: &mut HashMap>, + dst: ValueId, + op: &BinaryOp, + lhs: &ValueId, + rhs: &ValueId, +) -> Result<(), String> { + use crate::backend::llvm::compiler::helpers::{as_float, as_int}; + use inkwell::values::BasicValueEnum as BVE; + use inkwell::IntPredicate; + let lv = *vmap.get(lhs).ok_or("lhs missing")?; + let rv = *vmap.get(rhs).ok_or("rhs missing")?; + let mut handled_concat = false; + if let BinaryOp::Add = op { + let i8p = codegen.context.ptr_type(AddressSpace::from(0)); + let is_stringish = |vid: &ValueId| -> bool { + match func.metadata.value_types.get(vid) { + Some(crate::mir::MirType::String) => true, + Some(crate::mir::MirType::Box(_)) => true, + _ => false, + } + }; + match (lv, rv) { + (BVE::PointerValue(lp), BVE::PointerValue(rp)) => { + let fnty = i8p.fn_type(&[i8p.into(), i8p.into()], false); + let callee = codegen + .module + .get_function("nyash.string.concat_ss") + .unwrap_or_else(|| codegen.module.add_function("nyash.string.concat_ss", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[lp.into(), rp.into()], "concat_ss") + .map_err(|e| e.to_string())?; + let rv = call + .try_as_basic_value() + .left() + .ok_or("concat_ss returned void".to_string())?; + vmap.insert(dst, rv); + handled_concat = true; + } + (BVE::PointerValue(lp), BVE::IntValue(ri)) => { + if is_stringish(lhs) && is_stringish(rhs) { + let i64t = codegen.context.i64_type(); + let fnty_conv = i64t.fn_type(&[i8p.into()], false); + let conv = codegen + .module + .get_function("nyash.box.from_i8_string") + .unwrap_or_else(|| codegen.module.add_function("nyash.box.from_i8_string", fnty_conv, None)); + let call_c = codegen + .builder + .build_call(conv, &[lp.into()], "lhs_i8_to_handle") + .map_err(|e| e.to_string())?; + let lh = call_c + .try_as_basic_value() + .left() + .ok_or("from_i8_string returned void".to_string())? + .into_int_value(); + let fnty_hh = i64t.fn_type(&[i64t.into(), i64t.into()], false); + let callee = codegen + .module + .get_function("nyash.string.concat_hh") + .unwrap_or_else(|| codegen.module.add_function("nyash.string.concat_hh", fnty_hh, None)); + let call = codegen + .builder + .build_call(callee, &[lh.into(), ri.into()], "concat_hh") + .map_err(|e| e.to_string())?; + let rv = call + .try_as_basic_value() + .left() + .ok_or("concat_hh returned void".to_string())?; + vmap.insert(dst, rv); + handled_concat = true; + } else { + let i64t = codegen.context.i64_type(); + let fnty = i8p.fn_type(&[i8p.into(), i64t.into()], false); + let callee = codegen + .module + .get_function("nyash.string.concat_si") + .unwrap_or_else(|| codegen.module.add_function("nyash.string.concat_si", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[lp.into(), ri.into()], "concat_si") + .map_err(|e| e.to_string())?; + let rv = call + .try_as_basic_value() + .left() + .ok_or("concat_si returned void".to_string())?; + vmap.insert(dst, rv); + handled_concat = true; + } + } + (BVE::IntValue(li), BVE::PointerValue(rp)) => { + if is_stringish(lhs) && is_stringish(rhs) { + let i64t = codegen.context.i64_type(); + let fnty_conv = i64t.fn_type(&[i8p.into()], false); + let conv = codegen + .module + .get_function("nyash.box.from_i8_string") + .unwrap_or_else(|| codegen.module.add_function("nyash.box.from_i8_string", fnty_conv, None)); + let call_c = codegen + .builder + .build_call(conv, &[rp.into()], "rhs_i8_to_handle") + .map_err(|e| e.to_string())?; + let rh = call_c + .try_as_basic_value() + .left() + .ok_or("from_i8_string returned void".to_string())? + .into_int_value(); + let fnty_hh = i64t.fn_type(&[i64t.into(), i64t.into()], false); + let callee = codegen + .module + .get_function("nyash.string.concat_hh") + .unwrap_or_else(|| codegen.module.add_function("nyash.string.concat_hh", fnty_hh, None)); + let call = codegen + .builder + .build_call(callee, &[li.into(), rh.into()], "concat_hh") + .map_err(|e| e.to_string())?; + let rv = call + .try_as_basic_value() + .left() + .ok_or("concat_hh returned void".to_string())?; + vmap.insert(dst, rv); + handled_concat = true; + } else { + let i64t = codegen.context.i64_type(); + let fnty = i8p.fn_type(&[i64t.into(), i8p.into()], false); + let callee = codegen + .module + .get_function("nyash.string.concat_is") + .unwrap_or_else(|| codegen.module.add_function("nyash.string.concat_is", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[li.into(), rp.into()], "concat_is") + .map_err(|e| e.to_string())?; + let rv = call + .try_as_basic_value() + .left() + .ok_or("concat_is returned void".to_string())?; + vmap.insert(dst, rv); + handled_concat = true; + } + } + _ => {} + } + } + if handled_concat { + return Ok(()); + } + + let out = if let (Some(li), Some(ri)) = (as_int(lv), as_int(rv)) { + use BinaryOp as B; + match op { + B::Add => codegen + .builder + .build_int_add(li, ri, "iadd") + .map_err(|e| e.to_string())? + .into(), + B::Sub => codegen + .builder + .build_int_sub(li, ri, "isub") + .map_err(|e| e.to_string())? + .into(), + B::Mul => codegen + .builder + .build_int_mul(li, ri, "imul") + .map_err(|e| e.to_string())? + .into(), + B::Div => codegen + .builder + .build_int_signed_div(li, ri, "idiv") + .map_err(|e| e.to_string())? + .into(), + B::Mod => codegen + .builder + .build_int_signed_rem(li, ri, "imod") + .map_err(|e| e.to_string())? + .into(), + B::BitAnd => codegen + .builder + .build_and(li, ri, "iand") + .map_err(|e| e.to_string())? + .into(), + B::BitOr => codegen + .builder + .build_or(li, ri, "ior") + .map_err(|e| e.to_string())? + .into(), + B::BitXor => codegen + .builder + .build_xor(li, ri, "ixor") + .map_err(|e| e.to_string())? + .into(), + B::Shl => codegen + .builder + .build_left_shift(li, ri, "ishl") + .map_err(|e| e.to_string())? + .into(), + B::Shr => codegen + .builder + .build_right_shift(li, ri, false, "ishr") + .map_err(|e| e.to_string())? + .into(), + B::And | B::Or => { + // Treat as logical on integers: convert to i1 and and/or + let lb = types::to_bool(codegen.context, li.into(), &codegen.builder)?; + let rb = types::to_bool(codegen.context, ri.into(), &codegen.builder)?; + match op { + B::And => codegen + .builder + .build_and(lb, rb, "land") + .map_err(|e| e.to_string())? + .into(), + _ => codegen + .builder + .build_or(lb, rb, "lor") + .map_err(|e| e.to_string())? + .into(), + } + } + } + } else if let (Some(lf), Some(rf)) = (as_float(lv), as_float(rv)) { + use BinaryOp as B; + match op { + B::Add => codegen + .builder + .build_float_add(lf, rf, "fadd") + .map_err(|e| e.to_string())? + .into(), + B::Sub => codegen + .builder + .build_float_sub(lf, rf, "fsub") + .map_err(|e| e.to_string())? + .into(), + B::Mul => codegen + .builder + .build_float_mul(lf, rf, "fmul") + .map_err(|e| e.to_string())? + .into(), + B::Div => codegen + .builder + .build_float_div(lf, rf, "fdiv") + .map_err(|e| e.to_string())? + .into(), + B::Mod => return Err("fmod not supported yet".to_string()), + _ => return Err("bit/logic ops on float".to_string()), + } + } else if let (BasicValueEnum::PointerValue(lp), BasicValueEnum::PointerValue(rp)) = (lv, rv) { + // Support pointer addition/subtraction if needed? For now, only equality is in compare. + return Err("unsupported pointer binop".to_string()); + } else { + return Err("binop type mismatch".to_string()); + }; + vmap.insert(dst, out); + Ok(()) +} diff --git a/src/backend/llvm/compiler/codegen/instructions/arrays.rs b/src/backend/llvm/compiler/codegen/instructions/arrays.rs new file mode 100644 index 00000000..6749aad0 --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/arrays.rs @@ -0,0 +1,130 @@ +use std::collections::HashMap; + +use inkwell::values::BasicValueEnum as BVE; + +use crate::backend::llvm::context::CodegenContext; +use crate::mir::{function::MirFunction, ValueId}; + +/// Handle ArrayBox fast-paths. Returns true if handled. +pub(super) fn try_handle_array_method<'ctx>( + codegen: &CodegenContext<'ctx>, + func: &MirFunction, + vmap: &mut HashMap>, + dst: &Option, + box_val: &ValueId, + method: &str, + args: &[ValueId], + recv_h: inkwell::values::IntValue<'ctx>, +) -> Result { + // Only when receiver is ArrayBox + let is_array = matches!(func.metadata.value_types.get(box_val), Some(crate::mir::MirType::Box(b)) if b == "ArrayBox"); + if !is_array { + return Ok(false); + } + let i64t = codegen.context.i64_type(); + match method { + "get" => { + if args.len() != 1 { + return Err("ArrayBox.get expects 1 arg".to_string()); + } + let idx_v = *vmap.get(&args[0]).ok_or("array.get index missing")?; + let idx_i = if let BVE::IntValue(iv) = idx_v { + iv + } else { + return Err("array.get index must be int".to_string()); + }; + let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false); + let callee = codegen + .module + .get_function("nyash_array_get_h") + .unwrap_or_else(|| codegen.module.add_function("nyash_array_get_h", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[recv_h.into(), idx_i.into()], "aget") + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + let rv = call + .try_as_basic_value() + .left() + .ok_or("array_get_h returned void".to_string())?; + vmap.insert(*d, rv); + } + Ok(true) + } + "set" => { + if args.len() != 2 { + return Err("ArrayBox.set expects 2 arg".to_string()); + } + let idx_v = *vmap.get(&args[0]).ok_or("array.set index missing")?; + let val_v = *vmap.get(&args[1]).ok_or("array.set value missing")?; + let idx_i = if let BVE::IntValue(iv) = idx_v { + iv + } else { + return Err("array.set index must be int".to_string()); + }; + let val_i = if let BVE::IntValue(iv) = val_v { + iv + } else { + return Err("array.set value must be int".to_string()); + }; + let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into()], false); + let callee = codegen + .module + .get_function("nyash_array_set_h") + .unwrap_or_else(|| codegen.module.add_function("nyash_array_set_h", fnty, None)); + let _ = codegen + .builder + .build_call(callee, &[recv_h.into(), idx_i.into(), val_i.into()], "aset") + .map_err(|e| e.to_string())?; + Ok(true) + } + "push" => { + if args.len() != 1 { + return Err("ArrayBox.push expects 1 arg".to_string()); + } + let val_v = *vmap.get(&args[0]).ok_or("array.push value missing")?; + let val_i = match val_v { + BVE::IntValue(iv) => iv, + BVE::PointerValue(pv) => codegen + .builder + .build_ptr_to_int(pv, i64t, "val_p2i") + .map_err(|e| e.to_string())?, + _ => return Err("array.push value must be int or handle ptr".to_string()), + }; + let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false); + let callee = codegen + .module + .get_function("nyash_array_push_h") + .unwrap_or_else(|| codegen.module.add_function("nyash_array_push_h", fnty, None)); + let _ = codegen + .builder + .build_call(callee, &[recv_h.into(), val_i.into()], "apush") + .map_err(|e| e.to_string())?; + Ok(true) + } + "length" => { + if !args.is_empty() { + return Err("ArrayBox.length expects 0 arg".to_string()); + } + let fnty = i64t.fn_type(&[i64t.into()], false); + let callee = codegen + .module + .get_function("nyash_array_length_h") + .unwrap_or_else(|| codegen.module.add_function("nyash_array_length_h", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[recv_h.into()], "alen") + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + let rv = call + .try_as_basic_value() + .left() + .ok_or("array_length_h returned void".to_string())?; + vmap.insert(*d, rv); + } + Ok(true) + } + _ => Ok(false), + } +} + diff --git a/src/backend/llvm/compiler/codegen/instructions/boxcall.rs b/src/backend/llvm/compiler/codegen/instructions/boxcall.rs index 61873315..92f1f1e0 100644 --- a/src/backend/llvm/compiler/codegen/instructions/boxcall.rs +++ b/src/backend/llvm/compiler/codegen/instructions/boxcall.rs @@ -4,6 +4,9 @@ use inkwell::AddressSpace; use inkwell::values::BasicValueEnum as BVE; use crate::backend::llvm::context::CodegenContext; +mod fields; +mod invoke; +mod marshal; use crate::mir::{function::MirFunction, ValueId}; // BoxCall lowering (large): mirrors existing logic; kept in one function for now @@ -48,395 +51,38 @@ pub(in super::super) fn lower_boxcall<'ctx>( 0 }; - // String concat fast-path (avoid plugin path for builtin StringBox) - if method == "concat" { - // Recognize receiver typed as String or StringBox - let is_string_recv = match func.metadata.value_types.get(box_val) { - Some(crate::mir::MirType::String) => true, - Some(crate::mir::MirType::Box(b)) if b == "StringBox" => true, - _ => false, - }; - if is_string_recv { - if args.len() != 1 { return Err("String.concat expects 1 arg".to_string()); } - // Prefer pointer-based concat to keep AOT string fast-path - let i8p = codegen.context.ptr_type(AddressSpace::from(0)); - let rhs_v = *vmap.get(&args[0]).ok_or("concat arg missing")?; - match (recv_v, rhs_v) { - (BVE::PointerValue(lp), BVE::PointerValue(rp)) => { - let fnty = i8p.fn_type(&[i8p.into(), i8p.into()], false); - let callee = codegen.module.get_function("nyash.string.concat_ss"). - unwrap_or_else(|| codegen.module.add_function("nyash.string.concat_ss", fnty, None)); - let call = codegen.builder.build_call(callee, &[lp.into(), rp.into()], "concat_ss_call").map_err(|e| e.to_string())?; - if let Some(d) = dst { let rv = call.try_as_basic_value().left().ok_or("concat_ss returned void".to_string())?; vmap.insert(*d, rv); } - return Ok(()); - } - (BVE::PointerValue(lp), BVE::IntValue(ri)) => { - let i64t = codegen.context.i64_type(); - let fnty = i8p.fn_type(&[i8p.into(), i64t.into()], false); - let callee = codegen.module.get_function("nyash.string.concat_si"). - unwrap_or_else(|| codegen.module.add_function("nyash.string.concat_si", fnty, None)); - let call = codegen.builder.build_call(callee, &[lp.into(), ri.into()], "concat_si_call").map_err(|e| e.to_string())?; - if let Some(d) = dst { let rv = call.try_as_basic_value().left().ok_or("concat_si returned void".to_string())?; vmap.insert(*d, rv); } - return Ok(()); - } - (BVE::IntValue(li), BVE::PointerValue(rp)) => { - let i64t = codegen.context.i64_type(); - let fnty = i8p.fn_type(&[i64t.into(), i8p.into()], false); - let callee = codegen.module.get_function("nyash.string.concat_is"). - unwrap_or_else(|| codegen.module.add_function("nyash.string.concat_is", fnty, None)); - let call = codegen.builder.build_call(callee, &[li.into(), rp.into()], "concat_is_call").map_err(|e| e.to_string())?; - if let Some(d) = dst { let rv = call.try_as_basic_value().left().ok_or("concat_is returned void".to_string())?; vmap.insert(*d, rv); } - return Ok(()); - } - _ => { /* fall through to generic path below */ } - } - } + // Delegate String methods + if super::strings::try_handle_string_method(codegen, func, vmap, dst, box_val, method, args, recv_v)? { + return Ok(()); } - // String length fast-path: length/len - if method == "length" || method == "len" { - // Only when receiver is String/StringBox by annotation - let is_string_recv = match func.metadata.value_types.get(box_val) { - Some(crate::mir::MirType::String) => true, - Some(crate::mir::MirType::Box(b)) if b == "StringBox" => true, - _ => false, - }; - if is_string_recv { - let i64t = codegen.context.i64_type(); - // Ensure we have a handle: convert i8* receiver to handle when needed - let recv_h = match recv_v { - BVE::IntValue(h) => h, - BVE::PointerValue(p) => { - let fnty = i64t.fn_type(&[codegen.context.ptr_type(AddressSpace::from(0)).into()], false); - let callee = codegen - .module - .get_function("nyash.box.from_i8_string") - .unwrap_or_else(|| codegen.module.add_function("nyash.box.from_i8_string", fnty, None)); - let call = codegen - .builder - .build_call(callee, &[p.into()], "str_ptr_to_handle") - .map_err(|e| e.to_string())?; - let rv = call - .try_as_basic_value() - .left() - .ok_or("from_i8_string returned void".to_string())?; - if let BVE::IntValue(iv) = rv { iv } else { return Err("from_i8_string ret expected i64".to_string()); } - } - _ => return Err("String.length receiver type unsupported".to_string()), - }; - // call i64 @nyash.string.len_h(i64) - let fnty = i64t.fn_type(&[i64t.into()], false); - let callee = codegen - .module - .get_function("nyash.string.len_h") - .unwrap_or_else(|| codegen.module.add_function("nyash.string.len_h", fnty, None)); - let call = codegen - .builder - .build_call(callee, &[recv_h.into()], "strlen_h") - .map_err(|e| e.to_string())?; - if let Some(d) = dst { - let rv = call - .try_as_basic_value() - .left() - .ok_or("len_h returned void".to_string())?; - vmap.insert(*d, rv); - } - return Ok(()); - } + // Delegate Array methods + if super::arrays::try_handle_array_method(codegen, func, vmap, dst, box_val, method, args, recv_h)? { + return Ok(()); } - // Array fast-paths - if let Some(crate::mir::MirType::Box(bname)) = func.metadata.value_types.get(box_val) { - if bname == "ArrayBox" && (method == "get" || method == "set" || method == "push" || method == "length") { - match method { - "get" => { - if args.len() != 1 { return Err("ArrayBox.get expects 1 arg".to_string()); } - let idx_v = *vmap.get(&args[0]).ok_or("array.get index missing")?; - let idx_i = if let BVE::IntValue(iv) = idx_v { iv } else { return Err("array.get index must be int".to_string()); }; - let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false); - let callee = codegen.module.get_function("nyash_array_get_h").unwrap_or_else(|| codegen.module.add_function("nyash_array_get_h", fnty, None)); - let call = codegen.builder.build_call(callee, &[recv_h.into(), idx_i.into()], "aget").map_err(|e| e.to_string())?; - if let Some(d) = dst { - let rv = call.try_as_basic_value().left().ok_or("array_get_h returned void".to_string())?; - vmap.insert(*d, rv); - } - return Ok(()); - } - "set" => { - if args.len() != 2 { return Err("ArrayBox.set expects 2 arg".to_string()); } - let idx_v = *vmap.get(&args[0]).ok_or("array.set index missing")?; - let val_v = *vmap.get(&args[1]).ok_or("array.set value missing")?; - let idx_i = if let BVE::IntValue(iv) = idx_v { iv } else { return Err("array.set index must be int".to_string()); }; - let val_i = if let BVE::IntValue(iv) = val_v { iv } else { return Err("array.set value must be int".to_string()); }; - let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into()], false); - let callee = codegen.module.get_function("nyash_array_set_h").unwrap_or_else(|| codegen.module.add_function("nyash_array_set_h", fnty, None)); - let _ = codegen.builder.build_call(callee, &[recv_h.into(), idx_i.into(), val_i.into()], "aset").map_err(|e| e.to_string())?; - return Ok(()); - } - "push" => { - if args.len() != 1 { return Err("ArrayBox.push expects 1 arg".to_string()); } - let val_v = *vmap.get(&args[0]).ok_or("array.push value missing")?; - let val_i = match val_v { - BVE::IntValue(iv) => iv, - BVE::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "val_p2i").map_err(|e| e.to_string())?, - _ => return Err("array.push value must be int or handle ptr".to_string()), - }; - let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false); - let callee = codegen.module.get_function("nyash_array_push_h").unwrap_or_else(|| codegen.module.add_function("nyash_array_push_h", fnty, None)); - let _ = codegen.builder.build_call(callee, &[recv_h.into(), val_i.into()], "apush").map_err(|e| e.to_string())?; - return Ok(()); - } - "length" => { - if !args.is_empty() { return Err("ArrayBox.length expects 0 arg".to_string()); } - let fnty = i64t.fn_type(&[i64t.into()], false); - let callee = codegen.module.get_function("nyash_array_length_h").unwrap_or_else(|| codegen.module.add_function("nyash_array_length_h", fnty, None)); - let call = codegen.builder.build_call(callee, &[recv_h.into()], "alen").map_err(|e| e.to_string())?; - if let Some(d) = dst { - let rv = call.try_as_basic_value().left().ok_or("array_length_h returned void".to_string())?; - vmap.insert(*d, rv); - } - return Ok(()); - } - _ => {} - } - } + // Delegate Map methods + if super::maps::try_handle_map_method(codegen, func, vmap, dst, box_val, method, args, recv_h)? { + return Ok(()); } - // Map fast-paths (core-first): get/set/has/size using NyRT shims with handle receiver - if let Some(crate::mir::MirType::Box(bname)) = func.metadata.value_types.get(box_val) { - if bname == "MapBox" && (method == "get" || method == "set" || method == "has" || method == "size") { - let i64t = codegen.context.i64_type(); - match method { - "size" => { - if !args.is_empty() { return Err("MapBox.size expects 0 arg".to_string()); } - let fnty = i64t.fn_type(&[i64t.into()], false); - let callee = codegen.module.get_function("nyash.map.size_h").unwrap_or_else(|| codegen.module.add_function("nyash.map.size_h", fnty, None)); - let call = codegen.builder.build_call(callee, &[recv_h.into()], "msize").map_err(|e| e.to_string())?; - if let Some(d) = dst { - let rv = call.try_as_basic_value().left().ok_or("map.size_h returned void".to_string())?; - vmap.insert(*d, rv); - } - return Ok(()); - } - "has" => { - if args.len() != 1 { return Err("MapBox.has expects 1 arg".to_string()); } - let key_v = *vmap.get(&args[0]).ok_or("map.has key missing")?; - let key_i = match key_v { - BVE::IntValue(iv) => iv, - BVE::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "key_p2i").map_err(|e| e.to_string())?, - _ => return Err("map.has key must be int or handle ptr".to_string()), - }; - let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false); - let callee = codegen.module.get_function("nyash.map.has_h").unwrap_or_else(|| codegen.module.add_function("nyash.map.has_h", fnty, None)); - let call = codegen.builder.build_call(callee, &[recv_h.into(), key_i.into()], "mhas").map_err(|e| e.to_string())?; - if let Some(d) = dst { - let rv = call.try_as_basic_value().left().ok_or("map.has_h returned void".to_string())?; - vmap.insert(*d, rv); - } - return Ok(()); - } - "get" => { - if args.len() != 1 { return Err("MapBox.get expects 1 arg".to_string()); } - let key_v = *vmap.get(&args[0]).ok_or("map.get key missing")?; - // prefer integer key; if pointer, convert to handle and call get_hh - let call = match key_v { - BVE::IntValue(iv) => { - let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false); - let callee = codegen.module.get_function("nyash.map.get_h").unwrap_or_else(|| codegen.module.add_function("nyash.map.get_h", fnty, None)); - codegen.builder.build_call(callee, &[recv_h.into(), iv.into()], "mget").map_err(|e| e.to_string())? - } - BVE::PointerValue(pv) => { - // key: i8* -> i64 handle via from_i8_string (string key) - let fnty_conv = i64t.fn_type(&[codegen.context.ptr_type(AddressSpace::from(0)).into()], false); - let conv = codegen.module.get_function("nyash.box.from_i8_string").unwrap_or_else(|| codegen.module.add_function("nyash.box.from_i8_string", fnty_conv, None)); - let kcall = codegen.builder.build_call(conv, &[pv.into()], "key_i8_to_handle").map_err(|e| e.to_string())?; - let kh = kcall.try_as_basic_value().left().ok_or("from_i8_string returned void".to_string())?.into_int_value(); - let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false); - let callee = codegen.module.get_function("nyash.map.get_hh").unwrap_or_else(|| codegen.module.add_function("nyash.map.get_hh", fnty, None)); - codegen.builder.build_call(callee, &[recv_h.into(), kh.into()], "mget_hh").map_err(|e| e.to_string())? - } - _ => return Err("map.get key must be int or pointer".to_string()), - }; - if let Some(d) = dst { - let rv = call.try_as_basic_value().left().ok_or("map.get returned void".to_string())?; - vmap.insert(*d, rv); - } - return Ok(()); - } - "set" => { - if args.len() != 2 { return Err("MapBox.set expects 2 args (key, value)".to_string()); } - let key_v = *vmap.get(&args[0]).ok_or("map.set key missing")?; - let val_v = *vmap.get(&args[1]).ok_or("map.set value missing")?; - let key_i = match key_v { - BVE::IntValue(iv) => iv, - BVE::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "key_p2i").map_err(|e| e.to_string())?, - _ => return Err("map.set key must be int or handle ptr".to_string()), - }; - let val_i = match val_v { - BVE::IntValue(iv) => iv, - BVE::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "val_p2i").map_err(|e| e.to_string())?, - _ => return Err("map.set value must be int or handle ptr".to_string()), - }; - let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into()], false); - let callee = codegen.module.get_function("nyash.map.set_h").unwrap_or_else(|| codegen.module.add_function("nyash.map.set_h", fnty, None)); - let _ = codegen.builder.build_call(callee, &[recv_h.into(), key_i.into(), val_i.into()], "mset").map_err(|e| e.to_string())?; - return Ok(()); - } - _ => {} - } - } - } - - // getField - if method == "getField" { - if args.len() != 1 { return Err("getField expects 1 arg (name)".to_string()); } - let name_v = *vmap.get(&args[0]).ok_or("getField name missing")?; - let name_p = if let BVE::PointerValue(pv) = name_v { pv } else { return Err("getField name must be pointer".to_string()); }; - let i8p = codegen.context.ptr_type(AddressSpace::from(0)); - let fnty = i64t.fn_type(&[i64t.into(), i8p.into()], false); - let callee = codegen.module.get_function("nyash.instance.get_field_h").unwrap_or_else(|| codegen.module.add_function("nyash.instance.get_field_h", fnty, None)); - let call = codegen.builder.build_call(callee, &[recv_h.into(), name_p.into()], "getField").map_err(|e| e.to_string())?; - if let Some(d) = dst { - let rv = call.try_as_basic_value().left().ok_or("get_field returned void".to_string())?; - let h = if let BVE::IntValue(iv) = rv { iv } else { return Err("get_field ret expected i64".to_string()); }; - let pty = codegen.context.ptr_type(AddressSpace::from(0)); - let ptr = codegen.builder.build_int_to_ptr(h, pty, "gf_handle_to_ptr").map_err(|e| e.to_string())?; - vmap.insert(*d, ptr.into()); - } - // continue for potential further lowering - } - if method == "setField" { - if args.len() != 2 { return Err("setField expects 2 args (name, value)".to_string()); } - let name_v = *vmap.get(&args[0]).ok_or("setField name missing")?; - let val_v = *vmap.get(&args[1]).ok_or("setField value missing")?; - let name_p = if let BVE::PointerValue(pv) = name_v { pv } else { return Err("setField name must be pointer".to_string()); }; - let val_h = match val_v { - BVE::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "valp2i").map_err(|e| e.to_string())?, - BVE::IntValue(iv) => iv, - BVE::FloatValue(_) => return Err("setField value must be int/handle".to_string()), - _ => return Err("setField value must be int/handle".to_string()), - }; - let i8p = codegen.context.ptr_type(AddressSpace::from(0)); - let fnty = i64t.fn_type(&[i64t.into(), i8p.into(), i64t.into()], false); - let callee = codegen.module.get_function("nyash.instance.set_field_h").unwrap_or_else(|| codegen.module.add_function("nyash.instance.set_field_h", fnty, None)); - let _ = codegen.builder.build_call(callee, &[recv_h.into(), name_p.into(), val_h.into()], "setField").map_err(|e| e.to_string())?; - // continue for method_id path if any + // getField/setField + if fields::try_handle_field_method(codegen, vmap, dst, method, args, recv_h)? { + return Ok(()); } if let Some(mid) = method_id { - // Build argc and a1..a4 - let argc_val = i64t.const_int(args.len() as u64, false); - let mut a1 = i64t.const_zero(); - let mut a2 = i64t.const_zero(); - let mut a3 = i64t.const_zero(); - let mut a4 = i64t.const_zero(); - let get_i64 = |vid: ValueId| -> Result, String> { - let bv = *vmap.get(&vid).ok_or("arg missing")?; - match bv { - BVE::IntValue(iv) => Ok(iv), - BVE::PointerValue(pv) => codegen - .builder - .build_ptr_to_int(pv, i64t, "p2i") - .map_err(|e| e.to_string()), - BVE::FloatValue(fv) => { - let fnty = i64t.fn_type(&[codegen.context.f64_type().into()], false); - let callee = codegen - .module - .get_function("nyash.box.from_f64") - .unwrap_or_else(|| codegen.module.add_function("nyash.box.from_f64", fnty, None)); - let call = codegen - .builder - .build_call(callee, &[fv.into()], "arg_f64_to_box") - .map_err(|e| e.to_string())?; - let rv = call.try_as_basic_value().left().ok_or("from_f64 returned void".to_string())?; - if let BVE::IntValue(h) = rv { Ok(h) } else { Err("from_f64 ret expected i64".to_string()) } - } - _ => Err("unsupported arg value".to_string()), - } - }; - if args.len() >= 1 { a1 = get_i64(args[0])?; } - if args.len() >= 2 { a2 = get_i64(args[1])?; } - if args.len() >= 3 { a3 = get_i64(args[2])?; } - if args.len() >= 4 { a4 = get_i64(args[3])?; } - - // Note: String.concat should return a handle (i64) and be processed by the tagged path below. - // tagged fixed-arity <=4 - let mut tag1 = i64t.const_int(3, false); - let mut tag2 = i64t.const_int(3, false); - let mut tag3 = i64t.const_int(3, false); - let mut tag4 = i64t.const_int(3, false); - let classify = |vid: ValueId| -> Option { vmap.get(&vid).map(|v| classify_tag(*v)) }; - if args.len() >= 1 { if let Some(t) = classify(args[0]) { tag1 = i64t.const_int(t as u64, false); } } - if args.len() >= 2 { if let Some(t) = classify(args[1]) { tag2 = i64t.const_int(t as u64, false); } } - if args.len() >= 3 { if let Some(t) = classify(args[2]) { tag3 = i64t.const_int(t as u64, false); } } - if args.len() >= 4 { if let Some(t) = classify(args[3]) { tag4 = i64t.const_int(t as u64, false); } } - if args.len() <= 4 { - let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into()], false); - let callee = codegen.module.get_function("nyash_plugin_invoke3_tagged_i64").unwrap_or_else(|| codegen.module.add_function("nyash_plugin_invoke3_tagged_i64", fnty, None)); - let tid = i64t.const_int(type_id as u64, true); - let midv = i64t.const_int((*mid) as u64, false); - let call = codegen.builder.build_call(callee, &[tid.into(), midv.into(), argc_val.into(), recv_h.into(), a1.into(), tag1.into(), a2.into(), tag2.into(), a3.into(), tag3.into(), a4.into(), tag4.into()], "pinvoke_tagged").map_err(|e| e.to_string())?; - if let Some(d) = dst { - let rv = call.try_as_basic_value().left().ok_or("invoke3_i64 returned void".to_string())?; - if let Some(mt) = func.metadata.value_types.get(d) { - match mt { - crate::mir::MirType::Integer | crate::mir::MirType::Bool => { vmap.insert(*d, rv); } - // String: keep as i64 handle (do not cast to i8*) - crate::mir::MirType::String => { vmap.insert(*d, rv); } - // Box/Array/Future/Unknown: cast handle to opaque pointer - crate::mir::MirType::Box(_) | crate::mir::MirType::Array(_) | crate::mir::MirType::Future(_) | crate::mir::MirType::Unknown => { - let h = if let BVE::IntValue(iv) = rv { iv } else { return Err("invoke ret expected i64".to_string()); }; - let pty = codegen.context.ptr_type(AddressSpace::from(0)); - let ptr = codegen.builder.build_int_to_ptr(h, pty, "ret_handle_to_ptr").map_err(|e| e.to_string())?; - vmap.insert(*d, ptr.into()); - } - _ => { vmap.insert(*d, rv); } - } - } else { - vmap.insert(*d, rv); - } - } - return Ok(()); - } - // variable length path - let n = args.len() as u32; - let arr_ty = i64t.array_type(n); - let vals_arr = entry_builder.build_alloca(arr_ty, "vals_arr").map_err(|e| e.to_string())?; - let tags_arr = entry_builder.build_alloca(arr_ty, "tags_arr").map_err(|e| e.to_string())?; - for (i, vid) in args.iter().enumerate() { - let idx = [codegen.context.i32_type().const_zero(), codegen.context.i32_type().const_int(i as u64, false)]; - let gep_v = unsafe { codegen.builder.build_in_bounds_gep(arr_ty, vals_arr, &idx, &format!("v_gep_{}", i)).map_err(|e| e.to_string())? }; - let gep_t = unsafe { codegen.builder.build_in_bounds_gep(arr_ty, tags_arr, &idx, &format!("t_gep_{}", i)).map_err(|e| e.to_string())? }; - let vi = get_i64(*vid)?; - let ti = classify_tag(*vmap.get(vid).ok_or("missing arg")?); - codegen.builder.build_store(gep_v, vi).map_err(|e| e.to_string())?; - codegen.builder.build_store(gep_t, i64t.const_int(ti as u64, false)).map_err(|e| e.to_string())?; - } - let vals_ptr = codegen.builder.build_pointer_cast(vals_arr, codegen.context.ptr_type(AddressSpace::from(0)), "vals_arr_i8p").map_err(|e| e.to_string())?; - let tags_ptr = codegen.builder.build_pointer_cast(tags_arr, codegen.context.ptr_type(AddressSpace::from(0)), "tags_arr_i8p").map_err(|e| e.to_string())?; - let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into(), i64t.into(), codegen.context.ptr_type(AddressSpace::from(0)).into(), codegen.context.ptr_type(AddressSpace::from(0)).into()], false); - let callee = codegen.module.get_function("nyash.plugin.invoke_tagged_v_i64").unwrap_or_else(|| codegen.module.add_function("nyash.plugin.invoke_tagged_v_i64", fnty, None)); - let tid = i64t.const_int(type_id as u64, true); - let midv = i64t.const_int((*mid) as u64, false); - let call = codegen.builder.build_call(callee, &[tid.into(), midv.into(), argc_val.into(), recv_h.into(), vals_ptr.into(), tags_ptr.into()], "pinvoke_tagged_v").map_err(|e| e.to_string())?; - if let Some(d) = dst { - let rv = call.try_as_basic_value().left().ok_or("invoke_v returned void".to_string())?; - if let Some(mt) = func.metadata.value_types.get(d) { - match mt { - crate::mir::MirType::Integer | crate::mir::MirType::Bool => { vmap.insert(*d, rv); } - crate::mir::MirType::String => { vmap.insert(*d, rv); } - crate::mir::MirType::Box(_) | crate::mir::MirType::Array(_) | crate::mir::MirType::Future(_) | crate::mir::MirType::Unknown => { - let h = if let BVE::IntValue(iv) = rv { iv } else { return Err("invoke ret expected i64".to_string()); }; - let pty = codegen.context.ptr_type(AddressSpace::from(0)); - let ptr = codegen.builder.build_int_to_ptr(h, pty, "ret_handle_to_ptr").map_err(|e| e.to_string())?; - vmap.insert(*d, ptr.into()); - } - _ => { vmap.insert(*d, rv); } - } - } else { - vmap.insert(*d, rv); - } - } + invoke::try_handle_tagged_invoke( + codegen, + func, + vmap, + dst, + *mid, + type_id, + recv_h, + args, + entry_builder, + )?; return Ok(()); } else { Err(format!("BoxCall requires method_id for method '{}'. The method_id should be automatically injected during MIR compilation.", method)) diff --git a/src/backend/llvm/compiler/codegen/instructions/boxcall/fields.rs b/src/backend/llvm/compiler/codegen/instructions/boxcall/fields.rs new file mode 100644 index 00000000..46c493bd --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/boxcall/fields.rs @@ -0,0 +1,93 @@ +use std::collections::HashMap; + +use inkwell::{values::BasicValueEnum as BVE, AddressSpace}; + +use crate::backend::llvm::context::CodegenContext; +use crate::mir::ValueId; + +/// Handle getField/setField; returns true if handled. +pub(super) fn try_handle_field_method<'ctx>( + codegen: &CodegenContext<'ctx>, + vmap: &mut HashMap>, + dst: &Option, + method: &str, + args: &[ValueId], + recv_h: inkwell::values::IntValue<'ctx>, +) -> Result { + let i64t = codegen.context.i64_type(); + match method { + "getField" => { + if args.len() != 1 { + return Err("getField expects 1 arg (name)".to_string()); + } + let name_v = *vmap.get(&args[0]).ok_or("getField name missing")?; + let name_p = if let BVE::PointerValue(pv) = name_v { + pv + } else { + return Err("getField name must be pointer".to_string()); + }; + let i8p = codegen.context.ptr_type(AddressSpace::from(0)); + let fnty = i64t.fn_type(&[i64t.into(), i8p.into()], false); + let callee = codegen + .module + .get_function("nyash.instance.get_field_h") + .unwrap_or_else(|| codegen.module.add_function("nyash.instance.get_field_h", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[recv_h.into(), name_p.into()], "getField") + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + let rv = call + .try_as_basic_value() + .left() + .ok_or("get_field returned void".to_string())?; + let h = if let BVE::IntValue(iv) = rv { + iv + } else { + return Err("get_field ret expected i64".to_string()); + }; + let pty = codegen.context.ptr_type(AddressSpace::from(0)); + let ptr = codegen + .builder + .build_int_to_ptr(h, pty, "gf_handle_to_ptr") + .map_err(|e| e.to_string())?; + vmap.insert(*d, ptr.into()); + } + Ok(true) + } + "setField" => { + if args.len() != 2 { + return Err("setField expects 2 args (name, value)".to_string()); + } + let name_v = *vmap.get(&args[0]).ok_or("setField name missing")?; + let val_v = *vmap.get(&args[1]).ok_or("setField value missing")?; + let name_p = if let BVE::PointerValue(pv) = name_v { + pv + } else { + return Err("setField name must be pointer".to_string()); + }; + let val_h = match val_v { + BVE::PointerValue(pv) => codegen + .builder + .build_ptr_to_int(pv, i64t, "valp2i") + .map_err(|e| e.to_string())?, + BVE::IntValue(iv) => iv, + BVE::FloatValue(_) => return Err("setField value must be int/handle".to_string()), + _ => return Err("setField value must be int/handle".to_string()), + }; + let i8p = codegen.context.ptr_type(AddressSpace::from(0)); + let fnty = i64t.fn_type(&[i64t.into(), i8p.into(), i64t.into()], false); + let callee = codegen + .module + .get_function("nyash.instance.set_field_h") + .unwrap_or_else(|| codegen.module.add_function("nyash.instance.set_field_h", fnty, None)); + let _ = codegen + .builder + .build_call(callee, &[recv_h.into(), name_p.into(), val_h.into()], "setField") + .map_err(|e| e.to_string())?; + Ok(true) + } + _ => Ok(false), + } +} + diff --git a/src/backend/llvm/compiler/codegen/instructions/boxcall/invoke.rs b/src/backend/llvm/compiler/codegen/instructions/boxcall/invoke.rs new file mode 100644 index 00000000..d779d959 --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/boxcall/invoke.rs @@ -0,0 +1,194 @@ +use std::collections::HashMap; + +use inkwell::{values::BasicValueEnum as BVE, AddressSpace}; + +use crate::backend::llvm::context::CodegenContext; +use crate::mir::{function::MirFunction, ValueId}; + +use super::marshal::{get_i64, get_tag_const}; + +/// Handle method_id-tagged plugin invoke path; returns Ok(()) if handled. +pub(super) fn try_handle_tagged_invoke<'ctx>( + codegen: &CodegenContext<'ctx>, + func: &MirFunction, + vmap: &mut HashMap>, + dst: &Option, + mid: u16, + type_id: i64, + recv_h: inkwell::values::IntValue<'ctx>, + args: &[ValueId], + entry_builder: &inkwell::builder::Builder<'ctx>, +) -> Result<(), String> { + let i64t = codegen.context.i64_type(); + let argc_val = i64t.const_int(args.len() as u64, false); + + // Fast path: <= 4 fixed args + if args.len() <= 4 { + let mut a = [i64t.const_zero(); 4]; + for (i, vid) in args.iter().enumerate() { + a[i] = get_i64(codegen, vmap, *vid)?; + } + let mut tags = [i64t.const_int(3, false); 4]; + for (i, vid) in args.iter().enumerate() { + tags[i] = get_tag_const(codegen, vmap, *vid); + } + let fnty = i64t.fn_type( + &[ + i64t.into(), i64t.into(), i64t.into(), i64t.into(), + i64t.into(), i64t.into(), i64t.into(), i64t.into(), + i64t.into(), i64t.into(), i64t.into(), i64t.into(), + ], + false, + ); + let callee = codegen + .module + .get_function("nyash_plugin_invoke3_tagged_i64") + .unwrap_or_else(|| codegen.module.add_function("nyash_plugin_invoke3_tagged_i64", fnty, None)); + let tid = i64t.const_int(type_id as u64, true); + let midv = i64t.const_int(mid as u64, false); + let call = codegen + .builder + .build_call( + callee, + &[ + tid.into(), midv.into(), argc_val.into(), recv_h.into(), + a[0].into(), tags[0].into(), a[1].into(), tags[1].into(), + a[2].into(), tags[2].into(), a[3].into(), tags[3].into(), + ], + "pinvoke_tagged", + ) + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + let rv = call + .try_as_basic_value() + .left() + .ok_or("invoke3_i64 returned void".to_string())?; + store_invoke_return(codegen, func, vmap, *d, rv)?; + } + return Ok(()); + } + + // Variable length path: build i64 arrays for vals and tags + let n = args.len() as u32; + let arr_ty = i64t.array_type(n); + let vals_arr = entry_builder + .build_alloca(arr_ty, "vals_arr") + .map_err(|e| e.to_string())?; + let tags_arr = entry_builder + .build_alloca(arr_ty, "tags_arr") + .map_err(|e| e.to_string())?; + for (i, vid) in args.iter().enumerate() { + let idx = [ + codegen.context.i32_type().const_zero(), + codegen.context.i32_type().const_int(i as u64, false), + ]; + let gep_v = unsafe { + codegen + .builder + .build_in_bounds_gep(arr_ty, vals_arr, &idx, &format!("v_gep_{}", i)) + .map_err(|e| e.to_string())? + }; + let gep_t = unsafe { + codegen + .builder + .build_in_bounds_gep(arr_ty, tags_arr, &idx, &format!("t_gep_{}", i)) + .map_err(|e| e.to_string())? + }; + let vi = get_i64(codegen, vmap, *vid)?; + let ti = get_tag_const(codegen, vmap, *vid); + codegen.builder.build_store(gep_v, vi).map_err(|e| e.to_string())?; + codegen.builder.build_store(gep_t, ti).map_err(|e| e.to_string())?; + } + let vals_ptr = codegen + .builder + .build_pointer_cast( + vals_arr, + codegen.context.ptr_type(AddressSpace::from(0)), + "vals_arr_i8p", + ) + .map_err(|e| e.to_string())?; + let tags_ptr = codegen + .builder + .build_pointer_cast( + tags_arr, + codegen.context.ptr_type(AddressSpace::from(0)), + "tags_arr_i8p", + ) + .map_err(|e| e.to_string())?; + let fnty = i64t.fn_type( + &[ + i64t.into(), + i64t.into(), + i64t.into(), + i64t.into(), + codegen.context.ptr_type(AddressSpace::from(0)).into(), + codegen.context.ptr_type(AddressSpace::from(0)).into(), + ], + false, + ); + let callee = codegen + .module + .get_function("nyash.plugin.invoke_tagged_v_i64") + .unwrap_or_else(|| codegen.module.add_function("nyash.plugin.invoke_tagged_v_i64", fnty, None)); + let tid = i64t.const_int(type_id as u64, true); + let midv = i64t.const_int(mid as u64, false); + let call = codegen + .builder + .build_call( + callee, + &[tid.into(), midv.into(), argc_val.into(), recv_h.into(), vals_ptr.into(), tags_ptr.into()], + "pinvoke_tagged_v", + ) + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + let rv = call + .try_as_basic_value() + .left() + .ok_or("invoke_v returned void".to_string())?; + store_invoke_return(codegen, func, vmap, *d, rv)?; + } + Ok(()) +} + +fn store_invoke_return<'ctx>( + codegen: &CodegenContext<'ctx>, + func: &MirFunction, + vmap: &mut HashMap>, + dst: ValueId, + rv: inkwell::values::BasicValueEnum<'ctx>, +) -> Result<(), String> { + if let Some(mt) = func.metadata.value_types.get(&dst) { + match mt { + crate::mir::MirType::Integer | crate::mir::MirType::Bool => { + vmap.insert(dst, rv); + } + crate::mir::MirType::String => { + // keep as i64 handle + vmap.insert(dst, rv); + } + crate::mir::MirType::Box(_) + | crate::mir::MirType::Array(_) + | crate::mir::MirType::Future(_) + | crate::mir::MirType::Unknown => { + let h = if let BVE::IntValue(iv) = rv { + iv + } else { + return Err("invoke ret expected i64".to_string()); + }; + let pty = codegen.context.ptr_type(AddressSpace::from(0)); + let ptr = codegen + .builder + .build_int_to_ptr(h, pty, "ret_handle_to_ptr") + .map_err(|e| e.to_string())?; + vmap.insert(dst, ptr.into()); + } + _ => { + vmap.insert(dst, rv); + } + } + } else { + vmap.insert(dst, rv); + } + Ok(()) +} + diff --git a/src/backend/llvm/compiler/codegen/instructions/boxcall/marshal.rs b/src/backend/llvm/compiler/codegen/instructions/boxcall/marshal.rs new file mode 100644 index 00000000..0e0823ce --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/boxcall/marshal.rs @@ -0,0 +1,59 @@ +use std::collections::HashMap; + +use inkwell::{values::BasicValueEnum as BVE, AddressSpace}; +use crate::backend::llvm::compiler::codegen::types; + +use crate::backend::llvm::context::CodegenContext; +use crate::mir::ValueId; + +/// Convert a value to i64 handle/int for plugin invoke (ptr->i64, f64->box->i64) +pub(super) fn get_i64<'ctx>( + codegen: &CodegenContext<'ctx>, + vmap: &HashMap>, + vid: ValueId, +) -> Result, String> { + let i64t = codegen.context.i64_type(); + let bv = *vmap.get(&vid).ok_or("arg missing")?; + match bv { + BVE::IntValue(iv) => Ok(iv), + BVE::PointerValue(pv) => codegen + .builder + .build_ptr_to_int(pv, i64t, "p2i") + .map_err(|e| e.to_string()), + BVE::FloatValue(fv) => { + let fnty = i64t.fn_type(&[codegen.context.f64_type().into()], false); + let callee = codegen + .module + .get_function("nyash.box.from_f64") + .unwrap_or_else(|| codegen.module.add_function("nyash.box.from_f64", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[fv.into()], "arg_f64_to_box") + .map_err(|e| e.to_string())?; + let rv = call + .try_as_basic_value() + .left() + .ok_or("from_f64 returned void".to_string())?; + if let BVE::IntValue(h) = rv { + Ok(h) + } else { + Err("from_f64 ret expected i64".to_string()) + } + } + _ => Err("unsupported arg value".to_string()), + } +} + +/// Classify a value into tag constant i64 (uses types::classify_tag) +pub(super) fn get_tag_const<'ctx>( + codegen: &CodegenContext<'ctx>, + vmap: &HashMap>, + vid: ValueId, +) -> inkwell::values::IntValue<'ctx> { + let i64t = codegen.context.i64_type(); + let tag = vmap + .get(&vid) + .map(|v| types::classify_tag(*v)) + .unwrap_or(3); + i64t.const_int(tag as u64, false) +} diff --git a/src/backend/llvm/compiler/codegen/instructions/maps.rs b/src/backend/llvm/compiler/codegen/instructions/maps.rs new file mode 100644 index 00000000..a6340f70 --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/maps.rs @@ -0,0 +1,169 @@ +use std::collections::HashMap; + +use inkwell::{values::BasicValueEnum as BVE, AddressSpace}; + +use crate::backend::llvm::context::CodegenContext; +use crate::mir::{function::MirFunction, ValueId}; + +/// Handle MapBox fast-paths (core-first). Returns true if handled. +pub(super) fn try_handle_map_method<'ctx>( + codegen: &CodegenContext<'ctx>, + func: &MirFunction, + vmap: &mut HashMap>, + dst: &Option, + box_val: &ValueId, + method: &str, + args: &[ValueId], + recv_h: inkwell::values::IntValue<'ctx>, +) -> Result { + let is_map = matches!(func.metadata.value_types.get(box_val), Some(crate::mir::MirType::Box(b)) if b == "MapBox"); + if !is_map { + return Ok(false); + } + let i64t = codegen.context.i64_type(); + match method { + "size" => { + if !args.is_empty() { + return Err("MapBox.size expects 0 arg".to_string()); + } + let fnty = i64t.fn_type(&[i64t.into()], false); + let callee = codegen + .module + .get_function("nyash.map.size_h") + .unwrap_or_else(|| codegen.module.add_function("nyash.map.size_h", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[recv_h.into()], "msize") + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + let rv = call + .try_as_basic_value() + .left() + .ok_or("map.size_h returned void".to_string())?; + vmap.insert(*d, rv); + } + Ok(true) + } + "has" => { + if args.len() != 1 { + return Err("MapBox.has expects 1 arg".to_string()); + } + let key_v = *vmap.get(&args[0]).ok_or("map.has key missing")?; + let key_i = match key_v { + BVE::IntValue(iv) => iv, + BVE::PointerValue(pv) => codegen + .builder + .build_ptr_to_int(pv, i64t, "key_p2i") + .map_err(|e| e.to_string())?, + _ => return Err("map.has key must be int or handle ptr".to_string()), + }; + let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false); + let callee = codegen + .module + .get_function("nyash.map.has_h") + .unwrap_or_else(|| codegen.module.add_function("nyash.map.has_h", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[recv_h.into(), key_i.into()], "mhas") + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + let rv = call + .try_as_basic_value() + .left() + .ok_or("map.has_h returned void".to_string())?; + vmap.insert(*d, rv); + } + Ok(true) + } + "get" => { + if args.len() != 1 { + return Err("MapBox.get expects 1 arg".to_string()); + } + let key_v = *vmap.get(&args[0]).ok_or("map.get key missing")?; + let call = match key_v { + BVE::IntValue(iv) => { + let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false); + let callee = codegen + .module + .get_function("nyash.map.get_h") + .unwrap_or_else(|| codegen.module.add_function("nyash.map.get_h", fnty, None)); + codegen + .builder + .build_call(callee, &[recv_h.into(), iv.into()], "mget") + .map_err(|e| e.to_string())? + } + BVE::PointerValue(pv) => { + // key: i8* -> i64 handle via from_i8_string (string key) + let fnty_conv = i64t + .fn_type(&[codegen.context.ptr_type(AddressSpace::from(0)).into()], false); + let conv = codegen + .module + .get_function("nyash.box.from_i8_string") + .unwrap_or_else(|| codegen.module.add_function("nyash.box.from_i8_string", fnty_conv, None)); + let kcall = codegen + .builder + .build_call(conv, &[pv.into()], "key_i8_to_handle") + .map_err(|e| e.to_string())?; + let kh = kcall + .try_as_basic_value() + .left() + .ok_or("from_i8_string returned void".to_string())? + .into_int_value(); + let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false); + let callee = codegen + .module + .get_function("nyash.map.get_hh") + .unwrap_or_else(|| codegen.module.add_function("nyash.map.get_hh", fnty, None)); + codegen + .builder + .build_call(callee, &[recv_h.into(), kh.into()], "mget_hh") + .map_err(|e| e.to_string())? + } + _ => return Err("map.get key must be int or pointer".to_string()), + }; + if let Some(d) = dst { + let rv = call + .try_as_basic_value() + .left() + .ok_or("map.get returned void".to_string())?; + vmap.insert(*d, rv); + } + Ok(true) + } + "set" => { + if args.len() != 2 { + return Err("MapBox.set expects 2 args (key, value)".to_string()); + } + let key_v = *vmap.get(&args[0]).ok_or("map.set key missing")?; + let val_v = *vmap.get(&args[1]).ok_or("map.set value missing")?; + let key_i = match key_v { + BVE::IntValue(iv) => iv, + BVE::PointerValue(pv) => codegen + .builder + .build_ptr_to_int(pv, i64t, "key_p2i") + .map_err(|e| e.to_string())?, + _ => return Err("map.set key must be int or handle ptr".to_string()), + }; + let val_i = match val_v { + BVE::IntValue(iv) => iv, + BVE::PointerValue(pv) => codegen + .builder + .build_ptr_to_int(pv, i64t, "val_p2i") + .map_err(|e| e.to_string())?, + _ => return Err("map.set value must be int or handle ptr".to_string()), + }; + let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into()], false); + let callee = codegen + .module + .get_function("nyash.map.set_h") + .unwrap_or_else(|| codegen.module.add_function("nyash.map.set_h", fnty, None)); + let _ = codegen + .builder + .build_call(callee, &[recv_h.into(), key_i.into(), val_i.into()], "mset") + .map_err(|e| e.to_string())?; + Ok(true) + } + _ => Ok(false), + } +} + diff --git a/src/backend/llvm/compiler/codegen/instructions/mod.rs b/src/backend/llvm/compiler/codegen/instructions/mod.rs index 8a3b9b50..e0f38692 100644 --- a/src/backend/llvm/compiler/codegen/instructions/mod.rs +++ b/src/backend/llvm/compiler/codegen/instructions/mod.rs @@ -6,6 +6,10 @@ mod boxcall; mod arith; mod mem; mod consts; +mod strings; +mod arrays; +mod maps; +mod arith_ops; pub(super) use blocks::{create_basic_blocks, precreate_phis}; pub(super) use flow::{emit_branch, emit_jump, emit_return}; @@ -15,4 +19,4 @@ pub(super) use boxcall::lower_boxcall; pub(super) use arith::lower_compare; pub(super) use mem::{lower_load, lower_store}; pub(super) use consts::lower_const; - +pub(super) use arith_ops::{lower_binop, lower_unary}; diff --git a/src/backend/llvm/compiler/codegen/instructions/strings.rs b/src/backend/llvm/compiler/codegen/instructions/strings.rs new file mode 100644 index 00000000..f6edf99a --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/strings.rs @@ -0,0 +1,151 @@ +use std::collections::HashMap; + +use inkwell::{values::BasicValueEnum as BVE, AddressSpace}; + +use crate::backend::llvm::context::CodegenContext; +use crate::mir::{function::MirFunction, ValueId}; + +/// Handle String-specific methods. Returns true if handled, false to let caller continue. +pub(super) fn try_handle_string_method<'ctx>( + codegen: &CodegenContext<'ctx>, + func: &MirFunction, + vmap: &mut HashMap>, + dst: &Option, + box_val: &ValueId, + method: &str, + args: &[ValueId], + recv_v: BVE<'ctx>, +) -> Result { + // Only act if receiver is annotated as String or StringBox + let is_string_recv = match func.metadata.value_types.get(box_val) { + Some(crate::mir::MirType::String) => true, + Some(crate::mir::MirType::Box(b)) if b == "StringBox" => true, + _ => false, + }; + if !is_string_recv { + return Ok(false); + } + + // concat fast-paths + if method == "concat" { + if args.len() != 1 { + return Err("String.concat expects 1 arg".to_string()); + } + let i8p = codegen.context.ptr_type(AddressSpace::from(0)); + let rhs_v = *vmap.get(&args[0]).ok_or("concat arg missing")?; + match (recv_v, rhs_v) { + (BVE::PointerValue(lp), BVE::PointerValue(rp)) => { + let fnty = i8p.fn_type(&[i8p.into(), i8p.into()], false); + let callee = codegen + .module + .get_function("nyash.string.concat_ss") + .unwrap_or_else(|| codegen.module.add_function("nyash.string.concat_ss", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[lp.into(), rp.into()], "concat_ss_call") + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + let rv = call + .try_as_basic_value() + .left() + .ok_or("concat_ss returned void".to_string())?; + vmap.insert(*d, rv); + } + return Ok(true); + } + (BVE::PointerValue(lp), BVE::IntValue(ri)) => { + let i64t = codegen.context.i64_type(); + let fnty = i8p.fn_type(&[i8p.into(), i64t.into()], false); + let callee = codegen + .module + .get_function("nyash.string.concat_si") + .unwrap_or_else(|| codegen.module.add_function("nyash.string.concat_si", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[lp.into(), ri.into()], "concat_si_call") + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + let rv = call + .try_as_basic_value() + .left() + .ok_or("concat_si returned void".to_string())?; + vmap.insert(*d, rv); + } + return Ok(true); + } + (BVE::IntValue(li), BVE::PointerValue(rp)) => { + let i64t = codegen.context.i64_type(); + let fnty = i8p.fn_type(&[i64t.into(), i8p.into()], false); + let callee = codegen + .module + .get_function("nyash.string.concat_is") + .unwrap_or_else(|| codegen.module.add_function("nyash.string.concat_is", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[li.into(), rp.into()], "concat_is_call") + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + let rv = call + .try_as_basic_value() + .left() + .ok_or("concat_is returned void".to_string())?; + vmap.insert(*d, rv); + } + return Ok(true); + } + _ => { /* fall through */ } + } + } + + // length/len fast-path + if method == "length" || method == "len" { + let i64t = codegen.context.i64_type(); + // Ensure handle for receiver (i8* -> i64 via from_i8_string) + let recv_h = match recv_v { + BVE::IntValue(h) => h, + BVE::PointerValue(p) => { + let fnty = i64t.fn_type(&[codegen.context.ptr_type(AddressSpace::from(0)).into()], false); + let callee = codegen + .module + .get_function("nyash.box.from_i8_string") + .unwrap_or_else(|| codegen.module.add_function("nyash.box.from_i8_string", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[p.into()], "str_ptr_to_handle") + .map_err(|e| e.to_string())?; + + let rv = call + .try_as_basic_value() + .left() + .ok_or("from_i8_string returned void".to_string())?; + if let BVE::IntValue(iv) = rv { + iv + } else { + return Err("from_i8_string ret expected i64".to_string()); + } + } + _ => return Err("String.length receiver type unsupported".to_string()), + }; + // call i64 @nyash.string.len_h(i64) + let fnty = i64t.fn_type(&[i64t.into()], false); + let callee = codegen + .module + .get_function("nyash.string.len_h") + .unwrap_or_else(|| codegen.module.add_function("nyash.string.len_h", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[recv_h.into()], "strlen_h") + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + let rv = call + .try_as_basic_value() + .left() + .ok_or("len_h returned void".to_string())?; + vmap.insert(*d, rv); + } + return Ok(true); + } + + Ok(false) +} + diff --git a/src/backend/llvm/compiler/codegen/mod.rs b/src/backend/llvm/compiler/codegen/mod.rs index 02976650..7ec4e3a4 100644 --- a/src/backend/llvm/compiler/codegen/mod.rs +++ b/src/backend/llvm/compiler/codegen/mod.rs @@ -147,157 +147,8 @@ impl LLVMCompiler { let block = func.blocks.get(&bid).unwrap(); for inst in &block.instructions { match inst { - MirInstruction::NewBox { - dst, - box_type, - args, - } => { - match (box_type.as_str(), args.len()) { - ("StringBox", 1) => { - // Pass-through: if arg was built as Const String, its lowering produced i8* already - let av = *vmap.get(&args[0]).ok_or("StringBox arg missing")?; - vmap.insert(*dst, av); - } - ("IntegerBox", 1) => { - // Pass-through integer payload as i64 - let av = *vmap.get(&args[0]).ok_or("IntegerBox arg missing")?; - vmap.insert(*dst, av); - } - // Minimal birth_i64 path for 1-2 args (i64 or handle-as-i64) - (_, n) if n == 1 || n == 2 => { - let type_id = *box_type_ids.get(box_type).unwrap_or(&0); - let i64t = codegen.context.i64_type(); - let fnty = i64t.fn_type( - &[i64t.into(), i64t.into(), i64t.into(), i64t.into()], - false, - ); - let callee = codegen - .module - .get_function("nyash.box.birth_i64") - .unwrap_or_else(|| { - codegen.module.add_function( - "nyash.box.birth_i64", - fnty, - None, - ) - }); - // argc - let argc = i64t.const_int(args.len() as u64, false); - // a1/a2 as i64 - let mut a1 = i64t.const_zero(); - let mut a2 = i64t.const_zero(); - if args.len() >= 1 { - let v = *vmap.get(&args[0]).ok_or("newbox arg[0] missing")?; - a1 = match v { - BasicValueEnum::IntValue(iv) => iv, - BasicValueEnum::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "arg0_p2i").map_err(|e| e.to_string())?, - _ => return Err("newbox arg[0]: unsupported type (expect int or handle ptr)".to_string()), - }; - } - if args.len() >= 2 { - let v = *vmap.get(&args[1]).ok_or("newbox arg[1] missing")?; - a2 = match v { - BasicValueEnum::IntValue(iv) => iv, - BasicValueEnum::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "arg1_p2i").map_err(|e| e.to_string())?, - _ => return Err("newbox arg[1]: unsupported type (expect int or handle ptr)".to_string()), - }; - } - let tid = i64t.const_int(type_id as u64, true); - let call = codegen - .builder - .build_call( - callee, - &[tid.into(), argc.into(), a1.into(), a2.into()], - "birth_i64", - ) - .map_err(|e| e.to_string())?; - let h = call - .try_as_basic_value() - .left() - .ok_or("birth_i64 returned void".to_string())? - .into_int_value(); - let pty = codegen.context.ptr_type(AddressSpace::from(0)); - let ptr = codegen - .builder - .build_int_to_ptr(h, pty, "handle_to_ptr") - .map_err(|e| e.to_string())?; - vmap.insert(*dst, ptr.into()); - } - _ => { - // No-arg birth via central type registry (preferred), - // fallback to env.box.new(name) when type_id is unavailable. - if !args.is_empty() { - return Err( - "NewBox with >2 args not yet supported in LLVM lowering" - .to_string(), - ); - } - let type_id = *box_type_ids.get(box_type).unwrap_or(&0); - // Temporary gate: allow forcing MapBox to plugin path explicitly - let force_plugin_map = std::env::var("NYASH_LLVM_FORCE_PLUGIN_MAP") - .ok() - .as_deref() - == Some("1"); - let i64t = codegen.context.i64_type(); - if type_id != 0 && !(box_type == "MapBox" && !force_plugin_map) { - // declare i64 @nyash.box.birth_h(i64) - let fn_ty = i64t.fn_type(&[i64t.into()], false); - let callee = codegen - .module - .get_function("nyash.box.birth_h") - .unwrap_or_else(|| { - codegen.module.add_function( - "nyash.box.birth_h", - fn_ty, - None, - ) - }); - let tid = i64t.const_int(type_id as u64, true); - let call = codegen - .builder - .build_call(callee, &[tid.into()], "birth") - .map_err(|e| e.to_string())?; - let h_i64 = call - .try_as_basic_value() - .left() - .ok_or("birth_h returned void".to_string())? - .into_int_value(); - let pty = codegen.context.ptr_type(AddressSpace::from(0)); - let ptr = codegen - .builder - .build_int_to_ptr(h_i64, pty, "handle_to_ptr") - .map_err(|e| e.to_string())?; - vmap.insert(*dst, ptr.into()); - } else { - // Fallback: call i64 @nyash.env.box.new(i8*) with type name - let i8p = codegen.context.ptr_type(AddressSpace::from(0)); - let fn_ty = i64t.fn_type(&[i8p.into()], false); - let callee = codegen - .module - .get_function("nyash.env.box.new") - .unwrap_or_else(|| codegen.module.add_function("nyash.env.box.new", fn_ty, None)); - let tn = codegen - .builder - .build_global_string_ptr(box_type.as_str(), "box_type_name") - .map_err(|e| e.to_string())?; - let call = codegen - .builder - .build_call(callee, &[tn.as_pointer_value().into()], "env_box_new") - .map_err(|e| e.to_string())?; - let h_i64 = call - .try_as_basic_value() - .left() - .ok_or("env.box.new returned void".to_string())? - .into_int_value(); - let pty = codegen.context.ptr_type(AddressSpace::from(0)); - let ptr = codegen - .builder - .build_int_to_ptr(h_i64, pty, "handle_to_ptr") - .map_err(|e| e.to_string())?; - vmap.insert(*dst, ptr.into()); - } - } - } + MirInstruction::NewBox { dst, box_type, args } => { + instructions::lower_newbox(&codegen, &mut vmap, *dst, box_type, args, &box_type_ids)?; } MirInstruction::Const { dst, value } => { let bval = match value { @@ -387,40 +238,12 @@ impl LLVMCompiler { instructions::lower_externcall(&codegen, func, &mut vmap, dst, iface_name, method_name, args)?; } MirInstruction::UnaryOp { dst, op, operand } => { - let v = *vmap.get(operand).ok_or("operand missing")?; - let out = match op { - UnaryOp::Neg => { - if let Some(iv) = as_int(v) { - codegen - .builder - .build_int_neg(iv, "ineg") - .map_err(|e| e.to_string())? - .into() - } else if let Some(fv) = as_float(v) { - codegen - .builder - .build_float_neg(fv, "fneg") - .map_err(|e| e.to_string())? - .into() - } else { - return Err("neg on non-number".to_string()); - } - } - UnaryOp::Not | UnaryOp::BitNot => { - if let Some(iv) = as_int(v) { - codegen - .builder - .build_not(iv, "inot") - .map_err(|e| e.to_string())? - .into() - } else { - return Err("not on non-int".to_string()); - } - } - }; - vmap.insert(*dst, out); + instructions::lower_unary(&codegen, &mut vmap, *dst, op, operand)?; } MirInstruction::BinOp { dst, op, lhs, rhs } => { + // Delegated to refactored lowering; keep legacy body for 0-diff but unreachable. + instructions::lower_binop(&codegen, func, &mut vmap, *dst, op, lhs, rhs)?; + continue; let lv = *vmap.get(lhs).ok_or("lhs missing")?; let rv = *vmap.get(rhs).ok_or("rhs missing")?; let mut handled_concat = false;