Files
hakorune/docs/development/current/main/phase196-select-bug-analysis.md

222 lines
6.5 KiB
Markdown
Raw Normal View History

# 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 に退行なし