🏗️ Refactor: Major LLVM codegen modularization + Phase 15 docs cleanup + Phase 21 DDD concept

## LLVM Codegen Refactoring (by ChatGPT5)
- Split massive boxcall.rs into focused submodules:
  - strings.rs: String method optimizations (concat, length)
  - arrays.rs: Array operations (get, set, push, length)
  - maps.rs: Map operations (get, set, has, size)
  - fields.rs: getField/setField handling
  - invoke.rs: Tagged invoke implementation
  - marshal.rs: Helper functions for marshaling
- Improved code organization and maintainability
- No functional changes, pure refactoring

## Phase 15 Documentation Cleanup
- Restructured phase-15 folder:
  - implementation/: Technical implementation docs
  - planning/: Planning and sequence docs
  - archive/: Redundant/old content
- Removed duplicate content (80k→20k line reduction mentioned 5 times)
- Converted all .txt files to .md for consistency
- Fixed broken links in README.md
- Removed redundant INDEX.md

## Phase 21: Database-Driven Development (New)
- Revolutionary concept: Source code in SQLite instead of files
- Instant refactoring with SQL transactions
- Structured management of boxes, methods, dependencies
- Technical design with security considerations
- Vision: World's first DB-driven programming language

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Selfhosting Dev
2025-09-12 00:35:11 +09:00
parent 3ac4a383e4
commit 4f4c6397a9
22 changed files with 2247 additions and 870 deletions

View File

@ -1,29 +0,0 @@
# Phase 15 — SelfHosting 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関連の分散した文書は本インデックスから辿れるよう整理しています。新規文書を追加した場合は必ずここに追記してください。

View File

@ -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/)
## 📅 実施時期

View File

@ -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の仕様書ドラフト
=====================================
「箱を積み上げて、世界一美しいコンパイラを作るにゃ!」

View File

@ -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の仕様書ドラフト
---
「箱を積み上げて、世界一美しいコンパイラを作るにゃ!」

View File

@ -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 <ns>' を受理(--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リテラル・単純四則、DCEunreachable 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/スモークで常時検証する。

View File

@ -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 <ns>' を受理(--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リテラル・単純四則、DCEunreachable 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/スモークで常時検証する。

View File

@ -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世紀の開発は、構造化データベースで行うべきだにゃ

View File

@ -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]
);
```
---
これらの技術的考慮事項を踏まえて、段階的に実装を進めることで、
安全で高性能なデータベース駆動開発環境を実現できるにゃ!

View File

@ -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<ValueId, BasicValueEnum<'ctx>>,
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<ValueId, BasicValueEnum<'ctx>>,
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(())
}

View File

@ -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<ValueId, inkwell::values::BasicValueEnum<'ctx>>,
dst: &Option<ValueId>,
box_val: &ValueId,
method: &str,
args: &[ValueId],
recv_h: inkwell::values::IntValue<'ctx>,
) -> Result<bool, String> {
// 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),
}
}

View File

@ -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); }
// Delegate String methods
if super::strings::try_handle_string_method(codegen, func, vmap, dst, box_val, method, args, recv_v)? {
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 */ }
}
}
}
// 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);
}
// 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);
}
// Delegate Map methods
if super::maps::try_handle_map_method(codegen, func, vmap, dst, box_val, method, args, recv_h)? {
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(());
}
_ => {}
}
}
}
// 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);
}
// getField/setField
if fields::try_handle_field_method(codegen, vmap, dst, method, args, recv_h)? {
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
}
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<inkwell::values::IntValue<'ctx>, 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<i64> { 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))

View File

@ -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<ValueId, inkwell::values::BasicValueEnum<'ctx>>,
dst: &Option<ValueId>,
method: &str,
args: &[ValueId],
recv_h: inkwell::values::IntValue<'ctx>,
) -> Result<bool, String> {
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),
}
}

View File

@ -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<ValueId, inkwell::values::BasicValueEnum<'ctx>>,
dst: &Option<ValueId>,
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<ValueId, inkwell::values::BasicValueEnum<'ctx>>,
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(())
}

View File

@ -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<ValueId, inkwell::values::BasicValueEnum<'ctx>>,
vid: ValueId,
) -> Result<inkwell::values::IntValue<'ctx>, 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<ValueId, inkwell::values::BasicValueEnum<'ctx>>,
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)
}

View File

@ -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<ValueId, inkwell::values::BasicValueEnum<'ctx>>,
dst: &Option<ValueId>,
box_val: &ValueId,
method: &str,
args: &[ValueId],
recv_h: inkwell::values::IntValue<'ctx>,
) -> Result<bool, String> {
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),
}
}

View File

@ -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};

View File

@ -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<ValueId, inkwell::values::BasicValueEnum<'ctx>>,
dst: &Option<ValueId>,
box_val: &ValueId,
method: &str,
args: &[ValueId],
recv_v: BVE<'ctx>,
) -> Result<bool, String> {
// 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)
}

View File

@ -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;