Files
hakorune/docs/development/current/main/phase196-select-bug-analysis.md
nyash-codex 996925ebaf fix(joinir): Phase 196 Select double-remap bug in instruction_rewriter
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>
2025-12-09 14:45:04 +09:00

6.5 KiB
Raw Blame History

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回 remapline 304
  • 手動 block remap で再度 remapline 317-333
  • 二重 remap が問題を引き起こしている可能性

仮説3: local_block_map のスコープ問題

  • Select で生成された then/else ブロックbb3, bb4が local_block_map に含まれていない
  • .unwrap_or(*bb) でフォールバックしている可能性

調査経路

  1. 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
  2. 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
  3. remap_instruction() の PHI 処理:

    • src/mir/builder/joinir_id_remapper.rs:327-334
    • こちらでも PHI inputs を remapline 331

根本原因(確定)

仮説2が正解: 二重 remap による破損

問題の詳細

instruction_rewriter.rs の PHI 処理で、ValueId が 二重に remap されていた:

  1. Line 304: remapper.remap_instruction(inst) で PHI inputs の ValueId を remap

    • JoinIR ValueId(14) → Host ValueId(21)
    • JoinIR ValueId(18) → Host ValueId(25)
  2. Line 328: remapper.remap_value(*val)再度 remap を試行

    • しかし *val は既に Host ValueId21, 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 を二重 remap1回目: remap_instruction, 2回目: manual remap

修正

  • PHI の block ID のみを remap、ValueId は既に remap 済みなのでそのまま使用

影響範囲

  • 1ファイル 1箇所のみ: instruction_rewriter.rs line 331

成果

  • Phase 195 の Multi-carrier Pattern 3 が完全動作
  • 既存 Pattern 1/2/3 に退行なし