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

222 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Phase 196: Select Expansion Bug Analysis
## Problem Summary
JoinIR→MIR の Select 展開処理で、PHI inputs に undefined ValueId が使用される問題が発生している。
## Reproduction Case
```hako
// 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
```rust
// 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
```bash
$ 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)
```bash
$ 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)
```bash
$ NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/loop_min_while.hako
0
1
2
RC: 0
```
### ✅ joinir_min_loop.hako (Pattern 2)
```bash
$ 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 に退行なし