feat(llvm): Phase 132-P0 - block_end_values tuple-key fix for cross-function isolation

## Problem
`block_end_values` used block ID only as key, causing collisions when
multiple functions share the same block IDs (e.g., bb0 in both
condition_fn and main).

## Root Cause
- condition_fn's bb0 → block_end_values[0]
- main's bb0 → block_end_values[0] (OVERWRITES!)
- PHI resolution gets wrong snapshot → dominance error

## Solution (Box-First principle)
Change key from `int` to `Tuple[str, int]` (func_name, block_id):

```python
# Before
block_end_values: Dict[int, Dict[int, ir.Value]]

# After
block_end_values: Dict[Tuple[str, int], Dict[int, ir.Value]]
```

## Files Modified (Python - 6 files)

1. `llvm_builder.py` - Type annotation update
2. `function_lower.py` - Pass func_name to lower_blocks
3. `block_lower.py` - Use tuple keys for snapshot save/load
4. `resolver.py` - Add func_name parameter to resolve_incoming
5. `wiring.py` - Thread func_name through PHI wiring
6. `phi_manager.py` - Debug traces

## Files Modified (Rust - cleanup)

- Removed deprecated `loop_to_join.rs` (297 lines deleted)
- Updated pattern lowerers for cleaner exit handling
- Added lifecycle management improvements

## Verification

-  Pattern 1: VM RC: 3, LLVM Result: 3 (no regression)
- ⚠️ Case C: Still has dominance error (separate root cause)
  - Needs additional scope fixes (phi_manager, resolver caches)

## Design Principles

- **Box-First**: Each function is an isolated Box with scoped state
- **SSOT**: (func_name, block_id) uniquely identifies block snapshots
- **Fail-Fast**: No cross-function state contamination

## Known Issues (Phase 132-P1)

Other function-local state needs same treatment:
- phi_manager.predeclared
- resolver caches (i64_cache, ptr_cache, etc.)
- builder._jump_only_blocks

## Documentation

- docs/development/current/main/investigations/phase132-p0-case-c-root-cause.md
- docs/development/current/main/investigations/phase132-p0-tuple-key-implementation.md

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-15 05:36:50 +09:00
parent 18d56d5b88
commit 3f58f34592
22 changed files with 1076 additions and 384 deletions

View File

@ -137,8 +137,13 @@ def nearest_pred_on_path(
return None
def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[int, int]]):
"""Wire PHI incoming edges for (block_id, dst_vid) using declared (decl_b, v_src) pairs."""
def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[int, int]], func_name: str = "unknown"):
"""Wire PHI incoming edges for (block_id, dst_vid) using declared (decl_b, v_src) pairs.
Phase 132-P0: Accept func_name for tuple-key (func_name, block_id) resolution.
Args:
func_name: Function name for tuple-key (func_name, block_id) in block_end_values
"""
bb = builder.bb_map.get(block_id)
if bb is None:
return
@ -146,7 +151,8 @@ def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[in
phi = None
try:
snap = getattr(builder, 'block_end_values', {}) or {}
cur = (snap.get(int(block_id), {}) or {}).get(int(dst_vid))
# Phase 132-P0: Use tuple-key (func_name, block_id)
cur = (snap.get((func_name, int(block_id)), {}) or {}).get(int(dst_vid))
if cur is not None and hasattr(cur, 'add_incoming'):
# Ensure it belongs to the same block
cur_bb_name = getattr(getattr(cur, 'basic_block', None), 'name', None)
@ -210,7 +216,8 @@ def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[in
trace({"phi": "wire_replaced_src", "original": original_vs, "replaced": vs})
try:
# P0-4: Use resolve_incoming for PHI incoming values
val = builder.resolver.resolve_incoming(pred_match, vs)
# Phase 132-P0: Pass func_name for tuple-key resolution
val = builder.resolver.resolve_incoming(pred_match, vs, func_name=func_name)
trace({"phi": "wire_resolved", "vs": vs, "pred": pred_match, "val_type": type(val).__name__})
except Exception as e:
trace({"phi": "wire_resolve_fail", "vs": vs, "pred": pred_match, "error": str(e)})
@ -239,7 +246,13 @@ def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[in
return wired
def finalize_phis(builder):
def finalize_phis(builder, func_name: str = "unknown"):
"""Finalize PHI nodes by wiring their incoming edges.
Phase 132-P0: Pass func_name for tuple-key (func_name, block_id) resolution.
Args:
func_name: Function name for tuple-key (func_name, block_id) in block_end_values
"""
total_blocks = 0
total_dsts = 0
total_wired = 0
@ -247,7 +260,7 @@ def finalize_phis(builder):
total_blocks += 1
for dst_vid, incoming in (dst_map or {}).items():
total_dsts += 1
wired = wire_incomings(builder, int(block_id), int(dst_vid), incoming)
wired = wire_incomings(builder, int(block_id), int(dst_vid), incoming, func_name=func_name)
total_wired += int(wired or 0)
trace({"phi": "finalize", "block": int(block_id), "dst": int(dst_vid), "wired": int(wired or 0)})
trace({"phi": "finalize_summary", "blocks": int(total_blocks), "dsts": int(total_dsts), "incoming_wired": int(total_wired)})