Root cause: PHI inputs were being remapped twice in instruction_rewriter.rs - Line 304: remap_instruction() already remapped JoinIR → Host ValueIds - Line 328: remap_value() attempted to remap again → undefined ValueIds Fix: Only remap block IDs, use already-remapped ValueIds as-is Test results: - 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) - No [joinir/freeze], no regressions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
6.5 KiB
6.5 KiB
Phase 196: Select Expansion Bug Analysis
Problem Summary
JoinIR→MIR の Select 展開処理で、PHI inputs に undefined ValueId が使用される問題が発生している。
Reproduction Case
// apps/tests/phase195_sum_count.hako
local i = 1
local sum = 0
local count = 0
loop(i <= 5) {
if(i % 2 == 1) {
sum = sum + i
count = count + 1
} else {
sum = sum + 0
count = count + 0
}
i = i + 1
}
##症状 (Before)
JoinIR 側(正しい)
[joinir_block/handle_select] Created merge_block BasicBlockId(5) with 1 instructions
(first=Some(Phi { dst: ValueId(20), inputs: [(BasicBlockId(3), ValueId(14)), (BasicBlockId(4), ValueId(18))], type_hint: None }))
ValueId Remapping(正しい)
[DEBUG-177] JoinIR ValueId(14) → Host ValueId(21)
[DEBUG-177] JoinIR ValueId(18) → Host ValueId(25)
Block Remapping(正しい)
[trace:blocks] allocator: Block remap: join_func_1:BasicBlockId(3) → BasicBlockId(8)
[trace:blocks] allocator: Block remap: join_func_1:BasicBlockId(4) → BasicBlockId(9)
[trace:blocks] allocator: Block remap: join_func_1:BasicBlockId(5) → BasicBlockId(10)
MIR 側(壊れている)
bb10:
1: %27 = phi [%28, bb8], [%32, bb9]
1: br %20, label bb11, label bb12
問題: ValueId(28) と ValueId(32) は定義されていない!
- 期待値:
phi [%21, bb8], [%25, bb9](remap後の値) - 実際:
phi [%28, bb8], [%32, bb9](未定義の値)
根本原因の仮説
仮説1: PHI inputs の ValueId が remap されていない
handle_select()で生成された PHI は JoinIR ValueId を使用instruction_rewriter.rsの PHI remap ロジック(line 317-333)が何らかの理由で適用されていない
仮説2: 二重 remap による上書き
remap_instruction()で 1回 remap(line 304)- 手動 block remap で再度 remap(line 317-333)
- 二重 remap が問題を引き起こしている可能性
仮説3: local_block_map のスコープ問題
- Select で生成された then/else ブロック(bb3, bb4)が local_block_map に含まれていない
.unwrap_or(*bb)でフォールバックしている可能性
調査経路
-
handle_select() 実装:
src/mir/join_ir_vm_bridge/joinir_block_converter.rs:407-484- Select → Branch + then/else + merge(PHI) に展開
- PHI inputs に生の JoinIR ValueId を使用(line 461)
-
instruction_rewriter PHI remap:
src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs:317-333- PHI の block ID と ValueID を両方 remap
remapper.remap_value(*val)で ValueId を変換(line 328)
-
remap_instruction() の PHI 処理:
src/mir/builder/joinir_id_remapper.rs:327-334- こちらでも PHI inputs を remap(line 331)
根本原因(確定)
仮説2が正解: 二重 remap による破損
問題の詳細
instruction_rewriter.rs の PHI 処理で、ValueId が 二重に remap されていた:
-
Line 304:
remapper.remap_instruction(inst)で PHI inputs の ValueId を remap- JoinIR
ValueId(14)→ HostValueId(21) - JoinIR
ValueId(18)→ HostValueId(25)
- JoinIR
-
Line 328:
remapper.remap_value(*val)で 再度 remap を試行- しかし
*valは既に Host ValueId(21, 25)になっている! remap_value(ValueId(21))→value_mapに存在しない →unwrap_or(21)→ValueId(21)- 問題なく見えるが、実際には壊れている
- しかし
なぜ壊れたか?
remap_instruction() が返した inputs は 既に remap 済み なのに、line 328 で もう一度 remap しようとした。
しかし、実際には remap_value() は idempotent ではない可能性がある(value_map に存在しない場合は元の値を返すが、これは別の ValueId が偶然同じ番号の可能性がある)。
修正内容
File: src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs
Line: 317-335
// Before (Phase 172)
MirInstruction::Phi {
dst,
inputs,
type_hint: None,
} => MirInstruction::Phi {
dst,
inputs: inputs
.iter()
.map(|(bb, val)| {
let remapped_bb = local_block_map.get(bb).copied().unwrap_or(*bb);
let remapped_val = remapper.remap_value(*val); // ❌ 二重 remap!
(remapped_bb, remapped_val)
})
.collect(),
type_hint: None,
},
// After (Phase 196)
MirInstruction::Phi {
dst,
inputs,
type_hint: None,
} => MirInstruction::Phi {
dst,
inputs: inputs
.iter()
.map(|(bb, val)| {
let remapped_bb = local_block_map.get(bb).copied().unwrap_or(*bb);
// Phase 196 FIX: Don't double-remap values!
// remapper.remap_instruction() already remapped *val
(remapped_bb, *val) // ✅ 値はそのまま使う(既に remap 済み)
})
.collect(),
type_hint: None,
},
修正後の動作(After)
bb10:
1: %27 = phi [%21, bb8], [%25, bb9] // ✅ 正しい ValueId
1: br %20, label bb11, label bb12
テスト結果
✅ phase195_sum_count.hako
$ NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase195_sum_count.hako
93
RC: 0
期待値: sum=9 (1+3+5), count=3 → result=93 ✅
✅ loop_if_phi.hako (Single-carrier P3)
$ NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/loop_if_phi.hako
[Console LOG] sum=9
RC: 0
✅ loop_min_while.hako (Pattern 1)
$ NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/loop_min_while.hako
0
1
2
RC: 0
✅ joinir_min_loop.hako (Pattern 2)
$ NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/joinir_min_loop.hako
RC: 0
退行チェック
- ✅ Pattern 1 (Simple while): PASS
- ✅ Pattern 2 (Break): PASS
- ✅ Pattern 3 (Single-carrier): PASS
- ✅ Pattern 3 (Multi-carrier): PASS
- ✅ No
[joinir/freeze]warnings
まとめ
問題
- Select 展開で生成された PHI の inputs が undefined ValueId を参照
根本原因
instruction_rewriter.rsで ValueId を二重 remap(1回目: remap_instruction, 2回目: manual remap)
修正
- PHI の block ID のみを remap、ValueId は既に remap 済みなのでそのまま使用
影響範囲
- 1ファイル 1箇所のみ:
instruction_rewriter.rsline 331
成果
- Phase 195 の Multi-carrier Pattern 3 が完全動作
- 既存 Pattern 1/2/3 に退行なし