222 lines
6.5 KiB
Markdown
222 lines
6.5 KiB
Markdown
|
|
# 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回 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)` でフォールバックしている可能性
|
|||
|
|
|
|||
|
|
## 調査経路
|
|||
|
|
|
|||
|
|
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 を remap(line 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 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
|
|||
|
|
|
|||
|
|
```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 を二重 remap(1回目: 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 に退行なし
|