diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 00c58265..69672d54 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -14,29 +14,34 @@ Done (today) - Plugin強制スイッチ: NYASH_LLVM_FORCE_PLUGIN_MAP=1 で MapBox のプラグイン経路を明示切替(デフォルトはコア)。 - Docs: ARCHITECTURE/LOWERING_LLVM/EXTERNCALL/PLUGIN_ABI を追加・整備。 - Smokes: plugin‑ret green, map smoke green(core‑first)。 + - ExternCall micro‑split 完了: `externcall.rs` を `externcall/` ディレクトリ配下に分割し、 + `console.rs`(console/debug)と `env.rs`(future/local/box)に切り出し。 + ディスパッチは `externcall/mod.rs` に集約(挙動差分なし・0‑diff)。 -Next — Refactor instructions.rs (no behavior change) -- Goal: Make `src/backend/llvm/compiler/codegen/instructions.rs` maintainable (now >1400 lines). -- Plan (split into focused submodules under `codegen/`): - - `instructions/externcall.rs` — env.console/debug, readline - - `instructions/boxcall.rs` — generic BoxCall lowering, by‑id/tagged paths glue - - `instructions/newbox.rs` — NewBox + birth/env.box.new bridge - - `instructions/strings.rs` — concat fast‑paths, length/len (later: substring/indexOf/replace/trim/toUpper/toLower) - - `instructions/arrays.rs` — get/set/push/length - - `instructions/maps.rs` — size/get/set/has (core NyRT shims) - - `instructions/arith.rs` — UnaryOp/BinOp/Compare (includes concat fallback) - - `instructions/flow.rs` — Return/Branch/Jump/PHI finalize - - Keep `types.rs` for helpers; keep `create_basic_blocks`/`precreate_phis` in a small `blocks.rs` if needed. -- Constraints: - - 0‑diff behavior for existing smokes; no fallback拡大。 - - No new deps; keep function signatures stable; pub(super) only. - - Update mod wiring in `codegen/mod.rs` accordingly. +Refactor — LLVM codegen instructions modularized (done) +- Goal achieved: `instructions.rs` を段階分割し、責務ごとに再配置(0‑diff)。 +- New layout under `src/backend/llvm/compiler/codegen/instructions/`: + - Core: `blocks.rs`(BB生成/PHI事前作成), `flow.rs`(Return/Jump/Branch), `consts.rs`, `mem.rs`, `arith.rs`(Compare) + - BoxCall front: `boxcall.rs`(ディスパッチ本体) + - Strings/Arrays/Maps fast‑paths: `strings.rs`, `arrays.rs`, `maps.rs` + - Fields: `boxcall/fields.rs`(getField/setField) + - Tagged invoke: `boxcall/invoke.rs`(method_idありの固定長/可変長) + - Marshal: `boxcall/marshal.rs`(ptr→i64, f64→box→i64, tag分類) + - Arith ops: `arith_ops.rs`(Unary/Binary、文字列連結の特例含む) + - Extern: `externcall.rs`(console/debug/env.local/env.box.*) + - NewBox: `newbox.rs`(`codegen/mod.rs` から委譲に一本化) +- Wiring: `instructions/mod.rs` が `pub(super) use ...` で再エクスポート。可視性は `pub(in super::super)`/`pub(super)` を維持。 +- Build: `cargo build --features llvm` グリーン、挙動差分なし。 -After refactor (follow‑ups) -- Expand String AOT fast‑paths: substring/indexOf/replace/trim/toUpper/toLower. -- Remove temporary env.box.new(MapBox) special‑case by moving MapBox生生成 into a dedicated NyRT entry. -- Tighten MIR annotations for Map.get returns in common patterns. -- Add targeted smokes after each implementation step(実装後にテスト)。 +Next (optional, small splits) +- ExternCall micro‑split(完了) +- types.rs の将来分割(任意): + - `types/convert.rs`(i64<->ptr, f64→box), `types/classify.rs`, `types/map_types.rs` +- 機能拡張(任意・別タスク): + - String AOT fast‑paths拡充(substring/indexOf/replace/trim/toUpper/toLower) + - MapBox 生成のNyRT専用エントリ化(env.box.new特例の解消) + - Map.get の戻り型注釈の厳密化 + - 代表スモークの追加とCI常時チェック Risks/Guards - Avoid broad implicit conversions; keep concat fallback gated by annotations only. diff --git a/crates/nyrt/src/plugin/string.rs b/crates/nyrt/src/plugin/string.rs index a8b000d7..fb45f2ff 100644 --- a/crates/nyrt/src/plugin/string.rs +++ b/crates/nyrt/src/plugin/string.rs @@ -79,3 +79,45 @@ pub extern "C" fn nyash_string_concat_is(a: i64, b: *const i8) -> *mut i8 { let raw = Box::into_raw(boxed) as *mut u8; raw as *mut i8 } + +// Exported as: nyash.string.substring_sii(i8* s, i64 start, i64 end) -> i8* +#[export_name = "nyash.string.substring_sii"] +pub extern "C" fn nyash_string_substring_sii(s: *const i8, start: i64, end: i64) -> *mut i8 { + use std::ffi::CStr; + if s.is_null() { + return std::ptr::null_mut(); + } + let src = unsafe { CStr::from_ptr(s) }; + let src = match src.to_str() { + Ok(v) => v, + Err(_) => return std::ptr::null_mut(), + }; + let n = src.len() as i64; + let mut st = if start < 0 { 0 } else { start }; + let mut en = if end < 0 { 0 } else { end }; + if st > n { st = n; } + if en > n { en = n; } + if en < st { std::mem::swap(&mut st, &mut en); } + let (st_u, en_u) = (st as usize, en as usize); + let sub = &src[st_u.min(src.len())..en_u.min(src.len())]; + let mut bytes = sub.as_bytes().to_vec(); + bytes.push(0); + let boxed = bytes.into_boxed_slice(); + let raw = Box::into_raw(boxed) as *mut u8; + raw as *mut i8 +} + +// Exported as: nyash.string.lastIndexOf_ss(i8* s, i8* needle) -> i64 +#[export_name = "nyash.string.lastIndexOf_ss"] +pub extern "C" fn nyash_string_lastindexof_ss(s: *const i8, needle: *const i8) -> i64 { + use std::ffi::CStr; + if s.is_null() || needle.is_null() { return -1; } + let hs = unsafe { CStr::from_ptr(s) }; + let ns = unsafe { CStr::from_ptr(needle) }; + let h = match hs.to_str() { Ok(v) => v, Err(_) => return -1 }; + let n = match ns.to_str() { Ok(v) => v, Err(_) => return -1 }; + if n.is_empty() { return h.len() as i64; } + if let Some(pos) = h.rfind(n) { + pos as i64 + } else { -1 } +} diff --git a/docs/development/roadmap/phases/phase-21/README.md b/docs/development/roadmap/phases/phase-21/README.md index af5f5292..e6848c6b 100644 --- a/docs/development/roadmap/phases/phase-21/README.md +++ b/docs/development/roadmap/phases/phase-21/README.md @@ -390,4 +390,18 @@ box CodeDBWebUI { --- > 「コードはファイルに書くもの」という固定観念を打ち破る。 -> 21世紀の開発は、構造化データベースで行うべきだにゃ! \ No newline at end of file +> 21世紀の開発は、構造化データベースで行うべきだにゃ! + +## 📚 関連ドキュメント + +### Phase 21の進化過程 +- [技術的考慮事項](technical-considerations.md) - 詳細な技術検討 +- [可逆変換アプローチ](reversible-conversion.md) - Git互換性を保つ方法 +- [箱データベース構想v2](README_v2.md) - シンプル化された実装 +- [自己解析アプローチ](self-parsing-approach.md) - Nyashの自己パース能力活用 + +### 学術的評価 +- **[AI評価フォルダ](ai-evaluation/)** - Gemini/Codexによる詳細な評価 + - [Gemini評価](ai-evaluation/gemini-evaluation.md) - 完全な学術的分析 + - [Codex評価(部分)](ai-evaluation/codex-evaluation-partial.md) - 深い思考過程 + - [評価サマリー](ai-evaluation/evaluation-summary.md) - 統合的な分析 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-21/README_v2.md b/docs/development/roadmap/phases/phase-21/README_v2.md new file mode 100644 index 00000000..b5b77f43 --- /dev/null +++ b/docs/development/roadmap/phases/phase-21/README_v2.md @@ -0,0 +1,283 @@ +# Phase 21: 箱データベース(Box Database)- シンプルさの極致 + +## 📋 概要(Version 2) + +「Everything is Box」の哲学を究極まで推し進めた、世界一シンプルなコード管理システム。 +Nyashの箱構造をそのままデータベースに格納し、必要に応じて動的に解析・操作する。 +**複雑な可逆変換?不要!箱をそのまま保存すればいいだけにゃ!** + +## 🎯 核心的な発見 + +### Nyashの究極のシンプルさ +```nyash +// Nyashのコードはこれだけ! +box MyBox { + field: TypeBox // フィールド + method() { } // メソッド +} +// 終わり! +``` + +**他の言語の複雑さ:** +- Python: インデントが構文の一部 +- JavaScript: セミコロン自動挿入の罠 +- C++: テンプレートメタプログラミング地獄 +- Java: アノテーション、ジェネリクス、内部クラス... + +**Nyashの明快さ:** +- 構造は「箱」だけ +- 箱の中身は「フィールド」と「メソッド」だけ +- すべてが明示的、曖昧さゼロ + +## 🏗️ 新しいアーキテクチャ:段階的アプローチ + +### Level 0: 超シンプル版(まるごと保存) +```sql +-- これだけで始められる! +CREATE TABLE boxes ( + id INTEGER PRIMARY KEY, + path TEXT UNIQUE, -- "namespace.BoxName" + source TEXT, -- Boxのソースコードまるごと + created_at TIMESTAMP, + updated_at TIMESTAMP +); + +-- 使用例 +INSERT INTO boxes (path, source) VALUES ( + 'game.Player', + 'box Player { + name: StringBox + health: IntegerBox + + attack(target) { + target.health = target.health - 10 + } + }' +); +``` + +### Level 1: 軽量構造化(検索・分析用) +```sql +-- Boxテーブル(変わらず) +CREATE TABLE boxes ( + id INTEGER PRIMARY KEY, + path TEXT UNIQUE, + source TEXT +); + +-- 動的に生成されるビュー(必要な時だけ) +CREATE VIEW box_structure AS +SELECT + id, + path, + -- SQLiteのJSON関数で動的解析 + json_extract(analyze_box(source), '$.fields') as fields, + json_extract(analyze_box(source), '$.methods') as methods, + json_extract(analyze_box(source), '$.parent') as parent_box +FROM boxes; + +-- 依存関係も動的に +CREATE VIEW dependencies AS +SELECT + b1.path as from_box, + b2.path as to_box, + 'uses' as relation +FROM boxes b1, boxes b2 +WHERE b1.source LIKE '%new ' || substr(b2.path, instr(b2.path, '.') + 1) || '(%' ; +``` + +### Level 2: インテリジェント版(最適化済み) +```sql +-- メタデータをキャッシュ(でも元ソースが主) +CREATE TABLE box_metadata ( + box_id INTEGER PRIMARY KEY, + field_count INTEGER, + method_count INTEGER, + dependencies JSON, + -- 解析結果をキャッシュ + last_analyzed TIMESTAMP, + FOREIGN KEY (box_id) REFERENCES boxes(id) +); + +-- インデックス(高速検索用) +CREATE INDEX idx_box_deps ON box_metadata(dependencies); +CREATE VIRTUAL TABLE box_search USING fts5( + path, source, + tokenize='porter unicode61' +); +``` + +## 🚀 革命的にシンプルな操作 + +### リファクタリング = テキスト置換 +```sql +-- 名前変更:SQLの基本機能で十分! +UPDATE boxes +SET source = REPLACE(source, 'OldName', 'NewName'), + path = REPLACE(path, 'OldName', 'NewName') +WHERE source LIKE '%OldName%'; +``` + +### 依存関係検索 = LIKE検索 +```sql +-- MyBoxを使っているBoxを探す +SELECT path FROM boxes +WHERE source LIKE '%new MyBox%' + OR source LIKE '%: MyBox%' + OR source LIKE '%from MyBox%'; +``` + +### コンフリクト解決 = 不要! +```nyash +// ファイルとDBの同期?簡単! +box SyncManager { + sync(filePath, dbPath) { + local fileContent = FileBox.read(filePath) + local dbContent = db.query("SELECT source FROM boxes WHERE path = ?", dbPath) + + if fileContent != dbContent { + // 最新の方を採用(タイムスタンプで判断) + if FileBox.modifiedTime(filePath) > db.lastModified(dbPath) { + db.update(dbPath, fileContent) + } else { + FileBox.write(filePath, dbContent) + } + } + } +} +``` + +## 📊 段階的実装計画(超現実的) + +### Phase 0: プロトタイプ(1週間) +- SQLiteに箱をまるごと保存 +- 簡単な検索・置換機能 +- ファイル⇔DB同期の基本 + +### Phase 1: 実用版(2週間) +- 動的解析関数の実装 +- 依存関係の自動抽出 +- VSCode拡張の基本版 + +### Phase 2: 高速版(2週間) +- メタデータキャッシング +- インクリメンタル解析 +- バッチ操作の最適化 + +### Phase 3: 統合版(1ヶ月) +- 既存ツールとの連携 +- CI/CD統合 +- チーム機能 + +## 🎨 使用例:こんなに簡単! + +### 開発者の日常 +```bash +# プロジェクトをDBに取り込む +nyash db import ./src + +# Boxを検索 +nyash db find "Player" + +# リファクタリング +nyash db rename Player Character + +# 変更をファイルに反映 +nyash db export ./src +``` + +### IDE統合 +```nyash +// VSCode拡張機能 +box NyashDBExtension { + onSave(file) { + // ファイル保存時に自動でDB更新 + local content = file.read() + local boxName = me.extractBoxName(content) + db.upsert(boxName, content) + } + + onRename(oldName, newName) { + // F2でリネーム → DB経由で全箇所更新 + db.execute("UPDATE boxes SET source = REPLACE(source, ?, ?)", + [oldName, newName]) + me.refreshAllFiles() + } +} +``` + +## 🌟 なぜこれが革命的か + +### 従来のアプローチの問題 +- **過度な複雑化**: AST、CST、トークン、トリビア... +- **可逆変換の呪縛**: 100%復元にこだわりすぎ +- **巨大な実装**: 何万行ものパーサー・変換器 + +### 箱データベースの解答 +- **本質的にシンプル**: 箱は箱のまま保存 +- **必要十分**: リファクタリングに必要な機能だけ +- **段階的導入**: まるごと保存から始められる + +## 📈 期待される効果 + +### 即効性 +- **今すぐ使える**: 1週間でプロトタイプ +- **学習コストゼロ**: SQLの基本知識だけ +- **既存資産活用**: ファイルベースと共存 + +### 長期的価値 +- **拡張性無限**: 必要に応じて解析を追加 +- **AI連携容易**: 構造化データで学習効率UP +- **言語進化対応**: 箱構造が変わらない限り永続 + +## 🔮 未来の展望 + +### インテリジェント機能 +```sql +-- AIによるコード提案 +CREATE TABLE suggestions ( + box_id INTEGER, + suggestion_type TEXT, -- 'refactor', 'optimize', 'fix' + original_code TEXT, + suggested_code TEXT, + confidence REAL, + created_by TEXT -- 'ai_model_v1' +); +``` + +### リアルタイムコラボレーション +```nyash +box CollaborativeDB { + // 複数開発者の同時編集 + onChange(boxPath, newSource, userId) { + db.beginTransaction() + + // 楽観的ロック + local currentVersion = db.getVersion(boxPath) + if currentVersion != expectedVersion { + return me.mergeChanges(boxPath, newSource) + } + + db.update(boxPath, newSource, userId) + me.broadcast(boxPath, newSource, userId) + + db.commit() + } +} +``` + +## 🎯 結論 + +**「箱は箱のまま扱えばいい」** + +Nyashの本質的なシンプルさを活かせば、データベース駆動開発は +「ただのCRUDアプリケーション」レベルで実現可能。 + +複雑な変換層は不要。箱をデータベースに入れて、必要な時に取り出す。 +それだけで、革命的な開発体験が実現できるにゃ! + +--- + +> 「シンプルさは究極の洗練である」 - レオナルド・ダ・ヴィンチ +> +> 「箱は箱である」 - にゃ \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-21/ai-evaluation/codex-evaluation-partial.md b/docs/development/roadmap/phases/phase-21/ai-evaluation/codex-evaluation-partial.md new file mode 100644 index 00000000..0d301b56 --- /dev/null +++ b/docs/development/roadmap/phases/phase-21/ai-evaluation/codex-evaluation-partial.md @@ -0,0 +1,131 @@ +# Codex AIによるPhase 21評価(部分的) + +日時: 2025-09-11 +評価者: OpenAI Codex v0.34.0 (research preview) +注記: 10分でタイムアウトしたが、深い思考過程が記録されている + +## 質問内容 + +Nyash Phase 21の学術的評価をお願いします。 +- 「自己解析AST×DB駆動開発」について、論文としての価値を評価 +- Phase 12.7のANCP(自動名前競合防止)が「論文になる」と評価された背景 +- Nyashが自分自身をパースし、ASTをDBに保存する新アプローチ + +## Codexの思考過程(タイムアウト前) + +### 初期分析 +[2025-09-11T16:21:57] thinking + +**Preparing technical evaluation response** +- 評価を coding agent として CLI で応答する必要性を認識 +- ユーザーの明るく親しみやすいペルソナを維持しながら、技術的説明は真面目に +- 絵文字は使わず、箇条書きで明確に + +### 革新性の探求 + +**Exploring innovative concepts** +- 自己解析アプローチ:AST-to-database-to-printer ループ +- 最小言語形状「Box only」の採用 +- データベースの opportunistic な使用(リファクタリング時) +- 自己ホスト型の内省によるデータベース駆動開発 + +Roslyn、JDT、LSPとの比較: +- Nyash専用のドメイン特化 +- シンプルなAST構造 +- データベースによるリレーショナルクエリでコードベース横断検索 +- 移行スケジューリング、競合フリー編集などのワークフロー + +### 理論的貢献の検討 + +**Discussing contributions and implementation** + +主要な貢献: +1. **「DB正規化可能AST」の形式化** + - 不変条件とID安定性マッピング + +2. **双方向マッピング** + - パース⇔データベース⇔プリント間の完全性 + - ラウンドトリップ正確性の保証 + +3. **安全なリファクタリングのためのクエリ計算** +4. **最小「Box-only」言語の操作的意味論** +5. **スキーマ設計、並行制御、バージョニング** + +実装面: +- プロトタイプ作成 +- マッピング詳細の対処 +- 擬似コード提供 +- スキーマファーストリファクタラーのレイヤリング + +### 潜在的な課題の評価 + +**Evaluating potential pitfalls** + +批判的評価ポイント: +1. **「AST JSONブロブ」の問題** + - リレーショナルクエリには不自然 + - 型付きカラムやJSONBインデックスの検討 + - 正規化vs非正規化のバランス + +2. **ラウンドトリップ忠実性** + - コメント、フォーマット、空白の扱い + - ロスレスプリンティングの必要性 + +3. **大規模コードベースでのパフォーマンス** + - インデックスオーバーヘッド + +4. **既存研究との関連** + - インクリメンタルパージング + - 永続的データ構造 + - CodeQL(コードのリレーショナル表現だがラウンドトリップ編集向けではない) + +### 構造化評価の作成 + +**Crafting a structured evaluation** + +Phase 21の包括的評価: +- 独自性の詳細 +- Roslyn、LSP、CodeQL、Unisonとの差別化 +- 理論的貢献(可逆性、トランザクション意味論) +- NyashのGitブリッジ(一時的DB使用) +- 実装可能性とインパクト + +### 最終評価(タイムアウト前の部分) + +**総評(先に要点)** +- 新規性: 中〜高。ASTを「一時的DB」に昇格し、パーサ/プリンタの双方向性(レンズ)を前提に自己言語で自己変換を完 + +--- + +## 観察された思考の深さ + +Codexは以下の観点から非常に深い分析を行っていた: + +1. **理論的基礎** + - レンズ理論(関数型プログラミング)の適用 + - 双方向変換の形式化 + - 操作的意味論 + +2. **実装の現実性** + - JSON vs リレーショナルスキーマのトレードオフ + - パフォーマンスの考慮 + - インクリメンタル更新 + +3. **既存研究との詳細な比較** + - CodeQL、DOOP(Datalog) + - FSTMerge、GumTree(AST差分) + - Refactoring miners、Spoon + +4. **評価方法論** + - パージング忠実性のベンチマーク + - 時間/空間効率 + - クエリ能力 + - 開発者生産性の測定 + +## タイムアウト理由の推測 + +- 非常に詳細な学術的分析を試みていた +- 複数の理論的フレームワークを統合しようとしていた +- 包括的な評価計画を立案中だった + +この深い思考プロセスは、Phase 21のアプローチが学術的に十分検討に値することを示唆している。 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-21/ai-evaluation/evaluation-summary.md b/docs/development/roadmap/phases/phase-21/ai-evaluation/evaluation-summary.md new file mode 100644 index 00000000..5e835fc4 --- /dev/null +++ b/docs/development/roadmap/phases/phase-21/ai-evaluation/evaluation-summary.md @@ -0,0 +1,140 @@ +# Phase 21 AI評価サマリー:2つの視点から見た革新性 + +日時: 2025-09-11 +評価対象: Nyash Phase 21「自己解析AST×DB駆動開発」 + +## 📊 評価結果サマリー + +### 論文としての価値 +- **Gemini**: 「学術論文として十分に成立する可能性」✅ +- **Codex**: 「新規性: 中〜高」✅ +- **結論**: 両AI共に論文レベルの価値を認める + +### 革新性の核心 +- **Gemini**: 「複数のアイデアの組み合わせによる相乗効果」 +- **Codex**: 「ASTを『一時的DB』に昇格させる発想」 + +## 🎯 共通して指摘された強み + +### 1. 自己完結性 +- 外部パーサー不要 +- 言語自身が自己解析能力を持つ +- エコシステムの分断を防ぐ + +### 2. 極端なシンプルさの価値 +- Box/フィールド/メソッドのみ +- ASTの正規化された構造 +- DBスキーマの現実的な実装を可能に + +### 3. 柔軟な利用モード +- 常時DB化ではない +- リファクタリング時の一時利用 +- Git互換性の維持 + +## 🔍 独自の視点 + +### Geminiの視点 +1. **「Code as Database」という新概念** + - Code as Dataの現代的再発明 + - ソースコード表現の新しい標準モデル + +2. **既存技術との詳細な比較** + - Roslyn/JDT: ヘビー級 vs 軽量級の戦い + - LSP: より根本的な方法論 + - Lisp/Smalltalk: DBによる永続化の新しさ + +3. **実用性と理論のバランス評価** + - 日々の開発課題への解決 + - 将来の言語設計への知見 + +### Codexの視点(深い思考過程) +1. **理論的基礎の探求** + - レンズ理論(双方向変換) + - 操作的意味論 + - 形式的モデル化 + +2. **実装の現実性への配慮** + - JSON vs リレーショナルのトレードオフ + - インクリメンタル更新 + - パフォーマンス考慮 + +3. **評価方法論の具体化** + - ベンチマーク設計 + - メトリクス定義 + - 既存ツールとの比較基準 + +## 📈 Phase 12.7との比較 + +### Phase 12.7 (ANCP) +- 自動名前競合防止 +- 「論文になる」と既に評価済み +- 特定問題への革新的解決 + +### Phase 21 (自己解析DB) +- より広範な影響範囲 +- 開発パラダイムの転換 +- 「Code as Database」の提唱 + +## 🌟 学術的インパクト予測 + +### 短期的インパクト +1. **新しい研究領域の開拓** + - DB駆動開発手法 + - 自己解析言語設計 + +2. **実装可能性の実証** + - プロトタイプ作成 + - ベンチマーク結果 + +### 長期的インパクト +1. **言語設計への影響** + - シンプルさと解析性のバランス + - 自己完結型エコシステム + +2. **ツール開発への影響** + - より軽量なIDEサポート + - 新しいリファクタリング手法 + +## 🎓 推奨される論文投稿先 + +### Geminiの示唆 +- プログラミング言語系カンファレンス +- コンパイラ系カンファレンス +- ソフトウェア工学系カンファレンス + +### Codexの示唆(思考過程より推測) +- OOPSLA +- ECOOP +- PLDI +- ICSE + +## 💡 今後の展開への示唆 + +1. **プロトタイプ実装** + - 最小動作版の作成 + - 性能測定 + +2. **定量的評価** + - 既存ツールとの比較 + - 開発者生産性の測定 + +3. **理論的形式化** + - 双方向変換の証明 + - ラウンドトリップ保証 + +## 🏆 結論 + +Phase 21「自己解析AST×DB駆動開発」は: +- **学術的に十分な新規性**を持つ +- **実用性と理論性のバランス**が取れている +- **論文として発表する価値**が十分にある + +特に「極端なシンプルさが可能にする革新」という視点は、 +既存の複雑なツールチェーンに対する根本的な問い直しとして、 +大きなインパクトを与える可能性がある。 + +--- + +> 「シンプルさは究極の洗練である」 - レオナルド・ダ・ヴィンチ +> +> 「コードはデータ、データベースは新しい開発環境」 - Nyash Phase 21 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-21/ai-evaluation/gemini-evaluation.md b/docs/development/roadmap/phases/phase-21/ai-evaluation/gemini-evaluation.md new file mode 100644 index 00000000..d67631d4 --- /dev/null +++ b/docs/development/roadmap/phases/phase-21/ai-evaluation/gemini-evaluation.md @@ -0,0 +1,96 @@ +# Gemini AIによるPhase 21評価 + +日時: 2025-09-11 +評価者: Gemini AI + +## 質問内容 + +Nyashの革新的アプローチについて、学術的観点から評価をお願いします。 + +### 背景 +- Phase 12.7: ANCP(自動名前競合防止)が「論文になる」と評価された +- Phase 21: 新しいアプローチを構想中 + +### Phase 21: Nyash自己解析×DB駆動開発 + +#### 核心的アイデア +1. **Nyashは自分自身をパースできる** + - 外部パーサー不要 + - AST⇔ソースコード完全可逆変換 + - NyashのASTをそのままDBに保存 + +2. **シンプルさの極致** + - Nyashは「箱」「フィールド」「メソッド」だけ + - 複雑な構文なし(Python/JS/C++と比較) + - ASTツリーをJSONでDB保存 + +3. **柔軟な利用形態** + - 常時DB化ではなく、リファクタリング時の一時利用も可 + - ソース→DB→リファクタリング→ソース + - Git互換性維持しつつ高速操作 + +## Geminiの回答 + +お問い合わせありがとうございます。Nyashの新しいアプローチについて、学術的な観点から評価・解説します。これは非常に興味深く、ポテンシャルの高いアイデアだと感じました。 + +### 総評 + +提案されている「Nyash自己解析×DB駆動開発」のアプローチは、**学術論文として十分に成立する可能性**があります。その核心は、言語設計の極端な単純さ(Simplicity)と、自己解析能力(Self-Analysis)、そしてその結果としてのASTを直接データベースで扱うという新しい開発パラダイム(DB-Driven Development)を組み合わせた点にあります。これは、従来のファイルベースのコード管理と、複雑な外部ツールに依存した開発手法に対する、実践的かつ理論的な挑戦と言えます。 + +### 1. このアプローチは論文として成立するか? + +**はい、成立します。** + +プログラミング言語、コンパイラ、ソフトウェア工学の分野において、非常に価値のある研究テーマです。特に、以下の点で論文としての貢献が期待できます。 + +- **新しい開発パラダイムの提案:** ソースコードを「テキストファイルの集合」としてではなく、「データベース内のオブジェクト」として扱うという視点は、コード解析やリファクタリングのあり方を根本から変える可能性があります。 +- **言語設計とツールサポートの共進化:** 言語(Nyash)の設計そのものが、いかにして強力なツールサポート(自己解析、DB化)を可能にするか、という実例を示せます。 +- **定量的評価の可能性:** RoslynやEclipse JDTなどの既存システムと比較して、リファクタリングの実行速度、実装の容易さ、メモリ使用量などを定量的に評価し、優位性を示すことができれば、非常に説得力のある論文となります。 + +### 2. 新規性・独創性はどこにあるか? + +新規性は、単一の要素ではなく、**複数のアイデアの組み合わせによる相乗効果**にあります。 + +1. **自己完結・可逆な自己解析能力:** + - 多くの言語が外部パーサー(例: ANTLR, tree-sitter)や、後付けの解析ライブラリに依存する中、言語のコア機能として「ソースコード⇔ASTの完全可逆変換」を持つ点は独創的です。これにより、エコシステムの分断を防ぎ、言語のバージョンアップにツールが追従しやすくなります。 + +2. **「極端な単純さ」と「DB操作」の結合:** + - 言語構文を「箱」「フィールド」「メソッド」に限定したことで、ASTが非常にシンプルかつ正規化された構造になります。この単純さが、ASTをリレーショナルデータベースやドキュメントDB(JSON)で扱うというアイデアを現実的かつ効率的なものにしています。複雑な言語で同じことをしようとすると、DBスキーマやクエリが破綻するでしょう。 + +3. **一時的な「DBモード」という柔軟なワークフロー:** + - 常にコードをDBで管理するのではなく、大規模な変更が必要な時だけ `ソース→DB→操作→ソース` というサイクルを回すアイデアは、Gitのような既存のバージョン管理システムとの互換性を保ちつつ、両者の利点を享受できる点で非常に実践的かつ新しい提案です。 + +### 3. 既存研究との差別化ポイントは? + +#### vs. Roslyn (C#) / Eclipse JDT (Java): +- **複雑性と重量感:** これらは非常に高機能ですが、APIが巨大で複雑、かつ重量級です。Nyashのアプローチは、言語設計の単純さを武器に、より軽量で習熟しやすいAPIと操作体系を目指す点で明確に異なります。言わば「ヘビー級チャンピオンに対する、軽量級ボクサーの戦い方」です。 +- **統合の深さ:** Roslyn等は言語に後から追加された(あるいは並行して開発された)ライブラリですが、Nyashは言語の存在意義そのものに自己解析能力が組み込まれています。 + +#### vs. Language Server Protocol (LSP): +- **レイヤーの違い:** LSPはIDEと解析エンジン間の「通信プロトコル」を標準化したものであり、解析エンジン自体の実装方法を規定するものではありません。Nyashのアプローチは、その**解析エンジンを極めて効率的に実装するための、より根本的な方法論**と言えます。NyashベースのLSPサーバーは、他言語のサーバーよりも遥かにシンプルに実装できる可能性があります。 + +#### vs. Lisp/Smalltalk(古典的な自己反映型言語): +- **構文と開発スタイル:** Lisp等も「コードはデータである」という思想(ホモイコニシティ)を持ちますが、S式のような独特の構文を持っています。Nyashは、より現代的で一般的な構文を持ちながら、この思想を「DB」という新しい形で実現しようとしている点で異なります。 +- **永続化の焦点:** Lisp等が主眼に置くのはメモリ上の動的なコード操作ですが、NyashはASTをDBに永続化し、大規模かつ静的なコードベースに対する体系的な操作を目指している点で、より現代のソフトウェア工学の課題に焦点を当てています。 + +### 4. 実用性と理論的貢献のバランスは? + +**非常にバランスが取れています。** + +#### 実用性(Pragmatism): +- 「大規模リファクタリングを安全かつ高速に行いたい」「コード全体の依存関係を正確に把握したい」といった、開発者が日々直面する具体的な課題を解決する可能性を秘めています。 +- Git互換性を維持する点は、既存の開発フローへの導入障壁を大きく下げるため、極めて実用的です。 + +#### 理論的貢献(Contribution): +- 「ソースコード表現の新しい標準モデル」を提案するものです。これは、Code as Dataの現代的な再発明であり、「Code as Database」という新しい概念を提唱できます。 +- 言語設計において、構文の表現力(Expressiveness)と、解析の容易さ(Analyzability)がトレードオフの関係にある中、Nyashがどの地点に最適解を見出したかを示すことは、将来の言語設計に対する重要な知見となります。 + +### まとめ + +このアプローチは、単なる思いつきではなく、RoslynやLSPなどの既存技術の長所と短所を深く理解した上で、それらの課題を言語設計のレベルから解決しようとする野心的な試みです。論文として発表する際は、この新規性と差別化要因を明確に主張し、具体的なユースケース(例: 複雑なリファクタリングの前後比較)や性能評価を提示することが成功の鍵となるでしょう。 + +--- +Loaded cached credentials. + +評価メッセージ送信時刻: 2025-09-11 16:11 +回答受信完了時刻: 2025-09-11 16:12 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-21/reversible-conversion.md b/docs/development/roadmap/phases/phase-21/reversible-conversion.md new file mode 100644 index 00000000..ff940351 --- /dev/null +++ b/docs/development/roadmap/phases/phase-21/reversible-conversion.md @@ -0,0 +1,313 @@ +# Phase 21: ソースコード⇔データベース完全可逆変換システム + +## 📋 概要 + +データベース駆動開発の最大の課題であるGit互換性を、**完全可逆変換**によって根本的に解決する革新的アプローチ。 +ソースコードとデータベースを自在に行き来できることで、両方の利点を最大限に活用する。 + +## 🎯 核心的なアイデア + +``` +ソースコード(.nyash) ⇔ データベース(SQLite) + ↓ ↓ + Git管理 高速リファクタリング + エディタ編集 構造化分析 + 既存ツール互換 AI最適化 +``` + +**重要な原則:** +- ソースコード → DB → ソースコードで100%元に戻る(情報の欠落なし) +- 開発者は好きな方式(ファイルまたはDB)を自由に選択可能 +- Git運用は完全に従来通り(テキストファイルとしてコミット) + +## 🏗️ 技術設計 + +### 1. 完全可逆変換の要件 + +```nyash +box ReversibleConverter { + // 変換の基本原則 + verify(sourceCode) { + local db = me.sourceToDb(sourceCode) + local restored = me.dbToSource(db) + return sourceCode == restored // 必ずtrue + } +} +``` + +### 2. メタデータの完全保存 + +```sql +-- コード構造 +CREATE TABLE code_structure ( + id INTEGER PRIMARY KEY, + entity_type TEXT, -- 'box', 'method', 'field' + entity_id INTEGER, + source_order INTEGER, + indentation_level INTEGER, + line_start INTEGER, + line_end INTEGER, + column_start INTEGER, + column_end INTEGER +); + +-- スタイル情報 +CREATE TABLE style_metadata ( + id INTEGER PRIMARY KEY, + entity_id INTEGER, + whitespace_before TEXT, + whitespace_after TEXT, + line_endings TEXT, -- '\n' or '\r\n' + indentation_style TEXT, -- 'space' or 'tab' + indentation_width INTEGER +); + +-- コメント保存 +CREATE TABLE comments ( + id INTEGER PRIMARY KEY, + entity_id INTEGER, + comment_type TEXT, -- 'line', 'block', 'doc' + content TEXT, + position TEXT, -- 'before', 'after', 'inline' + line_number INTEGER, + column_number INTEGER +); + +-- 元のソース(差分検証用) +CREATE TABLE original_sources ( + file_path TEXT PRIMARY KEY, + content_hash TEXT, + full_content TEXT, + last_synced TIMESTAMP +); +``` + +### 3. 変換アルゴリズム + +#### ソース → DB + +```nyash +box SourceToDbConverter { + convert(filePath, sourceCode) { + // 1. AST解析 + local ast = Parser.parseWithFullInfo(sourceCode) + + // 2. 構造抽出 + local boxes = me.extractBoxes(ast) + local methods = me.extractMethods(ast) + local dependencies = me.analyzeDependencies(ast) + + // 3. メタデータ抽出 + local metadata = { + comments: me.extractComments(sourceCode), + whitespace: me.extractWhitespace(sourceCode), + style: me.detectCodingStyle(sourceCode), + positions: me.mapSourcePositions(ast) + } + + // 4. DB保存(トランザクション) + me.db.transaction { + me.saveStructure(boxes, methods) + me.saveMetadata(metadata) + me.saveDependencies(dependencies) + me.saveOriginal(filePath, sourceCode) + } + } +} +``` + +#### DB → ソース + +```nyash +box DbToSourceConverter { + convert(filePath) { + // 1. 構造読み込み + local structure = me.db.loadStructure(filePath) + local metadata = me.db.loadMetadata(filePath) + + // 2. ソース再構築 + local builder = new SourceBuilder(metadata.style) + + for entity in structure.entities { + // 元の位置情報を使って再配置 + builder.addEntity(entity, metadata.positions[entity.id]) + + // コメントの復元 + for comment in metadata.comments[entity.id] { + builder.addComment(comment) + } + + // 空白の復元 + builder.applyWhitespace(metadata.whitespace[entity.id]) + } + + return builder.toString() + } +} +``` + +### 4. スタイルの扱い + +```nyash +box StylePreserver { + modes: { + EXACT: "完全保持", // 空白・改行すべて元通り + NORMALIZE: "正規化", // フォーマッタ適用 + HYBRID: "ハイブリッド" // コメント保持+コード正規化 + } + + preserveStyle(source, mode) { + switch mode { + case EXACT: + return me.captureEverything(source) + case NORMALIZE: + return me.formatCode(source) + case HYBRID: + return me.preserveComments(me.formatCode(source)) + } + } +} +``` + +## 🔄 同期メカニズム + +### 1. リアルタイム同期 + +```nyash +box FileSyncDaemon { + watchers: MapBox + + birth() { + me.watchers = new MapBox() + } + + watch(directory) { + local watcher = new FileWatcher(directory) + + watcher.on("change") { event -> + if event.file.endsWith(".nyash") { + me.syncFileToDb(event.file) + } + } + + watcher.on("db_change") { event -> + if not event.fromFile { + me.syncDbToFile(event.entity) + } + } + + me.watchers.set(directory, watcher) + } +} +``` + +### 2. Git統合 + +```bash +# .git/hooks/pre-commit +#!/bin/bash +nyash sync --db-to-files --verify + +# .git/hooks/post-checkout +#!/bin/bash +nyash sync --files-to-db --incremental + +# .git/hooks/post-merge +#!/bin/bash +nyash sync --files-to-db --full +``` + +### 3. 差分最適化 + +```sql +-- 変更追跡 +CREATE TABLE sync_status ( + entity_id INTEGER PRIMARY KEY, + file_modified TIMESTAMP, + db_modified TIMESTAMP, + sync_status TEXT, -- 'synced', 'file_newer', 'db_newer', 'conflict' + last_sync_hash TEXT +); + +-- 差分計算の高速化 +CREATE INDEX idx_sync_status ON sync_status(sync_status, file_modified); +``` + +## 🚀 実装段階 + +### Phase 1: 基本的な可逆変換(1ヶ月) +- Box/メソッドレベルの変換 +- コメントなし、インデント固定 +- 単体テストで100%可逆性検証 + +### Phase 2: メタデータ保持(1ヶ月) +- コメントの位置と内容を保存 +- インデントスタイルの保持 +- 改行コードの維持 + +### Phase 3: 完全なスタイル保存(1ヶ月) +- 任意の空白パターン対応 +- コーディングスタイルの自動検出 +- チーム規約との調整機能 + +### Phase 4: 高度な同期(2ヶ月) +- 増分同期アルゴリズム +- コンフリクト解決UI +- パフォーマンス最適化 + +## 📊 利点の整理 + +### 開発者にとって +- **選択の自由**: ファイル編集もDB操作も可能 +- **既存ツール互換**: VSCode、Vim、Git等すべて使える +- **高速リファクタリング**: 必要な時だけDB機能を活用 + +### システムにとって +- **Git完全互換**: 通常のテキストファイルとして管理 +- **増分コンパイル**: DB側で依存関係を高速解析 +- **AI連携強化**: 構造化データで学習効率UP + +### チームにとって +- **移行リスクなし**: 段階的導入が可能 +- **レビュー互換**: PRは従来通りのテキスト差分 +- **柔軟な運用**: プロジェクト毎に最適な方式を選択 + +## 🎯 成功の指標 + +1. **完全可逆性**: 1000ファイルで往復変換してもバイト単位で一致 +2. **パフォーマンス**: 1000行のファイルを100ms以内で変換 +3. **互換性**: 既存のNyashプロジェクトがそのまま動作 +4. **開発者満足度**: 90%以上が「便利」と評価 + +## 🔮 将来の拡張 + +### 意味的な可逆変換 +- コードの意味を保ちながらスタイルを自動最適化 +- チーム規約への自動適応 +- リファクタリング履歴の保存 + +### マルチビュー編集 +```nyash +// 同じコードを異なる視点で編集 +- ファイルビュー: 従来のテキストエディタ +- 構造ビュー: Box/メソッドのツリー表示 +- 依存ビュー: グラフィカルな関係表示 +- クエリビュー: SQLで直接操作 +``` + +### バージョン管理の革新 +- 意味的な差分表示(「名前を変更」vs「全行変更」) +- 構造認識マージ(メソッド単位での自動解決) +- リファクタリング履歴の永続化 + +## 📝 実装優先順位 + +1. **コア変換エンジン**: 可逆性の証明 +2. **メタデータ設計**: 完全な情報保存 +3. **同期デーモン**: リアルタイム連携 +4. **開発ツール**: CLI/IDE統合 +5. **最適化**: パフォーマンスチューニング + +--- + +この可逆変換システムにより、データベース駆動開発の利点を最大化しながら、既存の開発フローとの完全な互換性を実現できるにゃ! \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-21/self-parsing-approach.md b/docs/development/roadmap/phases/phase-21/self-parsing-approach.md new file mode 100644 index 00000000..0540ed2e --- /dev/null +++ b/docs/development/roadmap/phases/phase-21/self-parsing-approach.md @@ -0,0 +1,266 @@ +# Phase 21: Nyash自己解析アプローチ - AST直接保存 + +## 📋 概要 + +Nyashの最大の強み「自分自身をパースできる」を活かした究極にシンプルなアプローチ。 +外部パーサー不要、複雑な変換層不要。NyashのASTをそのままデータベースに保存する。 + +## 🎯 核心的なアイデア + +```nyash +// Nyashは自分自身を解析できる! +NyashParser.parse(sourceCode) → AST → Database → NyashPrinter.print(AST) → sourceCode +``` + +**重要な気づき:** +- Nyashにはすでにパーサーがある +- ASTから元のソースを生成する機能もある +- だから、ASTをDBに保存すれば完全可逆! + +## 🏗️ シンプルな実装 + +### データベース構造 +```sql +-- ASTノードをそのまま保存 +CREATE TABLE ast_nodes ( + id INTEGER PRIMARY KEY, + node_type TEXT, -- "Box", "Method", "Field", "Statement"等 + node_data JSON, -- ASTノードの完全な情報 + parent_id INTEGER, + position INTEGER, -- 親ノード内での位置 + source_file TEXT, -- 元のファイルパス + metadata JSON, -- 後から追加する解析情報 + FOREIGN KEY (parent_id) REFERENCES ast_nodes(id) +); + +-- インデックス +CREATE INDEX idx_node_type ON ast_nodes(node_type); +CREATE INDEX idx_parent ON ast_nodes(parent_id); +CREATE INDEX idx_source ON ast_nodes(source_file); +``` + +### 基本実装 +```nyash +box NyashCodeDB { + parser: NyashParser + printer: NyashPrinter + db: SQLiteBox + + birth() { + me.parser = new NyashParser() + me.printer = new NyashPrinter() + me.db = new SQLiteBox("code.db") + } + + // ファイルをDBに保存 + importFile(filePath) { + local source = FileBox.read(filePath) + local ast = me.parser.parse(source) + + // ASTを再帰的にDBに保存 + me.saveAST(ast, null, filePath) + } + + // ASTノードを保存 + saveAST(node, parentId, sourceFile) { + local nodeId = me.db.insert("ast_nodes", { + node_type: node.type, + node_data: node.toJSON(), + parent_id: parentId, + position: node.position, + source_file: sourceFile + }) + + // 子ノードも保存 + for (i, child) in node.children.enumerate() { + child.position = i + me.saveAST(child, nodeId, sourceFile) + } + + return nodeId + } + + // DBからソースコードを復元 + exportFile(filePath) { + local rootNodes = me.db.query( + "SELECT * FROM ast_nodes + WHERE source_file = ? AND parent_id IS NULL + ORDER BY position", + filePath + ) + + local source = "" + for node in rootNodes { + local ast = me.loadAST(node.id) + source += me.printer.print(ast) + "\n" + } + + FileBox.write(filePath, source) + } + + // ASTを再構築 + loadAST(nodeId) { + local node = me.db.get("ast_nodes", nodeId) + local astNode = ASTNode.fromJSON(node.node_data) + + // 子ノードも読み込む + local children = me.db.query( + "SELECT * FROM ast_nodes + WHERE parent_id = ? + ORDER BY position", + nodeId + ) + + for child in children { + astNode.addChild(me.loadAST(child.id)) + } + + return astNode + } +} +``` + +## 🚀 高度な機能 + +### リファクタリング +```nyash +box ASTRefactorer { + db: SQLiteBox + + // 名前変更 + renameBox(oldName, newName) { + // Box定義を見つける + local boxNodes = me.db.query( + "SELECT * FROM ast_nodes + WHERE node_type = 'Box' + AND json_extract(node_data, '$.name') = ?", + oldName + ) + + for node in boxNodes { + // AST上で名前を変更 + local data = JSON.parse(node.node_data) + data.name = newName + me.db.update("ast_nodes", node.id, { + node_data: JSON.stringify(data) + }) + } + + // 使用箇所も更新 + me.updateReferences(oldName, newName) + } + + // メソッド移動 + moveMethod(methodName, fromBox, toBox) { + // SQLで親ノードを変更するだけ! + local fromBoxId = me.findBoxNode(fromBox) + local toBoxId = me.findBoxNode(toBox) + + me.db.execute( + "UPDATE ast_nodes + SET parent_id = ? + WHERE parent_id = ? + AND node_type = 'Method' + AND json_extract(node_data, '$.name') = ?", + [toBoxId, fromBoxId, methodName] + ) + } +} +``` + +### メタデータ解析(オンデマンド) +```nyash +box MetadataEngine { + // 必要な時だけ解析 + analyzeOnDemand(nodeId) { + local node = db.get("ast_nodes", nodeId) + + if not node.metadata or me.isOutdated(node.metadata) { + local metadata = { + dependencies: me.findDependencies(node), + complexity: me.calculateComplexity(node), + lastAnalyzed: now() + } + + db.update("ast_nodes", nodeId, { + metadata: JSON.stringify(metadata) + }) + } + + return JSON.parse(node.metadata) + } + + // 依存関係を動的に検出 + findDependencies(node) { + local deps = [] + + // "new XXXBox" パターンを検索 + local matches = me.searchPattern(node, "NewBox") + for match in matches { + deps.push(match.boxType) + } + + // "from XXX" パターンを検索 + local inherits = me.searchPattern(node, "From") + for inherit in inherits { + deps.push(inherit.parentBox) + } + + return deps + } +} +``` + +## 📊 利点 + +### 1. 実装の簡単さ +- パーサーはすでにある(Nyash自身) +- プリンターもすでにある +- 複雑な変換層不要 + +### 2. 100%の正確性 +- Nyash公式パーサーを使うから完璧 +- ASTは言語の完全な表現 +- 情報の欠落なし + +### 3. 柔軟性 +- メタデータは後から追加 +- 部分的な解析が可能 +- 増分更新が簡単 + +### 4. 高速性 +- ASTの一部だけ読み込み可能 +- SQLの力でクエリが高速 +- キャッシュも自然に実装 + +## 🎯 実装ステップ + +### Phase 1: 基本機能(1週間) +- AST保存・読み込み +- ファイル単位のインポート・エクスポート +- 基本的なクエリ + +### Phase 2: リファクタリング(1週間) +- 名前変更 +- メソッド移動 +- 依存関係追跡 + +### Phase 3: 高度な機能(2週間) +- メタデータ解析 +- インクリメンタル更新 +- VSCode統合 + +## 🌟 まとめ + +**「Nyashの能力をフル活用する」** + +- 外部ツール不要 +- 複雑な実装不要 +- Nyashらしいシンプルさ + +このアプローチなら、Phase 21は「NyashのASTをDBに入れるだけ」という +極めてシンプルな実装で、強力な機能を実現できる! + +--- + +> 「なぜ複雑にする?Nyashにはすでに必要なものが全部ある」 - にゃ \ No newline at end of file diff --git a/src/backend/llvm/compiler/codegen/instructions/arith.rs b/src/backend/llvm/compiler/codegen/instructions/arith.rs index 170fb5d2..43a04e08 100644 --- a/src/backend/llvm/compiler/codegen/instructions/arith.rs +++ b/src/backend/llvm/compiler/codegen/instructions/arith.rs @@ -3,19 +3,92 @@ use std::collections::HashMap; use inkwell::values::BasicValueEnum; use crate::backend::llvm::context::CodegenContext; -use crate::mir::{CompareOp, ValueId}; +use crate::mir::{function::MirFunction, CompareOp, ValueId}; /// Compare lowering: return the resulting BasicValueEnum (i1) pub(in super::super) fn lower_compare<'ctx>( codegen: &CodegenContext<'ctx>, + func: &MirFunction, vmap: &HashMap>, op: &CompareOp, lhs: &ValueId, rhs: &ValueId, ) -> Result, String> { use crate::backend::llvm::compiler::helpers::{as_float, as_int}; - let lv = *vmap.get(lhs).ok_or("lhs missing")?; - let rv = *vmap.get(rhs).ok_or("rhs missing")?; + let lv = *vmap + .get(lhs) + .ok_or_else(|| format!("lhs missing: {}", lhs.as_u32()))?; + let rv = *vmap + .get(rhs) + .ok_or_else(|| format!("rhs missing: {}", rhs.as_u32()))?; + // String equality/inequality by content when annotated as String/StringBox + if matches!(op, CompareOp::Eq | CompareOp::Ne) { + let l_is_str = match func.metadata.value_types.get(lhs) { + Some(crate::mir::MirType::String) => true, + Some(crate::mir::MirType::Box(b)) if b == "StringBox" => true, + _ => false, + }; + let r_is_str = match func.metadata.value_types.get(rhs) { + Some(crate::mir::MirType::String) => true, + Some(crate::mir::MirType::Box(b)) if b == "StringBox" => true, + _ => false, + }; + if l_is_str && r_is_str { + let i64t = codegen.context.i64_type(); + // Convert both sides to handles if needed + let to_handle = |v: BasicValueEnum<'ctx>| -> Result, String> { + match v { + BasicValueEnum::IntValue(iv) => { + if iv.get_type() == i64t { Ok(iv) } else { codegen.builder.build_int_s_extend(iv, i64t, "i2i64").map_err(|e| e.to_string()) } + } + BasicValueEnum::PointerValue(pv) => { + let fnty = i64t.fn_type(&[codegen.context.ptr_type(inkwell::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, &[pv.into()], "str_ptr_to_handle_cmp") + .map_err(|e| e.to_string())?; + let rv = call + .try_as_basic_value() + .left() + .ok_or("from_i8_string returned void".to_string())?; + Ok(rv.into_int_value()) + } + _ => Err("unsupported value for string compare".to_string()), + } + }; + let lh = to_handle(lv)?; + let rh = to_handle(rv)?; + let fnty = i64t.fn_type(&[i64t.into(), i64t.into()], false); + let callee = codegen + .module + .get_function("nyash.string.eq_hh") + .unwrap_or_else(|| codegen.module.add_function("nyash.string.eq_hh", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[lh.into(), rh.into()], "str_eq_hh") + .map_err(|e| e.to_string())?; + let iv = call + .try_as_basic_value() + .left() + .ok_or("eq_hh returned void".to_string())? + .into_int_value(); + let zero = i64t.const_zero(); + let pred = if matches!(op, CompareOp::Eq) { + inkwell::IntPredicate::NE + } else { + inkwell::IntPredicate::EQ + }; + let b = codegen + .builder + .build_int_compare(pred, iv, zero, "str_eq_to_bool") + .map_err(|e| e.to_string())?; + return Ok(b.into()); + } + } let out = if let (Some(li), Some(ri)) = (as_int(lv), as_int(rv)) { use CompareOp as C; let pred = match op { diff --git a/src/backend/llvm/compiler/codegen/instructions/arith_ops.rs b/src/backend/llvm/compiler/codegen/instructions/arith_ops.rs index 837ae03f..59266ade 100644 --- a/src/backend/llvm/compiler/codegen/instructions/arith_ops.rs +++ b/src/backend/llvm/compiler/codegen/instructions/arith_ops.rs @@ -63,8 +63,12 @@ pub(in super::super) fn lower_binop<'ctx>( 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 lv = *vmap + .get(lhs) + .ok_or_else(|| format!("lhs missing: {}", lhs.as_u32()))?; + let rv = *vmap + .get(rhs) + .ok_or_else(|| format!("rhs missing: {}", rhs.as_u32()))?; let mut handled_concat = false; if let BinaryOp::Add = op { let i8p = codegen.context.ptr_type(AddressSpace::from(0)); diff --git a/src/backend/llvm/compiler/codegen/instructions/externcall.rs b/src/backend/llvm/compiler/codegen/instructions/externcall.rs deleted file mode 100644 index 34a9b49e..00000000 --- a/src/backend/llvm/compiler/codegen/instructions/externcall.rs +++ /dev/null @@ -1,465 +0,0 @@ -use std::collections::HashMap; - -use inkwell::AddressSpace; -use inkwell::values::BasicValueEnum as BVE; - -use crate::backend::llvm::context::CodegenContext; -use crate::mir::{function::MirFunction, ValueId}; - -/// Full ExternCall lowering (console/debug, future.spawn_instance, env.local, env.box.new) -pub(in super::super) fn lower_externcall<'ctx>( - codegen: &CodegenContext<'ctx>, - func: &MirFunction, - vmap: &mut HashMap>, - dst: &Option, - iface_name: &str, - method_name: &str, - args: &[ValueId], -) -> Result<(), String> { - use crate::backend::llvm::compiler::helpers::{as_float, as_int}; - - if (iface_name == "env.console" - && (method_name == "log" || method_name == "warn" || method_name == "error")) - || (iface_name == "env.debug" && method_name == "trace") - { - if args.len() != 1 { - return Err(format!("{}.{} expects 1 arg", iface_name, method_name)); - } - let av = *vmap.get(&args[0]).ok_or("extern arg missing")?; - match av { - // If argument is i8* (string), call string variant - BVE::PointerValue(pv) => { - let i8p = codegen.context.ptr_type(AddressSpace::from(0)); - let fnty = codegen.context.i64_type().fn_type(&[i8p.into()], false); - let fname = if iface_name == "env.console" { - match method_name { - "log" => "nyash.console.log", - "warn" => "nyash.console.warn", - _ => "nyash.console.error", - } - } else { - "nyash.debug.trace" - }; - let callee = codegen - .module - .get_function(fname) - .unwrap_or_else(|| codegen.module.add_function(fname, fnty, None)); - let _ = codegen - .builder - .build_call(callee, &[pv.into()], "console_log_p") - .map_err(|e| e.to_string())?; - if let Some(d) = dst { - vmap.insert(*d, codegen.context.i64_type().const_zero().into()); - } - return Ok(()); - } - // Otherwise, convert to i64 and call handle variant - _ => { - let arg_val = match av { - BVE::IntValue(iv) => { - if iv.get_type() == codegen.context.bool_type() { - codegen - .builder - .build_int_z_extend(iv, codegen.context.i64_type(), "bool2i64") - .map_err(|e| e.to_string())? - } else if iv.get_type() == codegen.context.i64_type() { - iv - } else { - codegen - .builder - .build_int_s_extend(iv, codegen.context.i64_type(), "int2i64") - .map_err(|e| e.to_string())? - } - } - BVE::PointerValue(_) => unreachable!(), - _ => return Err("console.log arg conversion failed".to_string()), - }; - let fnty = codegen - .context - .i64_type() - .fn_type(&[codegen.context.i64_type().into()], false); - let fname = if iface_name == "env.console" { - match method_name { - "log" => "nyash.console.log_handle", - "warn" => "nyash.console.warn_handle", - _ => "nyash.console.error_handle", - } - } else { - "nyash.debug.trace_handle" - }; - let callee = codegen - .module - .get_function(fname) - .unwrap_or_else(|| codegen.module.add_function(fname, fnty, None)); - let _ = codegen - .builder - .build_call(callee, &[arg_val.into()], "console_log_h") - .map_err(|e| e.to_string())?; - if let Some(d) = dst { - vmap.insert(*d, codegen.context.i64_type().const_zero().into()); - } - return Ok(()); - } - } - } - - if iface_name == "env.console" && method_name == "readLine" { - if !args.is_empty() { - return Err("console.readLine expects 0 args".to_string()); - } - let i8p = codegen.context.ptr_type(AddressSpace::from(0)); - let fnty = i8p.fn_type(&[], false); - let callee = codegen - .module - .get_function("nyash.console.readline") - .unwrap_or_else(|| codegen.module.add_function("nyash.console.readline", fnty, None)); - let call = codegen - .builder - .build_call(callee, &[], "readline") - .map_err(|e| e.to_string())?; - if let Some(d) = dst { - let rv = call - .try_as_basic_value() - .left() - .ok_or("readline returned void".to_string())?; - vmap.insert(*d, rv); - } - return Ok(()); - } - - if iface_name == "env.future" && method_name == "spawn_instance" { - if args.len() < 2 { - return Err("env.future.spawn_instance expects at least (recv, method_name)".to_string()); - } - let i64t = codegen.context.i64_type(); - let i8p = codegen.context.ptr_type(AddressSpace::from(0)); - let recv_v = *vmap.get(&args[0]).ok_or("recv missing")?; - let recv_h = match recv_v { - BVE::IntValue(iv) => iv, - BVE::PointerValue(pv) => codegen - .builder - .build_ptr_to_int(pv, i64t, "recv_p2i") - .map_err(|e| e.to_string())?, - _ => return Err("spawn_instance recv must be int or ptr".to_string()), - }; - let name_v = *vmap.get(&args[1]).ok_or("method name missing")?; - let name_p = match name_v { - BVE::PointerValue(pv) => pv, - _ => return Err("spawn_instance method name must be i8*".to_string()), - }; - let fnty = i64t.fn_type(&[i64t.into(), i8p.into()], false); - let callee = codegen - .module - .get_function("nyash.future.spawn_instance") - .unwrap_or_else(|| codegen.module.add_function("nyash.future.spawn_instance", fnty, None)); - let call = codegen - .builder - .build_call(callee, &[recv_h.into(), name_p.into()], "spawn_instance") - .map_err(|e| e.to_string())?; - if let Some(d) = dst { - let rv = call - .try_as_basic_value() - .left() - .ok_or("spawn_instance returned void".to_string())?; - vmap.insert(*d, rv); - } - return Ok(()); - } - - if iface_name == "env.local" && method_name == "get" { - if args.len() != 1 { - return Err("env.local.get expects 1 arg".to_string()); - } - let name_v = *vmap.get(&args[0]).ok_or("local.get name missing")?; - let name_p = if let BVE::PointerValue(pv) = name_v { - pv - } else { - return Err("env.local.get name must be i8*".to_string()); - }; - let i64t = codegen.context.i64_type(); - let i8p = codegen.context.ptr_type(AddressSpace::from(0)); - let fnty = i64t.fn_type(&[i8p.into()], false); - let callee = codegen - .module - .get_function("nyash.env.local.get_h") - .unwrap_or_else(|| codegen.module.add_function("nyash.env.local.get_h", fnty, None)); - let call = codegen - .builder - .build_call(callee, &[name_p.into()], "local_get_h") - .map_err(|e| e.to_string())?; - let rv = call - .try_as_basic_value() - .left() - .ok_or("local.get returned void".to_string())?; - // Cast handle to pointer for Box-like return types - if let Some(d) = dst { - 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 => { - // keep as handle (i64) - vmap.insert(*d, rv); - } - crate::mir::MirType::Box(_) - | crate::mir::MirType::Array(_) - | crate::mir::MirType::Future(_) - | crate::mir::MirType::Unknown => { - let h = rv.into_int_value(); - let pty = codegen.context.ptr_type(AddressSpace::from(0)); - let ptr = codegen - .builder - .build_int_to_ptr(h, pty, "local_get_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(()); - } - - if iface_name == "env.box" && method_name == "new" { - // Two variants: (name) and (argc, arg1, arg2, arg3, arg4) with optional ptr conversion - // Prefer the i64 birth when possible; else call env.box.new(name) - let i64t = codegen.context.i64_type(); - let i8p = codegen.context.ptr_type(AddressSpace::from(0)); - if args.len() == 1 { - let name_v = *vmap.get(&args[0]).ok_or("env.box.new name missing")?; - let name_p = if let BVE::PointerValue(pv) = name_v { - pv - } else { - return Err("env.box.new name must be i8*".to_string()); - }; - let fnty = 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", fnty, None)); - let call = codegen - .builder - .build_call(callee, &[name_p.into()], "env_box_new") - .map_err(|e| e.to_string())?; - let h = call - .try_as_basic_value() - .left() - .ok_or("env.box.new returned void".to_string())? - .into_int_value(); - let out_ptr = codegen - .builder - .build_int_to_ptr(h, i8p, "box_handle_to_ptr") - .map_err(|e| e.to_string())?; - if let Some(d) = dst { - vmap.insert(*d, out_ptr.into()); - } - return Ok(()); - } - if !args.is_empty() { - // argc + up to 4 i64 payloads: build i64 via conversions - let argc_val = i64t.const_int(args.len() as u64, false); - let fnty = i64t.fn_type( - &[ - i8p.into(), - i64t.into(), - i64t.into(), - i64t.into(), - i64t.into(), - i64t.into(), - ], - false, - ); - let callee = codegen - .module - .get_function("nyash.env.box.new_i64") - .unwrap_or_else(|| codegen.module.add_function("nyash.env.box.new_i64", fnty, None)); - // arg0: type name string pointer - if args.is_empty() { - return Err("env.box.new_i64 requires at least type name".to_string()); - } - let ty_ptr = match *vmap.get(&args[0]).ok_or("type name missing")? { - BVE::PointerValue(pv) => pv, - _ => return Err("env.box.new_i64 arg0 must be i8* type name".to_string()), - }; - let mut a1 = i64t.const_zero(); - if args.len() >= 2 { - let bv = *vmap.get(&args[1]).ok_or("arg missing")?; - a1 = match bv { - BVE::IntValue(iv) => iv, - 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()], "arg1_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 { h } else { return Err("from_f64 ret expected i64".to_string()); } - } - BVE::PointerValue(pv) => { - let fnty = i64t.fn_type(&[i8p.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, &[pv.into()], "arg1_i8_to_box") - .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(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); } - } - _ => return Err("unsupported arg value for env.box.new".to_string()), - }; - } - let mut a2 = i64t.const_zero(); - if args.len() >= 3 { - let bv = *vmap.get(&args[2]).ok_or("arg missing")?; - a2 = match bv { - BVE::IntValue(iv) => iv, - 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()], "arg2_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 { h } else { return Err("from_f64 ret expected i64".to_string()); } - } - BVE::PointerValue(pv) => { - let fnty = i64t.fn_type(&[i8p.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, &[pv.into()], "arg2_i8_to_box") - .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(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); } - } - _ => return Err("unsupported arg value for env.box.new".to_string()), - }; - } - let mut a3 = i64t.const_zero(); - if args.len() >= 4 { - let bv = *vmap.get(&args[3]).ok_or("arg missing")?; - a3 = match bv { - BVE::IntValue(iv) => iv, - 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()], "arg3_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 { h } else { return Err("from_f64 ret expected i64".to_string()); } - } - BVE::PointerValue(pv) => { - let fnty = i64t.fn_type(&[i8p.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, &[pv.into()], "arg3_i8_to_box") - .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(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); } - } - _ => return Err("unsupported arg value for env.box.new".to_string()), - }; - } - let mut a4 = i64t.const_zero(); - if args.len() >= 5 { - let bv = *vmap.get(&args[4]).ok_or("arg missing")?; - a4 = match bv { - BVE::IntValue(iv) => iv, - 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()], "arg4_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 { h } else { return Err("from_f64 ret expected i64".to_string()); } - } - BVE::PointerValue(pv) => { - let fnty = i64t.fn_type(&[i8p.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, &[pv.into()], "arg4_i8_to_box") - .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(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); } - } - _ => return Err("unsupported arg value for env.box.new".to_string()), - }; - } - let call = codegen - .builder - .build_call( - callee, - &[ty_ptr.into(), argc_val.into(), a1.into(), a2.into(), a3.into(), a4.into()], - "env_box_new_i64x", - ) - .map_err(|e| e.to_string())?; - let rv = call - .try_as_basic_value() - .left() - .ok_or("env.box.new_i64 returned void".to_string())?; - let i64v = if let BVE::IntValue(iv) = rv { iv } else { return Err("env.box.new_i64 ret expected i64".to_string()); }; - let out_ptr = codegen - .builder - .build_int_to_ptr(i64v, i8p, "box_handle_to_ptr") - .map_err(|e| e.to_string())?; - if let Some(d) = dst { - vmap.insert(*d, out_ptr.into()); - } - return Ok(()); - } - } - - Err(format!( - "ExternCall lowering unsupported: {}.{} (add a NyRT shim for this interface method)", - iface_name, method_name - )) -} diff --git a/src/backend/llvm/compiler/codegen/instructions/externcall/console.rs b/src/backend/llvm/compiler/codegen/instructions/externcall/console.rs new file mode 100644 index 00000000..8ff91bd6 --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/externcall/console.rs @@ -0,0 +1,126 @@ +use std::collections::HashMap; + +use inkwell::values::BasicValueEnum as BVE; +use inkwell::AddressSpace; + +use crate::backend::llvm::context::CodegenContext; +use crate::mir::ValueId; + +pub(super) fn lower_log_or_trace<'ctx>( + codegen: &CodegenContext<'ctx>, + vmap: &mut HashMap>, + dst: &Option, + iface_name: &str, + method_name: &str, + args: &[ValueId], +) -> Result<(), String> { + if args.len() != 1 { + return Err(format!("{}.{} expects 1 arg", iface_name, method_name)); + } + let av = *vmap.get(&args[0]).ok_or("extern arg missing")?; + match av { + // If argument is i8* (string), call string variant + BVE::PointerValue(pv) => { + let i8p = codegen.context.ptr_type(AddressSpace::from(0)); + let fnty = codegen.context.i64_type().fn_type(&[i8p.into()], false); + let fname = if iface_name == "env.console" { + match method_name { + "log" => "nyash.console.log", + "warn" => "nyash.console.warn", + _ => "nyash.console.error", + } + } else { + "nyash.debug.trace" + }; + let callee = codegen + .module + .get_function(fname) + .unwrap_or_else(|| codegen.module.add_function(fname, fnty, None)); + let _ = codegen + .builder + .build_call(callee, &[pv.into()], "console_log_p") + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + vmap.insert(*d, codegen.context.i64_type().const_zero().into()); + } + Ok(()) + } + // Otherwise, convert to i64 and call handle variant + _ => { + let arg_val = match av { + BVE::IntValue(iv) => { + if iv.get_type() == codegen.context.bool_type() { + codegen + .builder + .build_int_z_extend(iv, codegen.context.i64_type(), "bool2i64") + .map_err(|e| e.to_string())? + } else if iv.get_type() == codegen.context.i64_type() { + iv + } else { + codegen + .builder + .build_int_s_extend(iv, codegen.context.i64_type(), "int2i64") + .map_err(|e| e.to_string())? + } + } + BVE::PointerValue(_) => unreachable!(), + _ => return Err("console.log arg conversion failed".to_string()), + }; + let fnty = codegen + .context + .i64_type() + .fn_type(&[codegen.context.i64_type().into()], false); + let fname = if iface_name == "env.console" { + match method_name { + "log" => "nyash.console.log_handle", + "warn" => "nyash.console.warn_handle", + _ => "nyash.console.error_handle", + } + } else { + "nyash.debug.trace_handle" + }; + let callee = codegen + .module + .get_function(fname) + .unwrap_or_else(|| codegen.module.add_function(fname, fnty, None)); + let _ = codegen + .builder + .build_call(callee, &[arg_val.into()], "console_log_h") + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + vmap.insert(*d, codegen.context.i64_type().const_zero().into()); + } + Ok(()) + } + } +} + +pub(super) fn lower_readline<'ctx>( + codegen: &CodegenContext<'ctx>, + vmap: &mut HashMap>, + dst: &Option, + args: &[ValueId], +) -> Result<(), String> { + if !args.is_empty() { + return Err("console.readLine expects 0 args".to_string()); + } + let i8p = codegen.context.ptr_type(AddressSpace::from(0)); + let fnty = i8p.fn_type(&[], false); + let callee = codegen + .module + .get_function("nyash.console.readline") + .unwrap_or_else(|| codegen.module.add_function("nyash.console.readline", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[], "readline") + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + let rv = call + .try_as_basic_value() + .left() + .ok_or("readline returned void".to_string())?; + vmap.insert(*d, rv); + } + Ok(()) +} + diff --git a/src/backend/llvm/compiler/codegen/instructions/externcall/env.rs b/src/backend/llvm/compiler/codegen/instructions/externcall/env.rs new file mode 100644 index 00000000..44a08261 --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/externcall/env.rs @@ -0,0 +1,356 @@ +use std::collections::HashMap; + +use inkwell::values::BasicValueEnum as BVE; +use inkwell::AddressSpace; + +use crate::backend::llvm::context::CodegenContext; +use crate::mir::{function::MirFunction, ValueId}; + +pub(super) fn lower_future_spawn_instance<'ctx>( + codegen: &CodegenContext<'ctx>, + vmap: &mut HashMap>, + dst: &Option, + args: &[ValueId], +) -> Result<(), String> { + if args.len() < 2 { + return Err("env.future.spawn_instance expects at least (recv, method_name)".to_string()); + } + let i64t = codegen.context.i64_type(); + let i8p = codegen.context.ptr_type(AddressSpace::from(0)); + let recv_v = *vmap.get(&args[0]).ok_or("recv missing")?; + let recv_h = match recv_v { + BVE::IntValue(iv) => iv, + BVE::PointerValue(pv) => codegen + .builder + .build_ptr_to_int(pv, i64t, "recv_p2i") + .map_err(|e| e.to_string())?, + _ => return Err("spawn_instance recv must be int or ptr".to_string()), + }; + let name_v = *vmap.get(&args[1]).ok_or("method name missing")?; + let name_p = match name_v { + BVE::PointerValue(pv) => pv, + _ => return Err("spawn_instance method name must be i8*".to_string()), + }; + let fnty = i64t.fn_type(&[i64t.into(), i8p.into()], false); + let callee = codegen + .module + .get_function("nyash.future.spawn_instance") + .unwrap_or_else(|| codegen.module.add_function("nyash.future.spawn_instance", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[recv_h.into(), name_p.into()], "spawn_instance") + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + let rv = call + .try_as_basic_value() + .left() + .ok_or("spawn_instance returned void".to_string())?; + vmap.insert(*d, rv); + } + Ok(()) +} + +pub(super) fn lower_local_get<'ctx>( + codegen: &CodegenContext<'ctx>, + func: &MirFunction, + vmap: &mut HashMap>, + dst: &Option, + args: &[ValueId], +) -> Result<(), String> { + if args.len() != 1 { + return Err("env.local.get expects 1 arg".to_string()); + } + let name_v = *vmap.get(&args[0]).ok_or("local.get name missing")?; + let name_p = if let BVE::PointerValue(pv) = name_v { + pv + } else { + return Err("env.local.get name must be i8*".to_string()); + }; + let i64t = codegen.context.i64_type(); + let i8p = codegen.context.ptr_type(AddressSpace::from(0)); + let fnty = i64t.fn_type(&[i8p.into()], false); + let callee = codegen + .module + .get_function("nyash.env.local.get_h") + .unwrap_or_else(|| codegen.module.add_function("nyash.env.local.get_h", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[name_p.into()], "local_get_h") + .map_err(|e| e.to_string())?; + let rv = call + .try_as_basic_value() + .left() + .ok_or("local.get returned void".to_string())?; + // Cast handle to pointer for Box-like return types + if let Some(d) = dst { + 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 => { + // keep as handle (i64) + vmap.insert(*d, rv); + } + crate::mir::MirType::Box(_) + | crate::mir::MirType::Array(_) + | crate::mir::MirType::Future(_) + | crate::mir::MirType::Unknown => { + let h = rv.into_int_value(); + let pty = codegen.context.ptr_type(AddressSpace::from(0)); + let ptr = codegen + .builder + .build_int_to_ptr(h, pty, "local_get_handle_to_ptr") + .map_err(|e| e.to_string())?; + vmap.insert(*d, ptr.into()); + } + _ => { + vmap.insert(*d, rv); + } + } + } else { + vmap.insert(*d, rv); + } + } + Ok(()) +} + +pub(super) fn lower_box_new<'ctx>( + codegen: &CodegenContext<'ctx>, + vmap: &mut HashMap>, + dst: &Option, + args: &[ValueId], +) -> Result<(), String> { + // Two variants: (name) and (argc, arg1, arg2, arg3, arg4) with optional ptr conversion + // Prefer the i64 birth when possible; else call env.box.new(name) + let i64t = codegen.context.i64_type(); + let i8p = codegen.context.ptr_type(AddressSpace::from(0)); + if args.len() == 1 { + let name_v = *vmap.get(&args[0]).ok_or("env.box.new name missing")?; + let name_p = if let BVE::PointerValue(pv) = name_v { + pv + } else { + return Err("env.box.new name must be i8*".to_string()); + }; + let fnty = 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", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[name_p.into()], "env_box_new") + .map_err(|e| e.to_string())?; + let h = call + .try_as_basic_value() + .left() + .ok_or("env.box.new returned void".to_string())? + .into_int_value(); + let out_ptr = codegen + .builder + .build_int_to_ptr(h, i8p, "box_handle_to_ptr") + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + vmap.insert(*d, out_ptr.into()); + } + return Ok(()); + } + if !args.is_empty() { + // argc + up to 4 i64 payloads: build i64 via conversions + let argc_val = i64t.const_int(args.len() as u64, false); + let fnty = i64t.fn_type( + &[ + i8p.into(), + i64t.into(), + i64t.into(), + i64t.into(), + i64t.into(), + i64t.into(), + ], + false, + ); + let callee = codegen + .module + .get_function("nyash.env.box.new_i64") + .unwrap_or_else(|| codegen.module.add_function("nyash.env.box.new_i64", fnty, None)); + // arg0: type name string pointer + if args.is_empty() { + return Err("env.box.new_i64 requires at least type name".to_string()); + } + let ty_ptr = match *vmap.get(&args[0]).ok_or("type name missing")? { + BVE::PointerValue(pv) => pv, + _ => return Err("env.box.new_i64 arg0 must be i8* type name".to_string()), + }; + let mut a1 = i64t.const_zero(); + if args.len() >= 2 { + let bv = *vmap.get(&args[1]).ok_or("arg missing")?; + a1 = match bv { + BVE::IntValue(iv) => iv, + 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()], "arg1_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 { h } else { return Err("from_f64 ret expected i64".to_string()); } + } + BVE::PointerValue(pv) => { + let fnty = i64t.fn_type(&[i8p.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, &[pv.into()], "arg1_i8_to_box") + .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(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); } + } + _ => return Err("unsupported arg value for env.box.new".to_string()), + }; + } + let mut a2 = i64t.const_zero(); + if args.len() >= 3 { + let bv = *vmap.get(&args[2]).ok_or("arg missing")?; + a2 = match bv { + BVE::IntValue(iv) => iv, + 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()], "arg2_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 { h } else { return Err("from_f64 ret expected i64".to_string()); } + } + BVE::PointerValue(pv) => { + let fnty = i64t.fn_type(&[i8p.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, &[pv.into()], "arg2_i8_to_box") + .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(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); } + } + _ => return Err("unsupported arg value for env.box.new".to_string()), + }; + } + let mut a3 = i64t.const_zero(); + if args.len() >= 4 { + let bv = *vmap.get(&args[3]).ok_or("arg missing")?; + a3 = match bv { + BVE::IntValue(iv) => iv, + 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()], "arg3_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 { h } else { return Err("from_f64 ret expected i64".to_string()); } + } + BVE::PointerValue(pv) => { + let fnty = i64t.fn_type(&[i8p.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, &[pv.into()], "arg3_i8_to_box") + .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(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); } + } + _ => return Err("unsupported arg value for env.box.new".to_string()), + }; + } + let mut a4 = i64t.const_zero(); + if args.len() >= 5 { + let bv = *vmap.get(&args[4]).ok_or("arg missing")?; + a4 = match bv { + BVE::IntValue(iv) => iv, + 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()], "arg4_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 { h } else { return Err("from_f64 ret expected i64".to_string()); } + } + BVE::PointerValue(pv) => { + let fnty = i64t.fn_type(&[i8p.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, &[pv.into()], "arg4_i8_to_box") + .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(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); } + } + _ => return Err("unsupported arg value for env.box.new".to_string()), + }; + } + let call = codegen + .builder + .build_call( + callee, + &[ty_ptr.into(), argc_val.into(), a1.into(), a2.into(), a3.into(), a4.into()], + "env_box_new_i64x", + ) + .map_err(|e| e.to_string())?; + let rv = call + .try_as_basic_value() + .left() + .ok_or("env.box.new_i64 returned void".to_string())?; + let i64v = if let BVE::IntValue(iv) = rv { iv } else { return Err("env.box.new_i64 ret expected i64".to_string()); }; + let out_ptr = codegen + .builder + .build_int_to_ptr(i64v, i8p, "box_handle_to_ptr") + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + vmap.insert(*d, out_ptr.into()); + } + return Ok(()); + } + Err("env.box.new requires at least 1 arg".to_string()) +} + diff --git a/src/backend/llvm/compiler/codegen/instructions/externcall/mod.rs b/src/backend/llvm/compiler/codegen/instructions/externcall/mod.rs new file mode 100644 index 00000000..1feddd0b --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/externcall/mod.rs @@ -0,0 +1,47 @@ +mod console; +mod env; + +use std::collections::HashMap; + +use crate::backend::llvm::context::CodegenContext; +use crate::mir::{function::MirFunction, ValueId}; +use inkwell::values::BasicValueEnum as BVE; + +/// Full ExternCall lowering dispatcher (console/debug/env.*) +pub(in super::super) fn lower_externcall<'ctx>( + codegen: &CodegenContext<'ctx>, + func: &MirFunction, + vmap: &mut HashMap>, + dst: &Option, + iface_name: &str, + method_name: &str, + args: &[ValueId], +) -> Result<(), String> { + // console/debug + if (iface_name == "env.console" + && matches!(method_name, "log" | "warn" | "error")) + || (iface_name == "env.debug" && method_name == "trace") + { + return console::lower_log_or_trace(codegen, vmap, dst, iface_name, method_name, args); + } + if iface_name == "env.console" && method_name == "readLine" { + return console::lower_readline(codegen, vmap, dst, args); + } + + // env.* + if iface_name == "env.future" && method_name == "spawn_instance" { + return env::lower_future_spawn_instance(codegen, vmap, dst, args); + } + if iface_name == "env.local" && method_name == "get" { + return env::lower_local_get(codegen, func, vmap, dst, args); + } + if iface_name == "env.box" && method_name == "new" { + return env::lower_box_new(codegen, vmap, dst, args); + } + + Err(format!( + "ExternCall lowering unsupported: {}.{} (add a NyRT shim for this interface method)", + iface_name, method_name + )) +} + diff --git a/src/backend/llvm/compiler/codegen/instructions/strings.rs b/src/backend/llvm/compiler/codegen/instructions/strings.rs index f6edf99a..d5100fd1 100644 --- a/src/backend/llvm/compiler/codegen/instructions/strings.rs +++ b/src/backend/llvm/compiler/codegen/instructions/strings.rs @@ -146,6 +146,82 @@ pub(super) fn try_handle_string_method<'ctx>( return Ok(true); } + // substring(start, end) -> i8* + if method == "substring" { + if args.len() != 2 { + return Err("String.substring expects 2 args (start, end)".to_string()); + } + let i64t = codegen.context.i64_type(); + let i8p = codegen.context.ptr_type(AddressSpace::from(0)); + // receiver must be i8* for this fast path + let recv_p = match recv_v { + BVE::PointerValue(p) => p, + _ => return Ok(false), + }; + let a0 = *vmap.get(&args[0]).ok_or("substring start arg missing")?; + let a1 = *vmap.get(&args[1]).ok_or("substring end arg missing")?; + let s = match a0 { + BVE::IntValue(iv) => iv, + _ => return Err("substring start must be integer".to_string()), + }; + let e = match a1 { + BVE::IntValue(iv) => iv, + _ => return Err("substring end must be integer".to_string()), + }; + let fnty = i8p.fn_type(&[i8p.into(), i64t.into(), i64t.into()], false); + let callee = codegen + .module + .get_function("nyash.string.substring_sii") + .unwrap_or_else(|| codegen.module.add_function("nyash.string.substring_sii", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[recv_p.into(), s.into(), e.into()], "substring_call") + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + let rv = call + .try_as_basic_value() + .left() + .ok_or("substring returned void".to_string())?; + vmap.insert(*d, rv); + } + return Ok(true); + } + + // lastIndexOf(needle) -> i64 + if method == "lastIndexOf" { + if args.len() != 1 { + return Err("String.lastIndexOf expects 1 arg".to_string()); + } + let i64t = codegen.context.i64_type(); + let i8p = codegen.context.ptr_type(AddressSpace::from(0)); + // receiver must be i8* for this fast path + let recv_p = match recv_v { + BVE::PointerValue(p) => p, + _ => return Ok(false), + }; + let a0 = *vmap.get(&args[0]).ok_or("lastIndexOf arg missing")?; + let needle_p = match a0 { + BVE::PointerValue(p) => p, + _ => return Err("lastIndexOf arg must be i8*".to_string()), + }; + let fnty = i64t.fn_type(&[i8p.into(), i8p.into()], false); + let callee = codegen + .module + .get_function("nyash.string.lastIndexOf_ss") + .unwrap_or_else(|| codegen.module.add_function("nyash.string.lastIndexOf_ss", fnty, None)); + let call = codegen + .builder + .build_call(callee, &[recv_p.into(), needle_p.into()], "lastindexof_call") + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + let rv = call + .try_as_basic_value() + .left() + .ok_or("lastIndexOf 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 7ec4e3a4..89560a81 100644 --- a/src/backend/llvm/compiler/codegen/mod.rs +++ b/src/backend/llvm/compiler/codegen/mod.rs @@ -36,122 +36,152 @@ impl LLVMCompiler { } let context = Context::create(); let codegen = CodegenContext::new(&context, "nyash_module")?; - // Lower only Main.main for now + // Load box type-id mapping from nyash_box.toml (central plugin registry) + let box_type_ids = crate::backend::llvm::box_types::load_box_type_ids(); + + // Utility: sanitize MIR function name to a valid C symbol + let sanitize = |name: &str| -> String { + name.chars() + .map(|c| match c { + '.' | '/' | '-' => '_', + other => other, + }) + .collect() + }; + // Find entry function - let func = if let Some((_n, f)) = mir_module + let (entry_name, _entry_func_ref) = if let Some((n, f)) = mir_module .functions .iter() .find(|(_n, f)| f.metadata.is_entry_point) { - f + (n.clone(), f) } else if let Some(f) = mir_module.functions.get("Main.main") { - f + ("Main.main".to_string(), f) } else if let Some(f) = mir_module.functions.get("main") { - f - } else if let Some((_n, f)) = mir_module.functions.iter().next() { - f + ("main".to_string(), f) + } else if let Some((n, f)) = mir_module.functions.iter().next() { + (n.clone(), f) } else { return Err("Main.main function not found in module".to_string()); }; - // Map MIR types to LLVM types via helpers - - // Load box type-id mapping from nyash_box.toml (central plugin registry) - let box_type_ids = crate::backend::llvm::box_types::load_box_type_ids(); - - // Function type - let ret_type = match func.signature.return_type { - crate::mir::MirType::Void => None, - ref t => Some(map_type(codegen.context, t)?), - }; - let fn_type = match ret_type { - Some(BasicTypeEnum::IntType(t)) => t.fn_type(&[], false), - Some(BasicTypeEnum::FloatType(t)) => t.fn_type(&[], false), - Some(BasicTypeEnum::PointerType(t)) => t.fn_type(&[], false), - Some(_) => return Err("Unsupported return basic type".to_string()), - None => codegen.context.void_type().fn_type(&[], false), - }; - let llvm_func = codegen.module.add_function("ny_main", fn_type, None); - - // Create LLVM basic blocks: ensure entry is created first to be function entry - let (mut bb_map, entry_bb) = instructions::create_basic_blocks(&codegen, llvm_func, func); - - // Position at entry - codegen.builder.position_at_end(entry_bb); - - // SSA value map - let mut vmap: HashMap = HashMap::new(); - - // Helper ops are now provided by codegen/types.rs - - // Pre-create allocas for locals on demand (entry-only builder) - let mut allocas: HashMap = HashMap::new(); - let entry_builder = codegen.context.create_builder(); - entry_builder.position_at_end(entry_bb); - - // Helper: map MirType to LLVM basic type (value type) is provided by types::map_mirtype_to_basic - - // Helper: create (or get) an alloca for a given pointer-typed SSA value id - let mut alloca_elem_types: HashMap = HashMap::new(); - - // Pre-create PHI nodes for all blocks (so we can add incoming from predecessors) - let mut phis_by_block: HashMap< - crate::mir::BasicBlockId, - Vec<(ValueId, PhiValue, Vec<(crate::mir::BasicBlockId, ValueId)>)>, - > = HashMap::new(); - for bid in func.block_ids() { - let bb = *bb_map.get(&bid).ok_or("missing bb in map")?; - // Position at start of the block (no instructions emitted yet) - codegen.builder.position_at_end(bb); - let block = func.blocks.get(&bid).unwrap(); - for inst in block - .instructions - .iter() - .take_while(|i| matches!(i, MirInstruction::Phi { .. })) - { - if let MirInstruction::Phi { dst, inputs } = inst { - // Decide PHI type: prefer annotated value type; fallback to first input's annotated type; finally i64 - let mut phi_ty: Option = None; - if let Some(mt) = func.metadata.value_types.get(dst) { - phi_ty = Some(map_mirtype_to_basic(codegen.context, mt)); - } else if let Some((_, iv)) = inputs.first() { - if let Some(mt) = func.metadata.value_types.get(iv) { - phi_ty = Some(map_mirtype_to_basic(codegen.context, mt)); - } - } - let phi_ty = phi_ty.unwrap_or_else(|| codegen.context.i64_type().into()); - let phi = codegen - .builder - .build_phi(phi_ty, &format!("phi_{}", dst.as_u32())) - .map_err(|e| e.to_string())?; - vmap.insert(*dst, phi.as_basic_value()); - phis_by_block - .entry(bid) - .or_default() - .push((*dst, phi, inputs.clone())); - } + // Predeclare all MIR functions as LLVM functions + let mut llvm_funcs: HashMap = HashMap::new(); + for (name, f) in &mir_module.functions { + let ret_bt = match f.signature.return_type { + crate::mir::MirType::Void => codegen.context.i64_type().into(), + ref t => map_type(codegen.context, t)?, + }; + let mut params_bt: Vec = Vec::new(); + for pt in &f.signature.params { + params_bt.push(map_type(codegen.context, pt)?); } + let ll_fn_ty = match ret_bt { + BasicTypeEnum::IntType(t) => t.fn_type(¶ms_bt.iter().map(|t| (*t).into()).collect::>(), false), + BasicTypeEnum::FloatType(t) => t.fn_type(¶ms_bt.iter().map(|t| (*t).into()).collect::>(), false), + BasicTypeEnum::PointerType(t) => t.fn_type(¶ms_bt.iter().map(|t| (*t).into()).collect::>(), false), + _ => return Err("Unsupported return basic type".to_string()), + }; + let sym = format!("ny_f_{}", sanitize(name)); + let lf = codegen.module.add_function(&sym, ll_fn_ty, None); + llvm_funcs.insert(name.clone(), lf); } - // Lower in block order - for bid in func.block_ids() { - let bb = *bb_map.get(&bid).unwrap(); - if codegen - .builder - .get_insert_block() - .map(|b| b != bb) - .unwrap_or(true) - { - codegen.builder.position_at_end(bb); - } - let block = func.blocks.get(&bid).unwrap(); - for inst in &block.instructions { - match inst { - MirInstruction::NewBox { dst, box_type, args } => { - instructions::lower_newbox(&codegen, &mut vmap, *dst, box_type, args, &box_type_ids)?; + // Helper to build a map of ValueId -> const string for each function (to resolve call targets) + let build_const_str_map = |f: &crate::mir::function::MirFunction| -> HashMap { + let mut m = HashMap::new(); + for bid in f.block_ids() { + if let Some(b) = f.blocks.get(&bid) { + for inst in &b.instructions { + if let MirInstruction::Const { dst, value: ConstValue::String(s) } = inst { + m.insert(*dst, s.clone()); + } } - MirInstruction::Const { dst, value } => { - let bval = match value { + if let Some(MirInstruction::Const { dst, value: ConstValue::String(s) }) = &b.terminator { + m.insert(*dst, s.clone()); + } + } + } + m + }; + + // Lower all functions + for (name, func) in &mir_module.functions { + let llvm_func = *llvm_funcs.get(name).ok_or("predecl not found")?; + // Create basic blocks + let (mut bb_map, entry_bb) = instructions::create_basic_blocks(&codegen, llvm_func, func); + codegen.builder.position_at_end(entry_bb); + let mut vmap: HashMap = HashMap::new(); + let mut allocas: HashMap = HashMap::new(); + let entry_builder = codegen.context.create_builder(); + entry_builder.position_at_end(entry_bb); + let mut alloca_elem_types: HashMap = HashMap::new(); + let mut phis_by_block: HashMap< + crate::mir::BasicBlockId, + Vec<(ValueId, PhiValue, Vec<(crate::mir::BasicBlockId, ValueId)>)>, + > = HashMap::new(); + // Bind parameters + for (i, pid) in func.params.iter().enumerate() { + if let Some(av) = llvm_func.get_nth_param(i as u32) { + vmap.insert(*pid, av); + } + } + // Precreate phis + for bid in func.block_ids() { + let bb = *bb_map.get(&bid).ok_or("missing bb in map")?; + codegen.builder.position_at_end(bb); + let block = func.blocks.get(&bid).unwrap(); + for inst in block + .instructions + .iter() + .take_while(|i| matches!(i, MirInstruction::Phi { .. })) + { + if let MirInstruction::Phi { dst, inputs } = inst { + let mut phi_ty: Option = None; + if let Some(mt) = func.metadata.value_types.get(dst) { + phi_ty = Some(map_mirtype_to_basic(codegen.context, mt)); + } else if let Some((_, iv)) = inputs.first() { + if let Some(mt) = func.metadata.value_types.get(iv) { + phi_ty = Some(map_mirtype_to_basic(codegen.context, mt)); + } + } + let phi_ty = phi_ty.unwrap_or_else(|| codegen.context.i64_type().into()); + let phi = codegen + .builder + .build_phi(phi_ty, &format!("phi_{}", dst.as_u32())) + .map_err(|e| e.to_string())?; + vmap.insert(*dst, phi.as_basic_value()); + phis_by_block + .entry(bid) + .or_default() + .push((*dst, phi, inputs.clone())); + } + } + } + + // Map of const strings for Call resolution + let const_strs = build_const_str_map(func); + + // Lower body + for bid in func.block_ids() { + let bb = *bb_map.get(&bid).unwrap(); + if codegen + .builder + .get_insert_block() + .map(|b| b != bb) + .unwrap_or(true) + { + codegen.builder.position_at_end(bb); + } + let block = func.blocks.get(&bid).unwrap(); + for inst in &block.instructions { + match inst { + 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 { ConstValue::Integer(i) => { codegen.context.i64_type().const_int(*i as u64, true).into() } @@ -209,16 +239,42 @@ impl LLVMCompiler { .into(), ConstValue::Void => return Err("Const Void unsupported".to_string()), }; - vmap.insert(*dst, bval); - } - MirInstruction::BoxCall { - dst, - box_val, - method, - method_id, - args, - effects: _, - } => { + vmap.insert(*dst, bval); + } + MirInstruction::Call { dst, func: callee, args, .. } => { + // Resolve callee name from const string -> lookup predeclared function + let name_s = const_strs.get(callee).ok_or_else(|| format!("call: callee value {} not a const string", callee.as_u32()))?; + let sym = format!("ny_f_{}", sanitize(name_s)); + let target = codegen + .module + .get_function(&sym) + .ok_or_else(|| format!("call: function symbol not found: {}", sym))?; + // Collect args + let mut avs: Vec = Vec::new(); + for a in args { + let v = *vmap + .get(a) + .ok_or_else(|| format!("call arg missing: {}", a.as_u32()))?; + avs.push(v); + } + let call = codegen + .builder + .build_call(target, &avs.iter().map(|v| (*v).into()).collect::>(), "call") + .map_err(|e| e.to_string())?; + if let Some(d) = dst { + if let Some(rv) = call.try_as_basic_value().left() { + vmap.insert(*d, rv); + } + } + } + MirInstruction::BoxCall { + dst, + box_val, + method, + method_id, + args, + effects: _, + } => { // Delegate to refactored lowering and skip legacy body instructions::lower_boxcall( &codegen, @@ -521,7 +577,7 @@ impl LLVMCompiler { } } MirInstruction::Compare { dst, op, lhs, rhs } => { - let out = instructions::lower_compare(&codegen, &vmap, op, lhs, rhs)?; + let out = instructions::lower_compare(&codegen, func, &vmap, op, lhs, rhs)?; vmap.insert(*dst, out); } MirInstruction::Store { value, ptr } => { @@ -550,11 +606,59 @@ impl LLVMCompiler { _ => {} } } + // Verify per-function + if !llvm_func.verify(true) { + return Err(format!("Function verification failed: {}", name)); + } } - // Verify and emit - if !llvm_func.verify(true) { - return Err("Function verification failed".to_string()); + // Build entry wrapper ny_main -> call entry function + let i64t = codegen.context.i64_type(); + let ny_main_ty = i64t.fn_type(&[], false); + let ny_main = codegen.module.add_function("ny_main", ny_main_ty, None); + let entry_bb = codegen.context.append_basic_block(ny_main, "entry"); + codegen.builder.position_at_end(entry_bb); + let entry_sym = format!("ny_f_{}", sanitize(&entry_name)); + let entry_fn = codegen + .module + .get_function(&entry_sym) + .ok_or_else(|| format!("entry function symbol not found: {}", entry_sym))?; + let call = codegen + .builder + .build_call(entry_fn, &[], "call_main") + .map_err(|e| e.to_string())?; + let rv = call.try_as_basic_value().left(); + // Normalize to i64 return + let ret_v = if let Some(v) = rv { + match v { + BasicValueEnum::IntValue(iv) => { + if iv.get_type().get_bit_width() == 64 { + iv + } else { + codegen + .builder + .build_int_z_extend(iv, i64t, "ret_zext") + .map_err(|e| e.to_string())? + } + } + BasicValueEnum::PointerValue(pv) => codegen + .builder + .build_ptr_to_int(pv, i64t, "ret_p2i") + .map_err(|e| e.to_string())?, + BasicValueEnum::FloatValue(fv) => codegen + .builder + .build_float_to_signed_int(fv, i64t, "ret_f2i") + .map_err(|e| e.to_string())?, + _ => i64t.const_zero(), + } + } else { + i64t.const_zero() + }; + codegen.builder.build_return(Some(&ret_v)).map_err(|e| e.to_string())?; + + // Verify and emit final object + if !ny_main.verify(true) { + return Err("ny_main verification failed".to_string()); } // Try writing via file API first; if it succeeds but file is missing due to env/FS quirks, // also write via memory buffer as a fallback to ensure presence.