feat(llvm): Phase 132 - Pattern 1 exit value parity fix + Box-First refactoring

## Phase 132: Exit PHI Value Parity Fix

### Problem
Pattern 1 (Simple While) returned 0 instead of final loop variable value (3)
- VM: RC: 3  (correct)
- LLVM: Result: 0  (wrong)

### Root Cause (Two Layers)
1. **JoinIR/Boundary**: Missing exit_bindings → ExitLineReconnector not firing
2. **LLVM Python**: block_end_values snapshot dropping PHI values

### Fix
**JoinIR** (simple_while_minimal.rs):
- Jump(k_exit, [i_param]) passes exit value

**Boundary** (pattern1_minimal.rs):
- Added LoopExitBinding with carrier_name="i", role=LoopState
- Enables ExitLineReconnector to update variable_map

**LLVM** (block_lower.py):
- Use predeclared_ret_phis for reliable PHI filtering
- Protect builder.vmap PHIs from overwrites (SSOT principle)

### Result
-  VM: RC: 3
-  LLVM: Result: 3
-  VM/LLVM parity achieved

## Phase 132-Post: Box-First Refactoring

### Rust Side
**JoinModule::require_function()** (mod.rs):
- Encapsulate function search logic
- 10 lines → 1 line (90% reduction)
- Reusable for Pattern 2-5

### Python Side
**PhiManager Box** (phi_manager.py - new):
- Centralized PHI lifecycle management
- 47 lines → 8 lines (83% reduction)
- SSOT: builder.vmap owns PHIs
- Fail-Fast: No silent overwrites

**Integration**:
- LLVMBuilder: Added phi_manager
- block_lower.py: Delegated to PhiManager
- tagging.py: Register PHIs with manager

### Documentation
**New Files**:
- docs/development/architecture/exit-phi-design.md
- docs/development/current/main/investigations/phase132-llvm-exit-phi-wrong-result.md
- docs/development/current/main/phases/phase-132/

**Updated**:
- docs/development/current/main/10-Now.md
- docs/development/current/main/phase131-3-llvm-lowering-inventory.md

### Design Principles
- Box-First: Logic encapsulated in classes/methods
- SSOT: Single Source of Truth (builder.vmap for PHIs)
- Fail-Fast: Early explicit failures, no fallbacks
- Separation of Concerns: 3-layer architecture

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-15 03:17:31 +09:00
parent a955dd6b18
commit 447d4ea246
16 changed files with 669 additions and 54 deletions

View File

@ -75,6 +75,7 @@ def setup_phi_placeholders(builder, blocks: List[Dict[str, Any]]):
ph = ensure_phi(builder, bid0, dst0, bb0)
# Keep a strong reference as a predeclared placeholder so
# later ensure_phi calls during finalize re-use the same SSA node.
# Phase 132-Post: Register PHI with PhiManager Box
try:
if not hasattr(builder, 'predeclared_ret_phis') or builder.predeclared_ret_phis is None:
builder.predeclared_ret_phis = {}
@ -82,6 +83,9 @@ def setup_phi_placeholders(builder, blocks: List[Dict[str, Any]]):
builder.predeclared_ret_phis = {}
try:
builder.predeclared_ret_phis[(int(bid0), int(dst0))] = ph
# Phase 132-Post: Box-First - register with PhiManager
if hasattr(builder, 'phi_manager'):
builder.phi_manager.register_phi(int(bid0), int(dst0), ph)
if debug_mode:
print(f"[phi_wiring/setup] Created PHI placeholder for v{dst0} in bb{bid0}")
except Exception:

View File

@ -84,6 +84,13 @@ def ensure_phi(builder, block_id: int, dst_vid: int, bb: ir.Block) -> ir.Instruc
pass
ph = b.phi(builder.i64, name=f"phi_{dst_vid}")
# Phase 132 Debug: Check if basic_block is set correctly
import os, sys
if os.environ.get('NYASH_PHI_ORDERING_DEBUG') == '1' or os.environ.get('NYASH_LLVM_VMAP_TRACE') == '1':
phi_bb = getattr(ph, 'basic_block', None)
phi_bb_name = getattr(phi_bb, 'name', None) if phi_bb is not None else None
bb_name = getattr(bb, 'name', None)
print(f"[phi_wiring/create] v{dst_vid} PHI created: phi.basic_block={phi_bb_name} expected={bb_name}", file=sys.stderr)
builder.vmap[dst_vid] = ph
trace({"phi": "ensure_create", "block": int(block_id), "dst": int(dst_vid), "after_term": block_has_terminator})
return ph