229 lines
6.6 KiB
Markdown
229 lines
6.6 KiB
Markdown
|
|
# Phase 131 P1.5: Root Cause Analysis Summary
|
||
|
|
|
||
|
|
Status: Analysis Complete
|
||
|
|
Date: 2025-12-18
|
||
|
|
Scope: Phase 131 P1.5 variable propagation failure diagnosis
|
||
|
|
|
||
|
|
## TL;DR
|
||
|
|
|
||
|
|
**Problem**: loop(true) { x=1; break }; return x → returns 0 instead of 1
|
||
|
|
|
||
|
|
**Root Cause**: Using PHI-based merge pipeline for Normalized IR's continuation-based control flow
|
||
|
|
|
||
|
|
**Solution**: Option B - Direct env→host wiring via ExitReconnectorBox (PHI-free, matches Normalized design)
|
||
|
|
|
||
|
|
## Investigation Timeline
|
||
|
|
|
||
|
|
### Step 1: Verify ExitMeta Creation ✅
|
||
|
|
|
||
|
|
**Location**: `src/mir/control_tree/normalized_shadow/loop_true_break_once.rs:317-327`
|
||
|
|
|
||
|
|
**Code**:
|
||
|
|
```rust
|
||
|
|
let mut exit_values = Vec::new();
|
||
|
|
for (i, var_name) in env_layout.writes.iter().enumerate() {
|
||
|
|
let k_exit_param_vid = k_exit_func.params[i];
|
||
|
|
exit_values.push((var_name.clone(), k_exit_param_vid));
|
||
|
|
}
|
||
|
|
let exit_meta = ExitMeta { exit_values };
|
||
|
|
```
|
||
|
|
|
||
|
|
**Trace Evidence**:
|
||
|
|
```
|
||
|
|
[trace:exit-line] collector: Collecting 1 exit values
|
||
|
|
[trace:exit-line] collector: checking carrier 'x' in variable_ctx.variable_map
|
||
|
|
[trace:exit-line] collector: collected 'x' JoinIR ValueId(6) → HOST ValueId(2), role=LoopState
|
||
|
|
```
|
||
|
|
|
||
|
|
**Conclusion**: ✅ ExitMeta is created correctly. `x` → `ValueId(6)` (k_exit param) is captured.
|
||
|
|
|
||
|
|
### Step 2: Verify ExitMetaCollector ✅
|
||
|
|
|
||
|
|
**Location**: `src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs:77-261`
|
||
|
|
|
||
|
|
**Trace Evidence**:
|
||
|
|
```
|
||
|
|
[trace:exit-line] collector: collected 1 bindings: [LoopExitBinding {
|
||
|
|
carrier_name: "x",
|
||
|
|
join_exit_value: ValueId(6),
|
||
|
|
host_slot: ValueId(2),
|
||
|
|
role: LoopState
|
||
|
|
}]
|
||
|
|
```
|
||
|
|
|
||
|
|
**Conclusion**: ✅ ExitMetaCollector works correctly. Boundary is created with proper mapping.
|
||
|
|
|
||
|
|
### Step 3: Verify variable_map Update ✅
|
||
|
|
|
||
|
|
**Trace Evidence**:
|
||
|
|
```
|
||
|
|
[joinir/exit-line] variable_ctx.variable_map['x'] ValueId(2) → ValueId(10)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Conclusion**: ✅ variable_map is updated with exit PHI result.
|
||
|
|
|
||
|
|
### Step 4: Identify PHI Mismatch ❌
|
||
|
|
|
||
|
|
**Trace Evidence**:
|
||
|
|
```
|
||
|
|
[DEBUG-177] Exit block PHI (carrier 'x'):
|
||
|
|
ValueId(10) = phi [(BasicBlockId(39), ValueId(4))]
|
||
|
|
|
||
|
|
[ERROR] ❌ [rust-vm] VM error: Invalid instruction:
|
||
|
|
phi pred mismatch at ValueId(10): no input for predecessor BasicBlockId(42)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Conclusion**: ❌ **PHI node has wrong predecessors**
|
||
|
|
- Has input from: BasicBlockId(39)
|
||
|
|
- Expected input from: BasicBlockId(42)
|
||
|
|
- This is a **CFG structure mismatch**
|
||
|
|
|
||
|
|
## Root Cause: CFG Structure Incompatibility
|
||
|
|
|
||
|
|
### Standard Loop CFG (Pattern 1-4)
|
||
|
|
|
||
|
|
```
|
||
|
|
entry
|
||
|
|
↓
|
||
|
|
header ←─────┐
|
||
|
|
├─→ body │
|
||
|
|
│ ↓ │
|
||
|
|
│ latch ──┘ (loop back edge)
|
||
|
|
↓
|
||
|
|
exit
|
||
|
|
↓
|
||
|
|
PHI = merge(break_edge, fallthrough)
|
||
|
|
```
|
||
|
|
|
||
|
|
**PHI needs inputs from**:
|
||
|
|
- Break edge from loop body
|
||
|
|
- Fallthrough from header (condition false)
|
||
|
|
|
||
|
|
### Normalized IR CFG (Continuation-based)
|
||
|
|
|
||
|
|
```
|
||
|
|
main(env)
|
||
|
|
↓ TailCall
|
||
|
|
loop_step(env)
|
||
|
|
├─→ (true) TailCall → loop_body(env)
|
||
|
|
│ ↓ TailCall
|
||
|
|
↓ k_exit(env) ──→ return env.x
|
||
|
|
└─→ (false) TailCall → k_exit(env) ──→ return env.x
|
||
|
|
```
|
||
|
|
|
||
|
|
**No loop back edge!** All connections are tail-calls.
|
||
|
|
|
||
|
|
**k_exit receives env as parameter** - this IS the final value (SSOT), no merging needed!
|
||
|
|
|
||
|
|
## Why PHI Generation Fails
|
||
|
|
|
||
|
|
The merge pipeline (`JoinIRConversionPipeline`) assumes:
|
||
|
|
1. Loop has a header with PHI nodes (for loop variables)
|
||
|
|
2. Exit block needs PHI to merge values from multiple loop exits
|
||
|
|
3. CFG has loop back edges and break edges
|
||
|
|
|
||
|
|
**But Normalized IR**:
|
||
|
|
1. No header PHI (env parameters are passed via tail-calls)
|
||
|
|
2. k_exit env parameter IS the final value (no merge needed)
|
||
|
|
3. No loop back edges (only forward tail-calls)
|
||
|
|
|
||
|
|
**The PHI generation logic doesn't match Normalized's control flow structure.**
|
||
|
|
|
||
|
|
## Why Option B is Correct
|
||
|
|
|
||
|
|
### Option B: Direct env→host Wiring (PHI-free)
|
||
|
|
|
||
|
|
**Principle**: k_exit env parameters are SSOT for final values
|
||
|
|
|
||
|
|
**Implementation**:
|
||
|
|
```rust
|
||
|
|
// For Normalized IR only
|
||
|
|
for (var_name, k_exit_param_vid) in exit_meta.exit_values {
|
||
|
|
// k_exit_param_vid is already the final value!
|
||
|
|
variable_map.insert(var_name, k_exit_param_vid);
|
||
|
|
}
|
||
|
|
// No PHI generation needed
|
||
|
|
```
|
||
|
|
|
||
|
|
**Why it works**:
|
||
|
|
1. **Matches Normalized design**: PHI-free continuations
|
||
|
|
2. **SSOT**: k_exit parameters are the canonical final values
|
||
|
|
3. **Simple**: No CFG analysis, no predecessor tracking
|
||
|
|
4. **Maintainable**: Works regardless of Normalized IR structure
|
||
|
|
|
||
|
|
### Option A: Fix PHI Generation (Rejected)
|
||
|
|
|
||
|
|
**Would require**:
|
||
|
|
1. Analyze Normalized IR's tail-call CFG structure
|
||
|
|
2. Map k_exit parameters to PHI inputs
|
||
|
|
3. Handle edge cases (conditional jumps, multiple exits)
|
||
|
|
4. Keep in sync with Normalized IR structure changes
|
||
|
|
|
||
|
|
**Problems**:
|
||
|
|
1. **Violates PHI-free design**: Normalized IR shouldn't generate PHI
|
||
|
|
2. **Complex**: CFG analysis for continuation-based control flow
|
||
|
|
3. **Fragile**: Breaks when Normalized IR structure changes
|
||
|
|
4. **Wrong abstraction**: k_exit params ARE the truth, why create PHI?
|
||
|
|
|
||
|
|
## Solution: ExitReconnectorBox
|
||
|
|
|
||
|
|
### Responsibility
|
||
|
|
|
||
|
|
Generate direct Copy instructions from k_exit env params to host variable_map (Normalized IR only)
|
||
|
|
|
||
|
|
### Contract
|
||
|
|
|
||
|
|
**Input**:
|
||
|
|
- `env_layout.writes`: Variables written in loop
|
||
|
|
- `k_exit_params`: k_exit function parameters (SSOT for final values)
|
||
|
|
- `exit_values`: Mapping from ExitMeta
|
||
|
|
|
||
|
|
**Output**:
|
||
|
|
- Direct variable_map updates (no PHI)
|
||
|
|
|
||
|
|
**Invariant**:
|
||
|
|
- Only used for Normalized shadow path
|
||
|
|
- Standard loops (Pattern 1-4) continue using existing merge pipeline
|
||
|
|
|
||
|
|
### Location
|
||
|
|
|
||
|
|
`src/mir/control_tree/normalized_shadow/exit_reconnector.rs`
|
||
|
|
|
||
|
|
**Rationale**: Normalized-specific logic stays in normalized_shadow/ module
|
||
|
|
|
||
|
|
## Verification Strategy
|
||
|
|
|
||
|
|
### Unit Tests
|
||
|
|
|
||
|
|
1. ExitReconnectorBox::reconnect() basic cases
|
||
|
|
2. Strict mode catches missing variables
|
||
|
|
3. Empty writes → empty bindings
|
||
|
|
|
||
|
|
### Integration Tests
|
||
|
|
|
||
|
|
1. phase131_loop_true_break_once_vm.sh → RC=1 (expected)
|
||
|
|
2. phase131_loop_true_break_once_llvm_exe.sh → RC=1
|
||
|
|
3. Regression: phase130, phase97 (既定挙動不変)
|
||
|
|
|
||
|
|
## Key Insights
|
||
|
|
|
||
|
|
1. **ExitMeta creation was NOT the problem** - it was already working correctly
|
||
|
|
2. **Boundary creation was NOT the problem** - ExitMetaCollector worked correctly
|
||
|
|
3. **The problem was using PHI-based merge** for a continuation-based control flow
|
||
|
|
4. **k_exit env parameters are SSOT** - no merging/PHI needed
|
||
|
|
5. **Option B respects Normalized IR's PHI-free design** - the right abstraction
|
||
|
|
|
||
|
|
## Next Steps
|
||
|
|
|
||
|
|
See `p1.5-option-b-analysis.md` for:
|
||
|
|
- Detailed implementation plan
|
||
|
|
- Testing strategy
|
||
|
|
- Commit sequence
|
||
|
|
- Fail-Fast verifier design
|
||
|
|
|
||
|
|
## Related Documents
|
||
|
|
|
||
|
|
- Phase 131 README: `docs/development/current/main/phases/phase-131/README.md`
|
||
|
|
- Option B Analysis: `docs/development/current/main/phases/phase-131/p1.5-option-b-analysis.md`
|
||
|
|
- Now: `docs/development/current/main/10-Now.md`
|