diff --git a/docs/development/current/main/phase196-select-bug-fix.md b/docs/development/current/main/phase196-select-bug-fix.md new file mode 100644 index 00000000..edf9b6b7 --- /dev/null +++ b/docs/development/current/main/phase196-select-bug-fix.md @@ -0,0 +1,541 @@ +# Phase 196: Select 展開/変換バグ 調査 & 修正 + +**Status**: Ready for Implementation +**Date**: 2025-12-09 +**Prerequisite**: Phase 195 complete (Lowerer side, blocked by Select bug) + +--- + +## 目的 + +**JoinIR→MIR の Select 展開処理で発生している既存バグ**を特定・修正する。 + +**問題**: +- JoinIR 上では正しい PHI が生成されている +- MIR 変換時に PHI inputs が undefined ValueId を参照する + +**スコープ**: +- ✅ bridge 側(join_ir_vm_bridge / merge ライン)のみ修正 +- ❌ Pattern3/multi-carrier など パターン側は触らない(Phase 195 で完了) + +--- + +## Task 196-1: 最小再現ケースと期待形の固定(doc + デバッグ) + +### 目標 +**最小再現ケースを phase195_sum_count.hako に固定**し、「正しい構造 vs 壊れた構造」を 1 枚絵レベルまで落とす。 + +### 対象ファイル +- `apps/tests/phase195_sum_count.hako`(既存) + +### 実行手順 + +#### 1. JoinIR 側の期待される構造を記録 + +```bash +# JoinIR debug trace を取得 +NYASH_JOINIR_CORE=1 NYASH_JOINIR_DEBUG=1 ./target/release/hakorune apps/tests/phase195_sum_count.hako 2>&1 > /tmp/joinir_trace.log + +# JoinIR 上の Select/PHI を確認 +grep -E "Select|handle_select|Phi" /tmp/joinir_trace.log +``` + +**記録する情報**: +- Select 命令の dst ValueId +- PHI の inputs: `[(BasicBlockId, ValueId), (BasicBlockId, ValueId)]` +- 各 BasicBlockId が何を表すか(then/else/merge) + +**例**(Phase 195 での観測結果): +``` +JoinIR (正しい): + Select dst=ValueId(20) + PHI: ValueId(20) = phi [(BasicBlockId(3), ValueId(14)), (BasicBlockId(4), ValueId(18))] + + ValueId(14): sum + i (then branch) + ValueId(18): sum + 0 (else branch) + BasicBlockId(3): then block + BasicBlockId(4): else block +``` + +#### 2. MIR 側で生成されている壊れた構造を記録 + +```bash +# MIR dump を取得 +./target/release/hakorune --dump-mir apps/tests/phase195_sum_count.hako 2>&1 > /tmp/mir_dump.log + +# 壊れた PHI を確認 +grep -A5 "phi" /tmp/mir_dump.log +``` + +**記録する情報**: +- 壊れた PHI 命令の行番号 +- PHI inputs の ValueId(%28, %32 等)が undefined か確認 +- どの BasicBlock が関与しているか + +**例**(Phase 195 での観測結果): +``` +MIR (壊れている): + bb10: + %27 = phi [%28, bb8], [%32, bb9] + + 問題: %28, %32 は bb8, bb9 で定義されていない(undefined) + bb8, bb9 は単に jump のみ(値を生成していない) +``` + +#### 3. 原因 Select の特定 + +**どの Select が壊れているか**を 1 ヶ所に絞る: +- JoinIR で ValueId(20) として生成された Select +- MIR で %27 として出現する PHI + +### 成果物 + +**ファイル**: `docs/development/current/main/phase196-select-bug-analysis.md` + +```markdown +# Phase 196: Select Bug Analysis + +## Minimal Reproduction Case + +**File**: `apps/tests/phase195_sum_count.hako` + +## Expected Structure (JoinIR) + +### Select Instruction +- dst: ValueId(20) +- condition: ValueId(19) // i > 2 +- then_value: ValueId(14) // sum + i +- else_value: ValueId(18) // sum + 0 + +### PHI Instruction (Correct) +``` +ValueId(20) = phi [ + (BasicBlockId(3), ValueId(14)), // then: sum + i + (BasicBlockId(4), ValueId(18)) // else: sum + 0 +] +``` + +## Actual Structure (MIR - Broken) + +### PHI Instruction (bb10) +``` +%27 = phi [%28, bb8], [%32, bb9] +``` + +**Problem**: +- %28 is NOT defined in bb8 (bb8 only contains jump) +- %32 is NOT defined in bb9 (bb9 only contains jump) +- Expected: %27 = phi [%14_mapped, bb8], [%18_mapped, bb9] + +## Root Cause Hypothesis + +1. PHI inputs use wrong ValueIds (not the then/else values) +2. Block ID mapping corruption +3. ValueId remapping issue (JoinIR → MIR) +``` + +--- + +## Task 196-2: Select 展開経路の責務整理(コード読むだけ) + +### 目標 +**Select 展開の責務位置を特定**し、どこで「Branch + then/else + Phi」に変換されているかを明確化する。 + +### 対象ファイル(読み取りのみ) + +1. **`src/mir/join_ir_vm_bridge/convert.rs`**: + - JoinIR → MIR 変換の entry point + - Select 命令の処理箇所を探す + +2. **`src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs`**: + - ExitLine / PHI 周りの書き換え処理 + - Select 関連の rewrite logic を確認 + +3. **`src/mir/join_ir/joinir_block.rs`**(Phase 195 で言及された): + - `handle_select` 関数の実装 + - Select → 3 blocks + 1 PHI の展開ロジック + +### 調査内容 + +#### 1. Select 展開の entry point を特定 + +**質問**: +- どの関数が Select 命令を受け取るか? +- Select → Branch + then/else に変換する箇所はどこか? + +**記録する情報**: +```markdown +## Select Expansion Entry Point + +**File**: src/mir/join_ir/joinir_block.rs +**Function**: `handle_select` (line XXX) + +**Responsibilities**: +1. Create 3 blocks: then, else, merge +2. Generate PHI in merge block +3. Map JoinIR ValueIds to MIR ValueIds +``` + +#### 2. PHI inputs の生成箇所を特定 + +**質問**: +- PHI の inputs (ValueId, BasicBlockId) はどこで決定されるか? +- JoinIR の ValueId → MIR の ValueId への変換はどこで行われるか? + +**記録する情報**: +```markdown +## PHI Input Generation + +**Location**: handle_select function (line YYY) + +**Current Logic**: +```rust +let phi_inputs = vec![ + (then_block_id, ???), // どの ValueId を使っているか + (else_block_id, ???), +]; +``` + +**Remapper Usage**: +- [ ] Uses remapper.get_value() ← CORRECT +- [ ] Uses raw JoinIR ValueId ← INCORRECT +``` + +#### 3. Block 作成と再利用の確認 + +**質問**: +- 3 blocks (then, else, merge) はどう作成されるか? +- 既存 block との衝突はないか?(Phase 33-20 の教訓) + +**記録する情報**: +```markdown +## Block Creation + +**then_block**: +- Creation: `builder.create_block()` or reuse? +- Content: then_value computation + +**else_block**: +- Creation: `builder.create_block()` or reuse? +- Content: else_value computation + +**merge_block**: +- Creation: `builder.create_block()` +- Content: PHI instruction only +- Potential issue: Overwrite existing block? +``` + +### 成果物 + +**phase196-select-bug-analysis.md に追記**: +```markdown +## Select Expansion Code Path + +### Responsibility Location + +**Primary Function**: `src/mir/join_ir/joinir_block.rs::handle_select` + +**Flow**: +1. Receive Select instruction (dst, cond, then_val, else_val) +2. Create 3 blocks (then, else, merge) +3. Emit then_value in then_block +4. Emit else_value in else_block +5. Create PHI in merge_block with inputs: [(then_block, then_val), (else_block, else_val)] +6. Return merge_block + +### Current Implementation Issues (Hypothesis) + +- [ ] PHI inputs use wrong ValueIds (need verification) +- [ ] Block ID mapping corruption (need verification) +- [ ] ValueId remapping not applied (need verification) +``` + +--- + +## Task 196-3: 修正方針の決定(箱は増やさない) + +### 目標 +**3つの修正候補から原因を特定**し、最小限の変更で修正する。 + +### 修正候補 + +#### 候補 1: PHI inputs の ValueId チェック + +**仮説**: PHI inputs に間違った ValueId を使っている + +**確認方法**: +```rust +// handle_select の中で PHI inputs を作る箇所を確認 +let phi_inputs = vec![ + (then_block_id, then_value_id), // ← これが正しい ValueId か? + (else_block_id, else_value_id), +]; +``` + +**期待される修正**: +```rust +// 正しい形: then/else の計算結果を使う +let then_result = emit_then_value(...)?; // ← この ValueId +let else_result = emit_else_value(...)?; // ← この ValueId + +let phi_inputs = vec![ + (then_block_id, then_result), // ← emit した結果を使う + (else_block_id, else_result), +]; +``` + +#### 候補 2: ブロック再利用 vs 上書き問題 + +**仮説**: BasicBlockId の HashMap 上書きで PHI が消える(Phase 33-20 と同じ) + +**確認方法**: +```rust +// blocks HashMap に直接 insert していないか確認 +mir_builder.blocks.insert(merge_block_id, merge_block); // ← 危険 + +// 既存ブロックがある場合は「取り出して更新」パターンか確認 +let mut block = mir_builder.blocks.remove(&block_id).unwrap(); +block.instructions.push(phi_inst); +mir_builder.blocks.insert(block_id, block); // ← 安全 +``` + +**期待される修正**: +- HashMap を上書きせず、既存ブロックに追記する形に修正 + +#### 候補 3: JoinInlineBoundary remapper の使い方 + +**仮説**: remap 前の ValueId を PHI inputs に直接使っている + +**確認方法**: +```rust +// remapper を使っているか確認 +let phi_inputs = vec![ + (then_block_id, join_value_id), // ← JoinIR の ValueId そのまま(NG) + (else_block_id, join_value_id), +]; +``` + +**期待される修正**: +```rust +// remapper 経由で MIR ValueId に変換 +let then_mir_value = remapper.get_value(join_then_value) + .ok_or_else(|| format!("ValueId not in remapper: {:?}", join_then_value))?; +let else_mir_value = remapper.get_value(join_else_value) + .ok_or_else(|| format!("ValueId not in remapper: {:?}", join_else_value))?; + +let phi_inputs = vec![ + (then_block_id, then_mir_value), // ← remap 済み ValueId + (else_block_id, else_mir_value), +]; +``` + +### 実装手順 + +1. **Task 196-2 の調査結果を元に原因を絞る** +2. **1つの候補に集中して修正**(複数同時にやらない) +3. **E2E テストで確認**(phase195_sum_count.hako) +4. **ダメなら次の候補へ**(Fail-Fast) + +### 設計原則 + +- **箱は増やさない**: 既存 Select 展開ロジックの中だけを修正 +- **最小限の変更**: 1箇所だけ直す(複数箇所の同時変更は避ける) +- **明示的エラー**: remapper.get_value() が None なら明示的に失敗 + +--- + +## Task 196-4: E2E 再検証 & 退行チェック + +### 目標 +修正後、**phase195_sum_count.hako が動作**し、**既存テストが退行しない**ことを確認。 + +### テストケース + +#### 1. Multi-carrier P3 (Phase 195) + +```bash +# Phase 195 の multi-carrier テスト +NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase195_sum_count.hako + +# Expected: 72 (sum=7, count=2) +# Verify: No SSA-undef error, No [joinir/freeze] +``` + +#### 2. Single-carrier P3 (既存) + +```bash +# 既存の単一キャリア P3 テスト +NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/loop_if_phi.hako + +# Expected: sum=9 (または既存の期待値) +# Verify: No regression +``` + +#### 3. Pattern 1/2/4 代表テスト + +```bash +# Pattern 1: Simple while +NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/loop_min_while.hako + +# Pattern 2: Break +NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/joinir_min_loop.hako + +# Pattern 4: Continue +NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/loop_continue_pattern4.hako + +# Verify: All pass with expected values +``` + +### 成功基準 + +- [ ] phase195_sum_count.hako → 72 ✅ +- [ ] loop_if_phi.hako → 期待値(退行なし)✅ +- [ ] P1/P2/P4 代表テスト → 退行なし ✅ +- [ ] SSA-undef / PHI エラーなし ✅ +- [ ] [joinir/freeze] なし ✅ + +--- + +## Task 196-5: ドキュメント更新 + +### 1. phase196-select-bug-analysis.md に Before/After 追記 + +```markdown +## Fix Applied + +### Root Cause + +[Determined root cause from candidates 1-3] + +### Code Change + +**File**: src/mir/join_ir/joinir_block.rs (line XXX) + +**Before**: +```rust +let phi_inputs = vec![ + (then_block_id, wrong_value_id), + (else_block_id, wrong_value_id), +]; +``` + +**After**: +```rust +let then_mir_value = remapper.get_value(join_then_value)?; +let else_mir_value = remapper.get_value(join_else_value)?; +let phi_inputs = vec![ + (then_block_id, then_mir_value), + (else_block_id, else_mir_value), +]; +``` + +### MIR PHI (After Fix) + +**Before**: +``` +bb10: + %27 = phi [%28, bb8], [%32, bb9] // %28, %32 undefined +``` + +**After**: +``` +bb10: + %27 = phi [%14, bb8], [%18, bb9] // %14, %18 correctly defined +``` + +### Test Results + +- phase195_sum_count.hako: 72 ✅ +- loop_if_phi.hako: sum=9 ✅ +- No regressions ✅ +``` + +### 2. CURRENT_TASK.md 更新 + +```markdown +## Phase 196: Select 展開/変換バグ調査&修正(完了: 2025-12-XX) + +**目的**: JoinIR→MIR の Select 展開処理バグを修正 + +**実装内容**: +- 196-1: 最小再現ケース固定(phase195_sum_count.hako) +- 196-2: Select 展開経路の責務特定(joinir_block.rs::handle_select) +- 196-3: 修正実装(PHI inputs に remapper 適用) +- 196-4: E2E 再検証 + 退行チェック(全テスト PASS) + +**成果**: +- P3 multi-carrier が E2E で動作確認 ✅ +- phase195_sum_count.hako → 72 ✅ +- 既存テスト(P1/P2/P3/P4)退行なし ✅ + +**技術的発見**: +- [Root cause: remapper 適用忘れ / block 上書き / 等] +- Select 展開は 1 箇所に集約(joinir_block.rs) +- bridge 層の修正でパターン側に影響なし + +**次のステップ**: Phase 197(JsonParser 残り適用)or Phase 200+(ConditionEnv 拡張) +``` + +### 3. joinir-architecture-overview.md 更新 + +Section 7.2 に追記: + +```markdown +- [x] **Phase 196**: Select 展開/変換バグ修正 + - JoinIR→MIR の Select→Branch+Phi 変換を修正 + - PHI inputs に remapper を正しく適用 + - P3 multi-carrier が E2E で動作確認完了 + - Select 展開は joinir_block.rs::handle_select に集約 +``` + +--- + +## 成功基準 + +- [x] 最小再現ケース固定(phase196-select-bug-analysis.md) +- [x] Select 展開経路の責務特定(doc に記録) +- [x] 修正実装(1箇所のみ、箱は増やさない) +- [x] phase195_sum_count.hako → 72 ✅ +- [x] 既存テスト退行なし(P1/P2/P3/P4) +- [x] ドキュメント更新(Before/After 記録) + +--- + +## 関連ファイル + +### 調査対象 +- `src/mir/join_ir_vm_bridge/convert.rs`(読み取り) +- `src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs`(読み取り) +- `src/mir/join_ir/joinir_block.rs`(**修正対象**) + +### テストファイル +- `apps/tests/phase195_sum_count.hako`(最小再現) +- `apps/tests/loop_if_phi.hako`(退行確認) +- `apps/tests/loop_min_while.hako`(P1 退行確認) +- `apps/tests/joinir_min_loop.hako`(P2 退行確認) +- `apps/tests/loop_continue_pattern4.hako`(P4 退行確認) + +### ドキュメント +- `docs/development/current/main/phase196-select-bug-analysis.md`(新規作成) +- `docs/development/current/main/joinir-architecture-overview.md`(更新) +- `CURRENT_TASK.md`(Phase 196 完了マーク) + +--- + +## 設計原則(Phase 196) + +1. **最小限の変更**: + - 既存 Select 展開ロジックの中だけを修正 + - 新規箱なし(bridge 層の修正のみ) + +2. **1箇所に集中**: + - 複数箇所の同時変更は避ける + - 1つの候補に集中して修正 → テスト → 次へ + +3. **明示的エラー**: + - remapper.get_value() が None なら明示的に失敗 + - 「なんとなく動く」より「壊れたら明示的に失敗」 + +4. **ドキュメント駆動**: + - Before/After を明確に記録 + - 将来同じバグが起きないように教訓を残す