195 lines
6.2 KiB
Markdown
195 lines
6.2 KiB
Markdown
|
|
# Phase 72: PHI Reserved Region Observation Report
|
||
|
|
|
||
|
|
## Executive Summary
|
||
|
|
|
||
|
|
**Date**: 2025-12-13
|
||
|
|
**Status**: ⚠️ **Finding: PHI dst allocation does NOT respect reserved region**
|
||
|
|
|
||
|
|
### Key Finding
|
||
|
|
|
||
|
|
PHI dst ValueIds are allocated via `builder.next_value_id()` from the host MirBuilder, NOT from the reserved region (0-99) described in `join_value_space.rs`.
|
||
|
|
|
||
|
|
### Evidence
|
||
|
|
|
||
|
|
1. **Documentation states**:
|
||
|
|
```rust
|
||
|
|
// src/mir/join_ir/lowering/join_value_space.rs
|
||
|
|
//! 0 100 1000 u32::MAX
|
||
|
|
//! ├──────────┼──────────┼──────────────────────────┤
|
||
|
|
//! │ PHI │ Param │ Local │
|
||
|
|
//! │ Reserved│ Region │ Region │
|
||
|
|
//! └──────────┴──────────┴──────────────────────────┘
|
||
|
|
//! - **PHI Reserved (0-99)**: Pre-reserved for LoopHeader PHI dst
|
||
|
|
```
|
||
|
|
|
||
|
|
2. **Actual PHI allocation** (from `loop_header_phi_builder.rs:90,147`):
|
||
|
|
```rust
|
||
|
|
let loop_var_phi_dst = builder.next_value_id(); // From host MirBuilder!
|
||
|
|
let phi_dst = builder.next_value_id(); // Not from JoinValueSpace!
|
||
|
|
```
|
||
|
|
|
||
|
|
3. **Observed PHI dst from `loop_min_while.hako`**:
|
||
|
|
```
|
||
|
|
bb4:
|
||
|
|
1: %3: String = phi [%2, bb0], [%12, bb7]
|
||
|
|
```
|
||
|
|
- PHI dst = `%3` (ValueId(3))
|
||
|
|
- ✅ This IS in reserved region (0-99)
|
||
|
|
|
||
|
|
### Analysis
|
||
|
|
|
||
|
|
#### Current Behavior
|
||
|
|
|
||
|
|
- PHI dst values come from `builder.next_value_id()` which starts from 0
|
||
|
|
- MirBuilder allocates ValueIds sequentially: 0, 1, 2, 3, ...
|
||
|
|
- Early ValueIds (from function setup) naturally fall into 0-99 range
|
||
|
|
- **This is ACCIDENTAL compliance**, not architectural enforcement
|
||
|
|
|
||
|
|
#### Observed Pattern
|
||
|
|
|
||
|
|
From `loop_min_while.hako` MIR dump:
|
||
|
|
- Entry block constants: `%1`, `%2` (ValueId 1,2)
|
||
|
|
- PHI dst: `%3` (ValueId 3) - in loop header
|
||
|
|
- Loop body values: `%8`, `%9`, `%10`, `%11`, `%12` (8-12)
|
||
|
|
- Exit value: `%17` (ValueId 17)
|
||
|
|
|
||
|
|
**Conclusion**: PHI dst happens to be low-numbered because it's allocated early in the function, NOT because of reserved region logic.
|
||
|
|
|
||
|
|
#### Why This Works Today
|
||
|
|
|
||
|
|
1. Loop header PHI is allocated BEFORE loop body instructions
|
||
|
|
2. Function entry typically uses ValueIds 0-10
|
||
|
|
3. PHI dst gets allocated in early range (0-20 typically)
|
||
|
|
4. No collision with JoinValueSpace regions (100-999, 1000+) because:
|
||
|
|
- JoinIR uses high ValueIds (100+, 1000+)
|
||
|
|
- Host MIR uses low ValueIds (0-99)
|
||
|
|
- They happen to not overlap in practice
|
||
|
|
|
||
|
|
### Risk Assessment
|
||
|
|
|
||
|
|
#### Current Risks: **LOW**
|
||
|
|
|
||
|
|
- No observed collisions in 937/937 tests
|
||
|
|
- JoinValueSpace and MirBuilder allocate from different ranges
|
||
|
|
- Pattern2 frontend bug (Phase 201) was fixed with explicit regions
|
||
|
|
|
||
|
|
#### Future Risks: **MEDIUM**
|
||
|
|
|
||
|
|
- If MirBuilder allocates 100+ ValueIds before loop header:
|
||
|
|
- PHI dst could be ValueId(100+)
|
||
|
|
- Could collide with JoinValueSpace Param region
|
||
|
|
- Would break `remap_values()` assumptions
|
||
|
|
- If JoinIR lowering uses ValueIds < 100:
|
||
|
|
- Could collide with PHI dst
|
||
|
|
- Would corrupt SSA graph
|
||
|
|
|
||
|
|
### Recommendation
|
||
|
|
|
||
|
|
**DO NOT strengthen verifier** to enforce PHI dst ∈ [0, 99].
|
||
|
|
|
||
|
|
**Reasons**:
|
||
|
|
1. Current architecture does NOT guarantee this
|
||
|
|
2. PHI dst allocation is a host MirBuilder concern, not JoinIR concern
|
||
|
|
3. Reserve region (0-99) is a JoinValueSpace contract for JoinIR lowering
|
||
|
|
4. PHI dst is allocated OUTSIDE JoinIR layer
|
||
|
|
|
||
|
|
**Instead**:
|
||
|
|
1. Document current behavior (Phase 72 observation)
|
||
|
|
2. Keep `JoinValueSpace.reserve_phi()` as debug marker only
|
||
|
|
3. Maintain existing collision detection (Phase 205)
|
||
|
|
4. Monitor for regressions in test suite
|
||
|
|
|
||
|
|
### Alternative: Architectural Fix (Future Phase)
|
||
|
|
|
||
|
|
If strict PHI dst reservation is desired:
|
||
|
|
|
||
|
|
1. **Allocate PHI dst from reserved pool**:
|
||
|
|
```rust
|
||
|
|
// In LoopHeaderPhiBuilder
|
||
|
|
let phi_dst = builder.alloc_phi_reserved(); // New API: 0-99 pool
|
||
|
|
```
|
||
|
|
|
||
|
|
2. **Separate PHI ValueId space**:
|
||
|
|
```rust
|
||
|
|
struct PhiReservedPool {
|
||
|
|
next_phi_id: u32, // Start at 0
|
||
|
|
}
|
||
|
|
impl PhiReservedPool {
|
||
|
|
fn alloc(&mut self) -> ValueId {
|
||
|
|
assert!(self.next_phi_id < 100, "PHI pool exhausted");
|
||
|
|
let id = ValueId(self.next_phi_id);
|
||
|
|
self.next_phi_id += 1;
|
||
|
|
id
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
3. **Fail-fast at 100 PHI nodes**:
|
||
|
|
- Explicit limit prevents accidental overflow
|
||
|
|
- 100 PHI nodes per function is generous
|
||
|
|
|
||
|
|
**Scope**: Phase 73+ (optional enhancement, not urgent)
|
||
|
|
|
||
|
|
## Implementation Record
|
||
|
|
|
||
|
|
### Files Modified
|
||
|
|
|
||
|
|
1. `src/mir/join_ir/verify_phi_reserved.rs` (new)
|
||
|
|
- Observation infrastructure
|
||
|
|
- Distribution analyzer
|
||
|
|
- Report generator
|
||
|
|
|
||
|
|
2. `src/mir/join_ir/mod.rs`
|
||
|
|
- Added verify_phi_reserved module
|
||
|
|
|
||
|
|
3. `src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs`
|
||
|
|
- Added observation hooks (debug-only)
|
||
|
|
|
||
|
|
4. `tests/phase72_phi_observation.rs` (created, not used)
|
||
|
|
- Integration test skeleton (visibility issues)
|
||
|
|
|
||
|
|
### Test Results
|
||
|
|
|
||
|
|
- Observation mechanism: ✅ Implemented
|
||
|
|
- Manual verification via `--dump-mir`: ✅ Confirmed PHI dst in low range
|
||
|
|
- Automatic test collection: ⚠️ Blocked by API visibility
|
||
|
|
|
||
|
|
### Decision
|
||
|
|
|
||
|
|
**Phase 72 COMPLETE** - Observation phase only.
|
||
|
|
|
||
|
|
**Verifier strengthening**: ❌ NOT RECOMMENDED
|
||
|
|
|
||
|
|
**Next steps**: Document findings, monitor in future phases.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Appendix: Observed PHI ValueIds
|
||
|
|
|
||
|
|
### loop_min_while.hako
|
||
|
|
- Loop variable `i`: PHI dst = %3 (ValueId(3))
|
||
|
|
- Range: [3, 3]
|
||
|
|
- ✅ In reserved region
|
||
|
|
|
||
|
|
### Expected Pattern (Not Tested)
|
||
|
|
- Multi-carrier loops (sum+count): PHI dst = %3, %4 expected
|
||
|
|
- Nested loops: PHI dst could be %5-10
|
||
|
|
- Complex functions: PHI dst could exceed 20
|
||
|
|
|
||
|
|
### Theoretical Maximum
|
||
|
|
|
||
|
|
Without enforcement:
|
||
|
|
- Large function with 200 const/copy before loop: PHI dst could be %200+
|
||
|
|
- Would fall into Param region (100-999)
|
||
|
|
- Would NOT be caught by current verifier
|
||
|
|
|
||
|
|
## Code References
|
||
|
|
|
||
|
|
- `src/mir/join_ir/lowering/join_value_space.rs`: Region definitions
|
||
|
|
- `src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs`: PHI allocation
|
||
|
|
- `docs/development/current/main/joinir-architecture-overview.md`: Invariant 8
|
||
|
|
|
||
|
|
## Phase 72 Complete
|
||
|
|
|
||
|
|
**Conclusion**: PHI dst allocation is currently stable through accidental low-numbering, not architectural enforcement. Verifier strengthening would create false assumptions. Document and monitor instead.
|