Implementation: - Add make_pattern2_scope_manager() helper for DRY - Header conditions use ExprLowerer for supported patterns - Legacy fallback for unsupported patterns - Fail-Fast on supported patterns that fail Tests: - 4 new tests (all pass) - test_expr_lowerer_supports_simple_header_condition_i_less_literal - test_expr_lowerer_supports_header_condition_var_less_var - test_expr_lowerer_header_condition_generates_expected_instructions - test_pattern2_header_condition_via_exprlowerer Also: Archive old phase documentation (34k lines removed) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
16 KiB
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 側の期待される構造を記録
# 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 側で生成されている壊れた構造を記録
# 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
# 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」に変換されているかを明確化する。
対象ファイル(読み取りのみ)
-
src/mir/join_ir_vm_bridge/convert.rs:- JoinIR → MIR 変換の entry point
- Select 命令の処理箇所を探す
-
src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs:- ExitLine / PHI 周りの書き換え処理
- Select 関連の rewrite logic を確認
-
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 に変換する箇所はどこか?
記録する情報:
## 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 への変換はどこで行われるか?
記録する情報:
## 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 に追記:
## 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 を使っている
確認方法:
// handle_select の中で PHI inputs を作る箇所を確認
let phi_inputs = vec![
(then_block_id, then_value_id), // ← これが正しい ValueId か?
(else_block_id, else_value_id),
];
期待される修正:
// 正しい形: 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 と同じ)
確認方法:
// 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 に直接使っている
確認方法:
// remapper を使っているか確認
let phi_inputs = vec![
(then_block_id, join_value_id), // ← JoinIR の ValueId そのまま(NG)
(else_block_id, join_value_id),
];
期待される修正:
// 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),
];
実装手順
- Task 196-2 の調査結果を元に原因を絞る
- 1つの候補に集中して修正(複数同時にやらない)
- E2E テストで確認(phase195_sum_count.hako)
- ダメなら次の候補へ(Fail-Fast)
設計原則
- 箱は増やさない: 既存 Select 展開ロジックの中だけを修正
- 最小限の変更: 1箇所だけ直す(複数箇所の同時変更は避ける)
- 明示的エラー: remapper.get_value() が None なら明示的に失敗
Task 196-4: E2E 再検証 & 退行チェック
目標
修正後、phase195_sum_count.hako が動作し、既存テストが退行しないことを確認。
テストケース
1. Multi-carrier P3 (Phase 195)
# 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 (既存)
# 既存の単一キャリア 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 代表テスト
# 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 追記
## 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:
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 に追記:
- [x] **Phase 196**: Select 展開/変換バグ修正
- JoinIR→MIR の Select→Branch+Phi 変換を修正
- PHI inputs に remapper を正しく適用
- P3 multi-carrier が E2E で動作確認完了
- Select 展開は joinir_block.rs::handle_select に集約
成功基準
- 最小再現ケース固定(phase196-select-bug-analysis.md)
- Select 展開経路の責務特定(doc に記録)
- 修正実装(1箇所のみ、箱は増やさない)
- phase195_sum_count.hako → 72 ✅
- 既存テスト退行なし(P1/P2/P3/P4)
- ドキュメント更新(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)
-
最小限の変更:
- 既存 Select 展開ロジックの中だけを修正
- 新規箱なし(bridge 層の修正のみ)
-
1箇所に集中:
- 複数箇所の同時変更は避ける
- 1つの候補に集中して修正 → テスト → 次へ
-
明示的エラー:
- remapper.get_value() が None なら明示的に失敗
- 「なんとなく動く」より「壊れたら明示的に失敗」
-
ドキュメント駆動:
- Before/After を明確に記録
- 将来同じバグが起きないように教訓を残す
Implementation Results(完了: 2025-12-09)
Status: ✅ Complete
修正ポイント
File: src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs
Line: 317-335
根本原因: PHI inputs の ValueId が二重に remap されていた
- Line 304:
remap_instruction()で JoinIR ValueId → Host ValueId に remap 済み - Line 328:
remap_value(*val)で再度 remap を試行 → undefined ValueId 参照
修正内容: Block ID のみ remap、ValueId は既に remap 済みなのでそのまま使用
// Before (Phase 172 - 壊れていた)
let remapped_val = remapper.remap_value(*val); // ❌ 二重 remap
(remapped_bb, remapped_val)
// After (Phase 196 - 修正後)
(remapped_bb, *val) // ✅ 値はそのまま使用(既に remap 済み)
具体例
Before:
bb10:
%27 = phi [%28, bb8], [%32, bb9] // %28, %32 undefined
After:
bb10:
%27 = phi [%21, bb8], [%25, bb9] // %21, %25 correctly defined
検証結果
- ✅ phase195_sum_count.hako: 93(multi-carrier P3)
- ✅ loop_if_phi.hako: sum=9(single-carrier P3)
- ✅ loop_min_while.hako: 0,1,2(Pattern 1)
- ✅ joinir_min_loop.hako: RC:0(Pattern 2)
- ✅ 退行なし: 全 Pattern (P1/P2/P3/P4) PASS
- ✅
[joinir/freeze]なし
詳細分析
完全な Before/After 分析、根本原因調査、テスト結果は以下を参照:
コミット
- [
996925eb] fix(joinir): Phase 196 Select double-remap bug in instruction_rewriter Status: Historical