Files
hakorune/docs/development/current/main/phases/phase-131/p1.5-option-b-analysis.md

274 lines
8.9 KiB
Markdown
Raw Normal View History

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