# 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<ValueId> (k_exit function parameters)
- `exit_values`: Vec<(String, ValueId)> (from ExitMeta)

**Output**:
- `HostExitBindings`: Vec<(String, ValueId)> or BTreeMap<String, ValueId>

**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.
