Add design documents for Phase 131 P1.5 DirectValue mode: - Root cause analysis of PHI-based exit merge assumptions - Option B (DirectValue) analysis and trade-offs - Implementation guide for exit value reconnection Also add exit_reconnector.rs module stub for future extraction. Related: - Phase 131: loop(true) break-once Normalized support - Normalized shadow path uses continuations, not PHI - Exit values reconnect directly to host variable_map 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
6.6 KiB
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:
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:
- Loop has a header with PHI nodes (for loop variables)
- Exit block needs PHI to merge values from multiple loop exits
- CFG has loop back edges and break edges
But Normalized IR:
- No header PHI (env parameters are passed via tail-calls)
- k_exit env parameter IS the final value (no merge needed)
- 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:
// 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:
- Matches Normalized design: PHI-free continuations
- SSOT: k_exit parameters are the canonical final values
- Simple: No CFG analysis, no predecessor tracking
- Maintainable: Works regardless of Normalized IR structure
Option A: Fix PHI Generation (Rejected)
Would require:
- Analyze Normalized IR's tail-call CFG structure
- Map k_exit parameters to PHI inputs
- Handle edge cases (conditional jumps, multiple exits)
- Keep in sync with Normalized IR structure changes
Problems:
- Violates PHI-free design: Normalized IR shouldn't generate PHI
- Complex: CFG analysis for continuation-based control flow
- Fragile: Breaks when Normalized IR structure changes
- 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 loopk_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
- ExitReconnectorBox::reconnect() basic cases
- Strict mode catches missing variables
- Empty writes → empty bindings
Integration Tests
- phase131_loop_true_break_once_vm.sh → RC=1 (expected)
- phase131_loop_true_break_once_llvm_exe.sh → RC=1
- Regression: phase130, phase97 (既定挙動不変)
Key Insights
- ExitMeta creation was NOT the problem - it was already working correctly
- Boundary creation was NOT the problem - ExitMetaCollector worked correctly
- The problem was using PHI-based merge for a continuation-based control flow
- k_exit env parameters are SSOT - no merging/PHI needed
- 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