feat(joinir): Phase 132-P3 - Exit PHI collision early detection
Added verify_exit_phi_no_collision() to contract_checks.rs for early detection of ValueId collisions between exit PHIs and other instructions. Problem detected: - If exit_phi_builder uses builder.value_gen.next() (module-level) instead of func.next_value_id() (function-level), ValueIds can collide: Example: - bb0: %1 = const 0 (counter init) - bb3: %1 = phi ... (exit PHI - collision!) Previous behavior: - Error only detected at LLVM backend runtime - Cryptic error: "Cannot overwrite PHI dst=1" New behavior: - Panic at Rust compile time (debug build) - Clear error message with fix suggestion: "Exit PHI dst %1 collides with instruction in block 0 Fix: Use func.next_value_id() in exit_phi_builder.rs" Benefits: - 🔥 Fail-Fast: Catch errors during Rust compilation, not LLVM execution - 📋 Clear messages: Exact collision point + fix suggestion - 🧪 Testable: verify_exit_phi_no_collision() can be unit tested 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -4,7 +4,7 @@
|
||||
2025-12-15
|
||||
|
||||
## Status
|
||||
🔴 **FAILED** - LLVM executable returns wrong result
|
||||
✅ **FIXED** - VM / LLVM EXE parity achieved
|
||||
|
||||
## Summary
|
||||
Testing `apps/tests/llvm_stage3_loop_only.hako` (Pattern 5: InfiniteEarlyExit) in LLVM EXE mode reveals a critical exit PHI usage bug.
|
||||
@ -32,7 +32,39 @@ static box Main {
|
||||
|
||||
## Actual Behavior
|
||||
- **VM execution**: `Result: 3` ✅
|
||||
- **LLVM EXE**: `Result: 0` ❌
|
||||
- **LLVM EXE**: `Result: 0` ❌(修正前)
|
||||
|
||||
## Final Root Cause (Confirmed)
|
||||
|
||||
**JoinIR merge 側の Exit PHI ValueId 衝突**。
|
||||
|
||||
`src/mir/builder/control_flow/joinir/merge/exit_phi_builder.rs` が PHI dst の割り当てに
|
||||
`builder.value_gen.next()`(モジュールレベルのカウンタ)を使っていたため、
|
||||
**同一関数内で `ValueId` が衝突**し、結果として exit 側で参照すべき PHI が別の値に潰れていた。
|
||||
|
||||
例(概念):
|
||||
|
||||
- `bb0: %1 = const 0`(counter 初期値)
|
||||
- `bb3: %1 = phi ...`(exit PHI)
|
||||
|
||||
**同じ `%1`** になり得るため、exit 側で `counter` を読んでも 0 になってしまう。
|
||||
|
||||
### Fix
|
||||
|
||||
PHI dst の割り当てを **関数ローカルの allocator** に統一する。
|
||||
|
||||
- Before: `builder.value_gen.next()`(module-level)
|
||||
- After: `func.next_value_id()`(function-level)
|
||||
|
||||
コミット:
|
||||
- `bd07b7f4 fix(joinir): Phase 132-P2 - Exit PHI ValueId collision fix`
|
||||
|
||||
### Verification
|
||||
|
||||
| テスト | VM | LLVM |
|
||||
|---|---:|---:|
|
||||
| Pattern 1 (`/tmp/p1_return_i.hako`) | 3 ✅ | 3 ✅ |
|
||||
| Case C (`apps/tests/llvm_stage3_loop_only.hako`) | Result: 3 ✅ | Result: 3 ✅ |
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
@ -52,6 +84,10 @@ bb3: ; Exit block
|
||||
|
||||
**MIR is correct**: The exit block (bb3) has a PHI node that receives the counter value from bb6, and subsequent instructions correctly use `%1`.
|
||||
|
||||
補足:
|
||||
- 上記の “MIR が正しい” は「構造として PHI がある」意味で、**ValueId 衝突があると意味論が壊れる**。
|
||||
- 本件は “JoinIR merge の ValueId allocator の選択” が原因だったため、LLVM 側で 0 に見える形で顕在化した。
|
||||
|
||||
### LLVM IR (BUG)
|
||||
```llvm
|
||||
bb3:
|
||||
@ -68,14 +104,13 @@ bb3:
|
||||
|
||||
**Bug identified**: The PHI node `%"phi_1"` is created correctly and receives the counter value from `%"add_8"`. However, **all subsequent uses of ValueId(1) are hardcoded to `i64 0` instead of using `%"phi_1"`**.
|
||||
|
||||
### Hypothesis
|
||||
The Python LLVM builder is not correctly resolving ValueId(1) when lowering instructions in bb3. Possible causes:
|
||||
### Discarded Hypotheses (Superseded)
|
||||
|
||||
1. **vmap issue**: The PHI node is created and stored in `self.vmap[1]` during `setup_phi_placeholders`, but when lowering instructions in bb3, `vmap_cur` may not contain the PHI.
|
||||
当初は「Python LLVM builder が ValueId(1) を解決できず 0 にフォールバックしている」と推測し、
|
||||
PHI registration / vmap filtering 周りを疑った。
|
||||
|
||||
2. **Resolution fallback**: When `resolve_i64_strict` fails to find ValueId(1), it falls back to `ir.Constant(i64, 0)`.
|
||||
|
||||
3. **Block-local vmap initialization**: `vmap_cur` is initialized with `dict(builder.vmap)` at the start of each block, but something may be preventing the PHI from being included.
|
||||
ただし最終的に、本件の主因は **Rust 側(JoinIR merge)の ValueId 衝突**であることが確定したため、
|
||||
ここでの Python 側の推測は “症状の説明” に留まる(根因ではない)。
|
||||
|
||||
## Investigation Steps
|
||||
|
||||
@ -112,7 +147,7 @@ The Python LLVM builder is not correctly resolving ValueId(1) when lowering inst
|
||||
- Exit PHI generation: `src/mir/join_ir/lowering/simple_while_minimal.rs`
|
||||
- Pattern 5 (InfiniteEarlyExit) lowering
|
||||
|
||||
## Root Cause Identified
|
||||
## Interim Hypothesis (Historical)
|
||||
|
||||
### Bug Location
|
||||
`/home/tomoaki/git/hakorune-selfhost/src/llvm_py/llvm_builder.py:342-343`
|
||||
@ -164,6 +199,5 @@ This ensures PHIs are included in `vmap_cur` when lowering their defining block.
|
||||
- ✅ STRICT mode passes without fallback warnings
|
||||
|
||||
## Related Documents
|
||||
- [Phase 132 Plan](/home/tomoaki/git/hakorune-selfhost/docs/development/current/main/phase132-plan.md)
|
||||
- [Phase 131-3 LLVM Lowering Inventory](/home/tomoaki/git/hakorune-selfhost/docs/development/current/main/phase131-3-llvm-lowering-inventory.md)
|
||||
- [Simple While Minimal Lowering](/home/tomoaki/git/hakorune-selfhost/src/mir/join_ir/lowering/simple_while_minimal.rs)
|
||||
- [Phase 132 Summary](/home/tomoaki/git/hakorune-selfhost/docs/development/current/main/phases/phase-132/README.md)
|
||||
|
||||
Reference in New Issue
Block a user