diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 3844ece5..6da8b3fc 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -20,6 +20,7 @@ Update — 2025-09-12 (LLVM flow + BB naming) Hot Update — 2025-09-12 (quick) - Flow: moved function verify to post‑lower; added terminator fallback only when MIR lacks one +- PHI(sealed): seal_block isolates cast insertion by saving/restoring the insertion point in the predecessor block and wires incoming only for the current predecessor (zero-synth as a last resort, logged). Avoids duplicate incoming and builder state leaks. - Compare: allow ptr↔int comparisons for all ops via ptr→i64 bridge - Strings: substring now accepts i64(handle) receiver (i2p); len/lastIndexOf stable - Arrays: method_id未注入でも get/set/push/length を NyRT 経由で処理 @@ -45,20 +46,20 @@ Next Steps(Sealed SSA 段階導入) 4) グリーン後、Sealed をデフォルトONにする前にスモーク一式で回帰確認。 TODO — Sealed SSA 段階導入(実装タスク) -- [ ] block_end_values 追加(LLVM Lower 内の per-BB 終端スナップショット) +- [x] block_end_values 追加(LLVM Lower 内の per-BB 終端スナップショット) - 追加先: `src/backend/llvm/compiler/codegen/mod.rs` - 形式: `HashMap>` - タイミング: 各BBの命令をすべて Lower した「直後」、終端命令を発行する「直前」に `vmap.clone()` を保存 - 目的: `seal_block` で pred 終端時点の値を安定取得する(現在の vmap 直接参照をやめる) -- [ ] `seal_block` をスナップショット参照に切替 +- [x] `seal_block` をスナップショット参照に切替 - 対象: `src/backend/llvm/compiler/codegen/instructions/flow.rs::seal_block` - 取得: `block_end_values[bid].get(in_vid)` を用いて `val` を取得 - フォールバック: もしスナップショットが無ければ(例外ケース)従来の `vmap` を参照し、警告ログを出す - ログ: `NYASH_LLVM_TRACE_PHI=1` 時に `[PHI] sealed add pred_bb=.. val=.. ty=.. (snapshot)` と明示 -- [ ] 非 sealed 経路の維持(回帰防止) +- [x] 非 sealed 経路の維持(回帰防止) - `emit_jump/emit_branch` は sealed=OFF の時のみ incoming を追加(現状仕様を維持) - sealed=ON の時は incoming 配線は一切行わず、`seal_block` のみで完結 -- [ ] 型整合(coerce)の継続強化 +- [x] 型整合(coerce)の継続強化 - 対象: `src/backend/llvm/compiler/codegen/instructions/flow.rs::coerce_to_type` - 方針: PHI の型は i8* 優先(String/Box/Array を含む場合)。ptr/int 混在は明示 cast で橋渡し - 検討: i1 ブリッジ(bool)の zext/trunc の置き場所は PHI 外側に寄せる(必要時) @@ -95,6 +96,30 @@ Note - フェーズ1では「終端APIと位置ずれの構造化」の最小適用に留め、フォールバック(最終unreachable)を併用 - フェーズ2で with_block の適用範囲を広げ、余剰なガード・分岐フォールバックを削除していく(ソースは小さくシンプルに) +Progress — 2025-09-12(Sealed + RAII 最小導入) +- Sealed: block_end_values(BB内定義のみをフィルタ)を導入し、incoming は pred 終端時点の snapshot から配線 +- Cast 挿入: pred 終端直前(position_before)に限定、終端後挿入を回避 +- BuilderCursor: emit_return/emit_jump/emit_branch を構造化(closed ブロックへの挿入を禁止) +- Call/BoxCall: 実引数を callee の型へ coerce(i2p/p2i/zext/trunc 等) +- Const String: nyash_string_new を entry ブロックで Hoist し、支配性違反を解消 +- Fallback(暫定): PHI 欠落や lhs/rhs missing に対し型ゼロを合成して進行(ログ付) + +Open Issues(要対応) +- Sealed配線: 一部合流で『各predに1 incoming』が未充足(synth で穴埋め中) +- Dominance: ループ/合流で稀に「Instruction does not dominate all uses!」が再出 +- 位置ずれ: Sealed 内の cast 生成が builder の挿入位置に影響する可能性(要隔離) + +Next TODO(優先度順) +1) flow::seal_block の挿入位置を完全隔離 + - 専用Builder or Cursor.with_block で pred 終端直前に cast を挿入(メインbuilder位置を汚さない) +2) preds_by_block を構築し、PHI incoming を実CFGの pred へ正規リマップ + - snapshot から in_vid を取得し、pred 数ぶんを網羅(synth は最終手段) + - 検証: incoming=pred数 を assert/ログ +3) with_block の適用拡大(entry_builder/配列alloca等のホットスポット) + - 位置ずれ温床を解消 → 余剰ガード/フォールバックを削除(コード縮小) +4) 回帰: Sealed=ON/OFF の一致確認(dep_tree_min_string ほか代表) + - NYASH_LLVM_TRACE_PHI=1 で配線ログ確認、ゼロ合成が消えることを目標 + Plan — PHI/SSA Hardening (Sealed SSA) - Sealed SSA 入れ替え(安全に段階導入) - Blockごとに `sealed: bool` と `incomplete_phis: Map` を保持 diff --git a/docs/development/roadmap/phases/phase-15/README.md b/docs/development/roadmap/phases/phase-15/README.md index 283a2baf..03808983 100644 --- a/docs/development/roadmap/phases/phase-15/README.md +++ b/docs/development/roadmap/phases/phase-15/README.md @@ -14,6 +14,25 @@ MIR 13命令の美しさを最大限に活かし、外部コンパイラ依存 4. **エコシステムの自立**: Nyashだけで完結する開発環境 5. **劇的なコード圧縮**: 75%削減で保守性・可読性の革命 +## 🚀 実装戦略(2025年9月更新) + +### Phase 15.2: LLVM層の独立化(実装中) +- nyash-llvm-compiler crateの分離 +- MIR JSON/バイナリ入力 → ネイティブEXE出力 +- 独立したツールとして配布可能 + +### Phase 15.3: Nyashコンパイラ実装 +- NyashでNyashパーサー実装 +- AST→MIR変換 +- ブートストラップでセルフホスティング達成! + +### Phase 15.4: VM層のNyash化(革新的) +- MIR解釈エンジンをNyashで実装 +- コンパイル不要の即座実行 +- デバッグ・開発効率の劇的向上 + +詳細:[セルフホスティング戦略 2025年9月版](implementation/self-hosting-strategy-2025-09.md) + ## 📊 主要成果物 ### コンパイラコンポーネント @@ -207,10 +226,11 @@ ny_free_buf(buffer) ## 📅 実施時期 -- **開始条件**: Phase 10-14完了後 -- **推定開始**: 2026年前半 -- **推定期間**: 6-8ヶ月 -- **早期着手**: YAML自動生成は今すぐ開始可能 +- **現在進行中**(2025年9月) +- **Phase 15.2**: LLVM独立化(実装中) +- **Phase 15.3**: Nyashコンパイラ(2025年後半) +- **Phase 15.4**: VM層Nyash化(2026年前半) +- **Phase 15.5**: ABI移行(LLVM完成後) ## 💡 期待される成果 diff --git a/docs/development/roadmap/phases/phase-15/ROADMAP.md b/docs/development/roadmap/phases/phase-15/ROADMAP.md index 01be697a..498f5d04 100644 --- a/docs/development/roadmap/phases/phase-15/ROADMAP.md +++ b/docs/development/roadmap/phases/phase-15/ROADMAP.md @@ -18,23 +18,29 @@ This roadmap is a living checklist to advance Phase 15 with small, safe boxes. U ## Next (small boxes) -1) Standard Ny std impl (P0→実体化) - - Implement P0 methods for string/array/map in Nyash (keep NyRT primitives minimal) - - Enable via `nyash.toml` `[ny_plugins]` (opt‑in); extend `tools/jit_smoke.sh` -2) Ny compiler MVP (Ny→MIR on JIT path) - - Ny tokenizer + recursive‑descent parser (current subset) in Ny; drive existing MIR builder - - Flag path: `NYASH_USE_NY_COMPILER=1` to switch rust→ny compiler; rust parser as fallback - - Add apps/selfhost-compiler/ and minimal smokes -3) Bootstrap loop (c0→c1→c1’) - - Use existing trace/hash harness to compare parity; add optional CI gate -4) Plugins CI split (継続) - - Core always‑on (JIT, plugins disabled); Plugins as optional job (strict off by default) -5) LLVM Native EXE Generation +1) LLVM Native EXE Generation (Phase 15.2) 🚀 - LLVM backend object → executable pipeline completion - Separate `nyash-llvm-compiler` crate (reduce main build weight) - Input: MIR (JSON/binary) → Output: native executable - Link with nyrt runtime (static/dynamic options) - Integration: `nyash --backend llvm --emit exe program.nyash -o program.exe` +2) Standard Ny std impl (P0→実体化) + - Implement P0 methods for string/array/map in Nyash (keep NyRT primitives minimal) + - Enable via `nyash.toml` `[ny_plugins]` (opt‑in); extend `tools/jit_smoke.sh` +3) Ny compiler MVP (Ny→MIR on JIT path) (Phase 15.3) 🎯 + - Ny tokenizer + recursive‑descent parser (current subset) in Ny; drive existing MIR builder + - Flag path: `NYASH_USE_NY_COMPILER=1` to switch rust→ny compiler; rust parser as fallback + - Add apps/selfhost-compiler/ and minimal smokes +4) Bootstrap loop (c0→c1→c1') + - Use existing trace/hash harness to compare parity; add optional CI gate + - **This achieves self-hosting!** Nyash compiles Nyash +5) VM Layer in Nyash (Phase 15.4) ⚡ + - Implement MIR interpreter in Nyash (13 core instructions) + - BoxCall/ExternCall bridge to existing infrastructure + - Optional LLVM JIT acceleration for hot paths + - Enable instant execution without compilation +6) Plugins CI split (継続) + - Core always‑on (JIT, plugins disabled); Plugins as optional job (strict off by default) ## Later (incremental) diff --git a/docs/development/roadmap/phases/phase-15/implementation/self-hosting-strategy-2025-09.md b/docs/development/roadmap/phases/phase-15/implementation/self-hosting-strategy-2025-09.md new file mode 100644 index 00000000..461ffbdf --- /dev/null +++ b/docs/development/roadmap/phases/phase-15/implementation/self-hosting-strategy-2025-09.md @@ -0,0 +1,132 @@ +# Phase 15 セルフホスティング戦略 2025年9月版 + +## 🎯 セルフホスティングの段階的実現戦略 + +### 現在地 +- ✅ v0 Nyパーサー(Ny→JSON IR)完成 +- ✅ MIR生成基盤あり(Rust実装) +- 🚧 LLVM層改善中(ChatGPT5協力) +- 📅 NyコンパイラMVP計画中 + +### 君の提案の妥当性検証 + +## 📊 セルフホスティングの段階 + +### Stage 1: LLVM層の独立(最優先)✅ +``` +現在: Rustモノリス → MIR → LLVM → オブジェクト +提案: Rustモノリス → MIR(JSON) → [LLVM EXE] → ネイティブEXE +``` + +**実装詳細**: +1. `nyash-llvm-compiler` crateを分離 +2. 入力:MIR(JSON/バイナリ) +3. 出力:ネイティブ実行ファイル +4. nyrtランタイムとのリンク + +**メリット**: +- ビルド時間短縮(Rust側は軽量化) +- 独立したツールとして配布可能 +- パイプライン明確化 + +### Stage 2: Nyashコンパイラ実装(現在計画中)✅ +``` +現在: Rustパーサー → MIR +提案: Nyashコンパイラ → AST/JSON → MIR生成層 +``` + +**実装詳細**: +1. Nyashで再帰下降パーサー実装 +2. AST構造をJSONで出力 +3. 既存のMIR生成層に接続 +4. `NYASH_USE_NY_COMPILER=1`で切替 + +**これでセルフホスティング達成!** +- Nyashで書いたコンパイラがNyashをコンパイル +- Rustコンパイラは不要に + +### Stage 3: VM層のNyash実装(革新的)⚡ +``` +現在: Rust VM → MIR解釈 +提案: Nyash VM → MIR解釈 → (必要時LLVM呼び出し) +``` + +**実装詳細**: +```nyash +box NyashVMBox { + mir_module: MIRModuleBox + pc_stack: ArrayBox + value_stack: ArrayBox + frame_stack: ArrayBox + + execute(mir_json) { + me.mir_module = MIRModuleBox.parse(mir_json) + me.runFunction("main") + } + + runFunction(name) { + local func = me.mir_module.getFunction(name) + local frame = new FrameBox(func) + me.frame_stack.push(frame) + + loop(frame.pc < func.instructions.length()) { + local inst = func.instructions[frame.pc] + me.executeInstruction(inst, frame) + frame.pc = frame.pc + 1 + } + } +} +``` + +**メリット**: +- **コンパイル不要**で即実行 +- VMロジックを動的に変更可能 +- デバッグ・実験が容易 + +## 🚀 実現順序の提案 + +### Phase 15.2: LLVM独立化 +1. LLVM層をcrateに分離 +2. MIR JSONインターフェース確立 +3. スタンドアロンEXE生成 + +### Phase 15.3: Nyashコンパイラ +1. パーサー実装(Nyashで) +2. AST→MIRブリッジ +3. ブートストラップテスト + +### Phase 15.4: VM層Nyash化 +1. MIR解釈エンジン(基本13命令) +2. BoxCall/ExternCallブリッジ +3. パフォーマンス最適化(JIT連携) + +## 💡 ABI移行タイミング + +**LLVM独立化完了後が最適**理由: +1. インターフェース確定後に最適化 +2. 独立EXEならABI変更の影響限定的 +3. パフォーマンス測定してから判断 + +## 📋 検証結果 + +**君の提案は正しい!** + +1. **LLVM EXE独立** → MIR JSONで疎結合 +2. **Nyashコンパイラ** → AST/JSONでMIR生成 +3. **セルフホスト完了** → Rustコンパイラ不要 +4. **VM層Nyash化** → 究極の柔軟性 + +この順序なら: +- 段階的に実現可能 +- 各段階で動作確認 +- リスク最小化 +- 最終的に完全セルフホスト + +## 🎯 次のアクション + +1. **LLVM crateの設計開始** +2. **MIR JSONスキーマ確定** +3. **Nyパーサー拡張計画** +4. **VMプロトタイプ設計** + +これが現実的で革新的なロードマップにゃ! \ No newline at end of file diff --git a/src/backend/llvm/compiler/codegen/instructions/builder_cursor.rs b/src/backend/llvm/compiler/codegen/instructions/builder_cursor.rs index 59477829..576152eb 100644 --- a/src/backend/llvm/compiler/codegen/instructions/builder_cursor.rs +++ b/src/backend/llvm/compiler/codegen/instructions/builder_cursor.rs @@ -26,6 +26,8 @@ impl<'ctx, 'b> BuilderCursor<'ctx, 'b> { let prev_bb = self.cur_llbb; // Preserve previous closed state let prev_closed = prev_bid.and_then(|id| self.closed_by_bid.get(&id).copied()); + // Preserve target block closed state and restore after + let tgt_closed_before = self.closed_by_bid.get(&bid).copied(); self.at_end(bid, bb); let r = body(self); @@ -39,6 +41,13 @@ impl<'ctx, 'b> BuilderCursor<'ctx, 'b> { if let (Some(pid), Some(closed)) = (prev_bid, prev_closed) { self.closed_by_bid.insert(pid, closed); } + if let Some(closed) = tgt_closed_before { + self.closed_by_bid.insert(bid, closed); + } else { + // If previously unknown, keep it marked as closed if a terminator exists + let has_term = unsafe { bb.get_terminator() }.is_some(); + self.closed_by_bid.insert(bid, has_term); + } r } diff --git a/src/backend/llvm/compiler/codegen/instructions/flow.rs b/src/backend/llvm/compiler/codegen/instructions/flow.rs index c8842245..554c01a1 100644 --- a/src/backend/llvm/compiler/codegen/instructions/flow.rs +++ b/src/backend/llvm/compiler/codegen/instructions/flow.rs @@ -250,8 +250,9 @@ fn coerce_to_type<'ctx>( } /// Sealed-SSA style: when a block is finalized, add PHI incoming for all successor blocks. -pub(in super::super) fn seal_block<'ctx>( +pub(in super::super) fn seal_block<'ctx, 'b>( codegen: &CodegenContext<'ctx>, + cursor: &mut BuilderCursor<'ctx, 'b>, func: &MirFunction, bid: BasicBlockId, succs: &HashMap>, @@ -269,11 +270,12 @@ pub(in super::super) fn seal_block<'ctx>( for sb in slist { if let Some(pl) = phis_by_block.get(sb) { for (_dst, phi, inputs) in pl { - if let Some((_, in_vid)) = inputs.iter().find(|(pred, _)| pred == &bid) { + // Handle only the current predecessor (bid) + if let Some((_, in_vid)) = inputs.iter().find(|(p, _)| p == &bid) { // Prefer the predecessor's block-end snapshot; fall back to current vmap let snap_opt = block_end_values - .get(&bid) - .and_then(|m| m.get(in_vid).copied()); + .get(&bid) + .and_then(|m| m.get(in_vid).copied()); let mut val = if let Some(sv) = snap_opt { sv } else { @@ -298,72 +300,69 @@ pub(in super::super) fn seal_block<'ctx>( BT::FloatType(ft) => ft.const_zero().into(), BT::PointerType(pt) => pt.const_zero().into(), _ => return Err(format!( - "phi incoming (seal) missing: pred={} succ_bb={} in_vid={} (no snapshot)", - bid.as_u32(), sb.as_u32(), in_vid.as_u32() - )), + "phi incoming (seal) missing: pred={} succ_bb={} in_vid={} (no snapshot)", + bid.as_u32(), sb.as_u32(), in_vid.as_u32() + )), } } }; - // Ensure any required casts are inserted BEFORE the predecessor's terminator - // Save and restore current insertion point around coercion - let saved_block = codegen.builder.get_insert_block(); - if let Some(pred_llbb) = bb_map.get(&bid) { - let term = unsafe { pred_llbb.get_terminator() }; - if let Some(t) = term { - // Insert casts right before the terminator of predecessor - codegen.builder.position_before(&t); - } else { - codegen.builder.position_at_end(*pred_llbb); + // Insert any required casts in the predecessor block, right before its terminator + let saved_block = codegen.builder.get_insert_block(); + if let Some(pred_llbb) = bb_map.get(&bid) { + let term = unsafe { pred_llbb.get_terminator() }; + if let Some(t) = term { + codegen.builder.position_before(&t); + } else { + codegen.builder.position_at_end(*pred_llbb); + } + } + val = coerce_to_type(codegen, phi, val)?; + if let Some(bb) = saved_block { codegen.builder.position_at_end(bb); } + let pred_bb = *bb_map.get(&bid).ok_or("pred bb missing")?; + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + let tys = phi + .as_basic_value() + .get_type() + .print_to_string() + .to_string(); + eprintln!( + "[PHI] sealed add pred_bb={} val={} ty={}{}", + bid.as_u32(), + in_vid.as_u32(), + tys, + if snap_opt.is_some() { " (snapshot)" } else { " (vmap)" } + ); + } + match val { + BasicValueEnum::IntValue(iv) => phi.add_incoming(&[(&iv, pred_bb)]), + BasicValueEnum::FloatValue(fv) => phi.add_incoming(&[(&fv, pred_bb)]), + BasicValueEnum::PointerValue(pv) => phi.add_incoming(&[(&pv, pred_bb)]), + _ => return Err("unsupported phi incoming value (seal)".to_string()), } - } - val = coerce_to_type(codegen, phi, val)?; - if let Some(bb) = saved_block { codegen.builder.position_at_end(bb); } - let pred_bb = *bb_map.get(&bid).ok_or("pred bb missing")?; - if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { - let tys = phi - .as_basic_value() - .get_type() - .print_to_string() - .to_string(); - eprintln!( - "[PHI] sealed add pred_bb={} val={} ty={}{}", - bid.as_u32(), - in_vid.as_u32(), - tys, - if snap_opt.is_some() { " (snapshot)" } else { " (vmap)" } - ); - } - match val { - BasicValueEnum::IntValue(iv) => phi.add_incoming(&[(&iv, pred_bb)]), - BasicValueEnum::FloatValue(fv) => phi.add_incoming(&[(&fv, pred_bb)]), - BasicValueEnum::PointerValue(pv) => phi.add_incoming(&[(&pv, pred_bb)]), - _ => return Err("unsupported phi incoming value (seal)".to_string()), - } } else { - // inputs に pred が見つからない場合でも、検証器は「各predに1エントリ」を要求する。 - // ゼロ(型に応じた null/0)を合成して追加する(ログ付) - let pred_bb = *bb_map.get(&bid).ok_or("pred bb missing")?; - use inkwell::types::BasicTypeEnum as BT; - let bt = phi.as_basic_value().get_type(); - let z: BasicValueEnum = match bt { - BT::IntType(it) => it.const_zero().into(), - BT::FloatType(ft) => ft.const_zero().into(), - BT::PointerType(pt) => pt.const_zero().into(), - _ => return Err("unsupported phi type for zero synth (seal)".to_string()), - }; - if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { - eprintln!( - "[PHI] sealed add (synth) pred_bb={} zero-ty={}", - bid.as_u32(), - bt.print_to_string().to_string() - ); - } - match z { - BasicValueEnum::IntValue(iv) => phi.add_incoming(&[(&iv, pred_bb)]), - BasicValueEnum::FloatValue(fv) => phi.add_incoming(&[(&fv, pred_bb)]), - BasicValueEnum::PointerValue(pv) => phi.add_incoming(&[(&pv, pred_bb)]), - _ => return Err("unsupported phi incoming (synth)".to_string()), - } + // Missing mapping for this predecessor: synthesize a typed zero + let pred_bb = *bb_map.get(&bid).ok_or("pred bb missing")?; + use inkwell::types::BasicTypeEnum as BT; + let bt = phi.as_basic_value().get_type(); + let z: BasicValueEnum = match bt { + BT::IntType(it) => it.const_zero().into(), + BT::FloatType(ft) => ft.const_zero().into(), + BT::PointerType(pt) => pt.const_zero().into(), + _ => return Err("unsupported phi type for zero synth (seal)".to_string()), + }; + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!( + "[PHI] sealed add (synth) pred_bb={} zero-ty={}", + bid.as_u32(), + bt.print_to_string().to_string() + ); + } + match z { + BasicValueEnum::IntValue(iv) => phi.add_incoming(&[(&iv, pred_bb)]), + BasicValueEnum::FloatValue(fv) => phi.add_incoming(&[(&fv, pred_bb)]), + BasicValueEnum::PointerValue(pv) => phi.add_incoming(&[(&pv, pred_bb)]), + _ => return Err("unsupported phi incoming (synth)".to_string()), + } } } } diff --git a/src/backend/llvm/compiler/codegen/mod.rs b/src/backend/llvm/compiler/codegen/mod.rs index dfd76792..2e92bde7 100644 --- a/src/backend/llvm/compiler/codegen/mod.rs +++ b/src/backend/llvm/compiler/codegen/mod.rs @@ -125,7 +125,7 @@ impl LLVMCompiler { > = HashMap::new(); // Snapshot of values at the end of each basic block (for sealed-SSA PHI wiring) let mut block_end_values: HashMap> = HashMap::new(); - // Build successors map (for optional sealed-SSA PHI wiring) + // Build successors and predecessors map (for optional sealed-SSA PHI wiring) let mut succs: HashMap> = HashMap::new(); for (bid, block) in &func.blocks { let v: Vec = block.successors.iter().copied().collect(); @@ -388,7 +388,7 @@ impl LLVMCompiler { } } if sealed_mode { - instructions::flow::seal_block(&codegen, func, *bid, &succs, &bb_map, &phis_by_block, &block_end_values, &vmap)?; + instructions::flow::seal_block(&codegen, &mut cursor, func, *bid, &succs, &bb_map, &phis_by_block, &block_end_values, &vmap)?; } } // Finalize function: ensure every basic block is closed with a terminator.