fix: ループexit PHI生成を追加し、break後の変数値伝播を修正

問題:
- ループexit時のPHI命令が完全に欠落していた
- break後の変数値が初期値に戻ってしまうバグ
- gemini_test_case.nyashで期待値2→実際0が出力

解決:
- LoopBuilderにexit_snapshots追加でbreak時点の変数を収集
- do_break()でスナップショット収集処理を追加
- create_exit_phis()メソッドを新規実装し、exit PHI生成

効果:
- gemini_test_caseが正しく2を出力
- 0回実行、複数break、continue混在すべてのケースで正常動作
- collect_printsのnullエラー解消

テスト済み:
- gemini_test_case.nyash:  期待値2
- test_loop_zero.nyash:  期待値42
- test_multi_break.nyash:  期待値20
- test_continue_break.nyash:  期待値3

MIR確認:
bb3: %15 = phi [%4, bb1], [%9, bb9]
exit PHIが正しく生成されている

Thanks: ChatGPT Pro for root cause analysis

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Selfhosting Dev
2025-09-23 09:48:29 +09:00
parent fc4c866151
commit c9e4a1a6e6
2 changed files with 132 additions and 26 deletions

View File

@ -276,32 +276,77 @@ NYASH_DISABLE_PLUGINS=1 ./target/release/nyash program.nyash
NYASH_LLVM_USE_HARNESS=1 ./target/release/nyash program.nyash
```
## 📝 Update (2025-09-23) 🚨 重大PHI命令バグ発見Step 4改行処理も課題
## 🔍 MIRデバッグ出力完全ガイド必読
### 🎯 **確実にMIRを出力する方法**(優先順)
```bash
# 1⃣ 最も確実: CLIフラグ使用
./target/release/nyash --dump-mir program.nyash
./target/release/nyash --dump-mir --mir-verbose program.nyash # 詳細版
# 2⃣ VM実行時のMIR出力
NYASH_VM_DUMP_MIR=1 ./target/release/nyash program.nyash
# 3⃣ JSON形式でファイル出力
./target/release/nyash --emit-mir-json debug.json program.nyash
cat debug.json | jq . # 整形表示
# 4⃣ PyVM用JSON自動生成
NYASH_VM_USE_PY=1 ./target/release/nyash program.nyash
cat tmp/nyash_pyvm_mir.json | jq .
```
### 📋 **MIR関連環境変数一覧**
| 環境変数 | 用途 | 出力先 |
|---------|-----|-------|
| `NYASH_VM_DUMP_MIR=1` | VM実行前MIR出力 | stderr |
| `NYASH_DUMP_JSON_IR=1` | JSON IR出力 | stdout |
| `NYASH_CLI_VERBOSE=1` | 詳細診断MIR含む | stderr |
| `NYASH_DEBUG_MIR_PRINTER=1` | MIRプリンターデバッグ | stderr |
### 🚨 **MIRが出力されない時のチェックリスト**
1.`--dump-mir` フラグを使用(最も確実)
2.`--backend vm` を明示的に指定
3.`NYASH_DISABLE_PLUGINS=1` でプラグイン干渉を排除
4.`NYASH_CLI_VERBOSE=1` で詳細情報取得
### 💡 **実用的デバッグフロー**
```bash
# Step 1: 基本MIR確認
./target/release/nyash --dump-mir gemini_test_case.nyash
# Step 2: 詳細MIR + エフェクト情報
./target/release/nyash --dump-mir --mir-verbose --mir-verbose-effects gemini_test_case.nyash
# Step 3: VM実行時の挙動確認
NYASH_VM_DUMP_MIR=1 NYASH_CLI_VERBOSE=1 ./target/release/nyash gemini_test_case.nyash
# Step 4: JSON形式で詳細解析
./target/release/nyash --emit-mir-json mir.json gemini_test_case.nyash
jq '.functions[0].blocks' mir.json # ブロック構造確認
```
## 📝 Update (2025-09-23) ✅ PHIバグ完全修正Exit PHI実装でループ制御フロー完成
- 🎉 **PHIバグ根本解決完了** Exit PHI生成実装でループ後の変数値が正しく伝播
- **修正前**: gemini_test_case期待値2→実際0初期値に戻る
- **修正後**: 期待値2が正しく出力 ✅
- **技術的成果**: たった3箇所の修正で根本解決
1. `exit_snapshots`フィールド追加break時点の変数収集
2. `do_break()`でスナップショット収集
3. `create_exit_phis()`メソッド新規実装
- **MIR確認**: `bb3: %15 = phi [%4, bb1], [%9, bb9]` exit PHI正常生成
- **全テスト合格**: 0回実行、複数break、continue混在すべて✅
-**フェーズM+M.2完全達成!** PHI統一革命でcollect_prints問題根本解決
-**Step 1完全達成** match式オブジェクトリテラル判定修正完了
- **修正完了**: `src/parser/expr/match_expr.rs``is_object_literal()` メソッド追加
- **副作用修正**: `match_token()``current_token()` で副作用除去
- **動作確認**: 単行match式 + オブジェクトリテラル ✅ 完全動作
- **Step 2完全達成** peek→match完全統一でアーキテクチャクリーンアップ完了
- **統一完了**: 15ファイルで `PeekExpr``MatchExpr` 一括置換完了
- **ファイル移行**: `lowering/peek.rs``match_expr.rs` 完全移行
- **コンパイル成功**: エラーゼロで正常ビルド完了
- **動作確認**: match式テスト正常動作 ✅
- **効果**: AI理解性向上・コードベース一貫性・保守性大幅向上
-**Step 3完全達成** Task先生による複数行パース問題根本原因特定完了
- **原因特定**: オブジェクトリテラルパーサーの改行スキップ不足(`src/parser/expr/primary.rs`
- **修正箇所**: L49、L77-79に `skip_newlines()` 追加で解決可能
- **工数**: 30分以内の簡単修正
- 🤔 **Step 4課題発見** 改行処理アーキテクチャの美しさ問題
- **問題**: `skip_newlines()` を各所に散りばめる方法は美しくない
- **課題**: 保守性・一貫性・見落としリスク・設計の美しさ
- **方針**: Gemini 3相談でアーキテクチャ根本改善検討
- 🚨 **重大発見!** PHI命令処理バグgemini_test_case: 期待値2→実際0
- **問題**: フェーズM+M.2のPHI統一作業でループ後変数マージに回帰バグ
- **詳細**: PHI命令は正しく動作v8→2だが、print時に間違ったPHIv4→0参照
- **根本原因**: ループ脱出後の変数PHI接続が初期値を参照している
- **影響**: Phase 15セルフホスティング基盤の重大バグ
- **次のアクション**: ChatGPT相談でMIRビルダー修正戦略立案
-**Step 1-3完全達成!** match式/peek統一/改行処理すべて完了
- match式オブジェクトリテラル判定修正 ✅
- peek→match完全統一15ファイル
- 複数行パース問題解決策特定 ✅
- 🚀 **ChatGPT Pro協働成功** 最強分析でExit PHI欠落を特定
- 完璧な原因分析ヘッダPHI○、exit PHI×
- スコープ化された美しい実装提案
- 段階的修正戦略で確実な実装
## 📝 Update (2025-09-22) 🎯 Phase 15 JITアーカイブ完了デバッグ大進展
-**JIT/Craneliftアーカイブ完了** Phase 15集中開発のため全JIT機能を安全にアーカイブ

View File

@ -44,6 +44,9 @@ pub struct LoopBuilder<'a> {
/// continue文からの変数スナップショット
continue_snapshots: Vec<(BasicBlockId, HashMap<String, ValueId>)>,
/// break文からの変数スナップショットexit PHI生成用
exit_snapshots: Vec<(BasicBlockId, HashMap<String, ValueId>)>,
// フェーズM: no_phi_modeフィールド削除常にPHI使用
}
@ -75,6 +78,11 @@ impl<'a> LoopBuilder<'a> {
/// Handle a `break` statement: jump to loop exit and continue in a fresh unreachable block.
fn do_break(&mut self) -> Result<ValueId, String> {
// Snapshot variables at break point for exit PHI generation
let snapshot = self.get_current_variable_map();
let cur_block = self.current_block()?;
self.exit_snapshots.push((cur_block, snapshot));
if let Some(exit_bb) = crate::mir::builder::loops::current_exit(self.parent_builder) {
self.jump_with_pred(exit_bb)?;
}
@ -108,6 +116,7 @@ impl<'a> LoopBuilder<'a> {
block_var_maps: HashMap::new(),
loop_header: None,
continue_snapshots: Vec::new(),
exit_snapshots: Vec::new(), // exit PHI用のスナップショット
// フェーズM: no_phi_modeフィールド削除
}
}
@ -235,8 +244,12 @@ impl<'a> LoopBuilder<'a> {
// 9. Headerブロックをシール全predecessors確定
self.seal_block(header_id, latch_id)?;
// 10. ループ後の処理
// 10. ループ後の処理 - Exit PHI生成
self.set_current_block(after_loop_id)?;
// Exit PHIの生成 - break時点での変数値を統一
self.create_exit_phis(header_id, after_loop_id)?;
// Pop loop context
crate::mir::builder::loops::pop_loop_context(self.parent_builder);
@ -317,6 +330,54 @@ impl<'a> LoopBuilder<'a> {
Ok(())
}
/// Exitブロックで変数のPHIを生成breakポイントでの値を統一
fn create_exit_phis(&mut self, header_id: BasicBlockId, exit_id: BasicBlockId) -> Result<(), String> {
// 全変数名を収集exit_snapshots内のすべての変数
let mut all_vars = std::collections::HashSet::new();
// Header直行ケース0回実行の変数を収集
let header_vars = self.get_current_variable_map();
for var_name in header_vars.keys() {
all_vars.insert(var_name.clone());
}
// break時点の変数を収集
for (_, snapshot) in &self.exit_snapshots {
for var_name in snapshot.keys() {
all_vars.insert(var_name.clone());
}
}
// 各変数に対してExit PHIを生成
for var_name in all_vars {
let mut phi_inputs = Vec::new();
// Header直行ケース0回実行の入力
if let Some(header_value) = header_vars.get(&var_name) {
phi_inputs.push((header_id, *header_value));
}
// 各breakポイントからの入力
for (block_id, snapshot) in &self.exit_snapshots {
if let Some(value) = snapshot.get(&var_name) {
phi_inputs.push((*block_id, *value));
}
}
// PHI入力が2つ以上なら、PHIードを生成
if phi_inputs.len() > 1 {
let phi_dst = self.new_value();
self.emit_phi_at_block_start(exit_id, phi_dst, phi_inputs)?;
self.update_variable(var_name, phi_dst);
} else if phi_inputs.len() == 1 {
// 単一入力なら直接使用(最適化)
self.update_variable(var_name, phi_inputs[0].1);
}
}
Ok(())
}
// --- ヘルパーメソッド(親ビルダーへの委譲) ---
fn current_block(&self) -> Result<BasicBlockId, String> {