# Phase 131 P1.5: Option B Analysis (Normalized Exit Reconnection) Status: Analysis Scope: Phase 131 P1.5 root cause analysis and Option B design Related: - Phase 131 README: `docs/development/current/main/phases/phase-131/README.md` - 10-Now.md: `docs/development/current/main/10-Now.md` ## Current Problem ### Symptom ``` [ERROR] ❌ [rust-vm] VM error: Invalid instruction: phi pred mismatch at ValueId(10): no input for predecessor BasicBlockId(42) ``` ### Root Cause **The problem is NOT with ExitMeta creation or boundary wiring.** The trace shows: 1. ✅ **ExitMeta created correctly**: `x` → `ValueId(6)` (k_exit parameter) 2. ✅ **ExitMetaCollector works**: Creates binding `x: join ValueId(6) → host ValueId(2)` 3. ✅ **variable_map updated**: `variable_map['x'] ValueId(2) → ValueId(10)` 4. ❌ **PHI has wrong predecessors**: `ValueId(10) = phi [(BasicBlockId(39), ValueId(4))]` - Expected: `BasicBlockId(42)` - Actual: Only has `BasicBlockId(39)` **The problem**: The existing merge pipeline **assumes a standard loop CFG structure** with multiple predecessors (loop header, exit branches). But Normalized IR uses **tail-call continuations** instead, so the CFG structure is different: ``` Standard Loop (Pattern 1-4): header → body → latch → header (loop back edge) ↓ exit (break edge) → Exit PHI needs inputs from both paths Normalized loop(true) break-once: main → loop_step → loop_body → k_exit → (return) All connections are tail-calls, no loop back edge! → Exit PHI generation logic doesn't match this structure ``` ## Why Option B? ### Option A (Fix PHI generation) - **Problem**: Normalized IR's CFG structure is fundamentally different from standard loops - **Risk**: Trying to generate correct PHI nodes requires deep understanding of: - Which blocks are predecessors in the tail-call CFG - How to map k_exit parameters to PHI inputs - Edge cases with conditional jumps in Normalized IR - **Maintenance**: Every change to Normalized IR structure might break PHI generation ### Option B (Direct env→host wiring, PHI-free) - **Principle**: **Normalized IR's k_exit env parameters ARE the final values (SSOT)** - **Implementation**: Simply copy k_exit env params to host variable_map, no PHI needed - **Why it works**: ``` k_exit(env) receives the final environment state env.x = final value of x → Just write env.x to host variable_map["x"] ``` - **Advantages**: - Respects Normalized IR's **PHI-free** design philosophy - Simpler: No CFG predecessor analysis needed - Maintainable: Works regardless of Normalized IR structure changes - SSOT: k_exit parameters are the canonical source of truth ## Option B Design ### Contract **For Normalized IR only:** - k_exit env parameters represent final variable values (SSOT) - No PHI generation for exit points - Direct Copy instructions: `host_var = k_exit_param` ### Implementation Plan #### 1. New Box: `ExitReconnectorBox` **Location**: `src/mir/control_tree/normalized_shadow/exit_reconnector.rs` **Responsibility**: Generate direct Copy instructions from k_exit params to host variable_map **Input**: - `env_layout.writes`: List of variables written in loop - `k_exit_params`: Vec (k_exit function parameters) - `exit_values`: Vec<(String, ValueId)> (from ExitMeta) **Output**: - `HostExitBindings`: Vec<(String, ValueId)> or BTreeMap **Algorithm**: ```rust for (var_name, k_exit_param_vid) in exit_values { // k_exit_param_vid is the SSOT final value host_exit_bindings.insert(var_name, k_exit_param_vid); } ``` #### 2. Routing Change (`routing.rs`) **Current** (lines 452-480): ```rust // Uses ExitMetaCollector → creates PHI → variable_map update let exit_bindings = ExitMetaCollector::collect(...); let boundary = JoinInlineBoundaryBuilder::new() .with_exit_bindings(exit_bindings) .build(); let exit_phi_result = JoinIRConversionPipeline::execute(...); ``` **New** (Normalized path only): ```rust // Normalized-specific path: bypass PHI generation if is_normalized_shadow { // Use ExitReconnectorBox to wire env→host directly let host_bindings = ExitReconnectorBox::reconnect( self, &join_meta.exit_meta, &env_layout, )?; // Update variable_map directly (no PHI) for (var_name, final_value) in host_bindings { self.variable_ctx.variable_map.insert(var_name, final_value); } // Return void (or final value if needed) Ok(Some(void_value)) } else { // Standard path: use existing merge pipeline (with PHI) let exit_bindings = ExitMetaCollector::collect(...); // ... existing code ... } ``` #### 3. Verifier (strict mode) **Check**: All variables in `env_layout.writes` exist in k_exit parameters ```rust if strict_enabled() { for var in &env_layout.writes { if !k_exit_env.contains_key(var) { freeze_with_hint( "phase131/exit_reconnect/missing", &format!("Variable '{}' in writes but not in k_exit env", var), "Ensure env_layout.writes matches k_exit parameter list (SSOT)" ); } } } ``` ### Why This Bypasses PHI Generation The key insight: **Normalized IR uses continuation parameters, not PHI nodes, for control flow merge** ``` Standard loop (needs PHI): x = 0 loop_header: x_phi = phi [x_init, x_next] ← merge from 2 paths ... Normalized loop (PHI-free): main(env) → loop_step(env) → loop_body(env) → k_exit(env) k_exit receives env.x as parameter ← SSOT, no merge needed ``` ## Testing Strategy ### Unit Tests 1. **ExitReconnectorBox::reconnect()** - Empty writes → empty bindings - Single write → single binding - Multiple writes → multiple bindings (order preserved) - Missing variable in k_exit → strict mode error ### Integration Tests 1. **phase131_loop_true_break_once_min.hako** - Expected: RC=1 (return value) - Verifies: x=0; loop { x=1; break }; return x → 1 2. **Regression** - phase130_if_only_post_if_add_vm.sh (if-only patterns still work) - phase97_next_non_ws_llvm_exe.sh (existing JoinIR patterns) ### Smoke Test Updates **Current failure**: ```bash $ bash tools/smokes/v2/profiles/integration/apps/phase131_loop_true_break_once_vm.sh [FAIL] phi pred mismatch at ValueId(10) ``` **Expected after fix**: ```bash $ bash tools/smokes/v2/profiles/integration/apps/phase131_loop_true_break_once_vm.sh [PASS] Output verified: 1 (exit code: 1) ``` ## Implementation Checklist - [ ] Create `exit_reconnector.rs` with ExitReconnectorBox - [ ] Update `routing.rs` to detect Normalized shadow path - [ ] Implement direct env→host wiring (bypass merge pipeline) - [ ] Add strict mode verifier - [ ] Update unit tests - [ ] Verify phase131 VM smoke - [ ] Verify phase131 LLVM EXE smoke - [ ] Run regression smokes (phase130, phase97) ## Commit Plan 1. `feat(normalized): Phase 131 P1.5 ExitReconnectorBox for host variable propagation` - Add exit_reconnector.rs - Pure function design (env_layout + k_exit params → host bindings) 2. `fix(joinir/dev): bypass PHI generation for Normalized path` - Update routing.rs to detect Normalized shadow - Wire ExitReconnectorBox instead of merge pipeline 3. `test(joinir): Phase 131 P1.5 enforce return parity (VM + LLVM EXE)` - Enable VM/LLVM smoke tests - Verify regression tests 4. `docs: Phase 131 P1.5 DONE` - Update 10-Now.md - Update phase-131/README.md ## Key Principles (SSOT) 1. **PHI禁止維持**: Normalized IR never generates PHI nodes (env parameters are SSOT) 2. **既定挙動不変**: Standard loop patterns (Pattern 1-4) use existing merge pipeline 3. **責務分離**: ExitReconnectorBox handles Normalized-specific wiring 4. **箱化モジュール化**: New reconnection logic in dedicated box, not scattered 5. **Fail-Fast**: Strict mode catches contract violations immediately ## Alternative Considered (Rejected) ### Option A: Fix PHI Generation for Normalized IR **Rejected because**: - Violates Normalized IR's PHI-free design philosophy - Requires complex CFG analysis for tail-call structure - High maintenance burden (CFG structure changes break it) - Not SSOT (k_exit parameters ARE the truth, why generate PHI?) ### Hybrid: PHI for some cases, direct wiring for others **Rejected because**: - Adds complexity (two code paths) - Hard to reason about when each applies - No clear benefit over pure Option B ## Conclusion **Option B is the correct choice** because: 1. **Respects Normalized IR design**: PHI-free continuations 2. **SSOT principle**: k_exit env parameters are the canonical final values 3. **Simplicity**: Direct wiring, no CFG predecessor analysis 4. **Maintainability**: Isolated in ExitReconnectorBox, doesn't touch merge pipeline 5. **Fail-Fast**: Strict mode verifier catches contract violations The root cause was **not** a missing ExitMeta or boundary creation issue. It was **using the wrong merge strategy** (PHI-based) for a control flow structure (continuation-based) that doesn't need it.