refactor(llvm): Phase 132-P1 - FunctionLowerContext Box isolation
Structural fix for cross-function state leakage bugs discovered in Phase 131-132. Problem: - Function-local state (block_end_values, def_blocks, phi_manager, caches) was globally managed, causing collisions across functions - Required manual clearing via _clear_function_local_state() (~80 lines) - Tuple-key workaround (func_name, block_id) added complexity Solution: FunctionLowerContext Box - Encapsulates all function-scoped state in a dedicated Box - Automatic lifetime management (created at entry, destroyed at exit) - Eliminates manual state clearing - Simplifies from tuple-key to simple block_id access Implementation: 1. Created src/llvm_py/context/function_lower_context.py (150+ lines) - block_end_values, def_blocks, jump_only_blocks - phi_manager, resolver_caches - Helper methods: get/set_block_snapshot(), register_def(), etc. 2. Updated function_lower.py - Creates context at function entry - Binds resolver to context for cache isolation - Removed _clear_function_local_state() (~80 lines) 3. Updated block_lower.py - Changed tuple-key (func_name, block_id) to simple block_id - Access via context.get_block_snapshot() / context.set_block_snapshot() 4. Updated resolver.py, phi_wiring/wiring.py, phi_wiring/tagging.py - All state access now through context 5. Fixed critical bug in phi_wiring/tagging.py - setup_phi_placeholders() was breaking context connection - Changed reassignment to .clear()/.update() to preserve reference Benefits: - ✅ Automatic state isolation (no manual clearing) - ✅ Clear ownership (one context per function) - ✅ Eliminated tuple-keys (simpler code) - ✅ Prevents bugs by design (cross-function leakage impossible) Test results: - ✅ Phase 87 smoke test: PASS - ✅ Phase 132 smoke test: PASS (both cases) - ✅ STRICT mode: Works with multi-function inputs - ✅ No regression Files modified: 8 (2 new, 6 updated) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -35,8 +35,12 @@ class Resolver:
|
||||
except Exception:
|
||||
self.global_vmap = None
|
||||
self.global_bb_map = None
|
||||
|
||||
|
||||
# Phase 132-P1: Context reference (will be set by bind_context)
|
||||
self.context = None
|
||||
|
||||
# Caches: (block_name, value_id) -> llvm value
|
||||
# Phase 132-P1: These are now managed via context, but kept for backward compatibility
|
||||
self.i64_cache: Dict[Tuple[str, int], ir.Value] = {}
|
||||
self.ptr_cache: Dict[Tuple[str, int], ir.Value] = {}
|
||||
self.f64_cache: Dict[Tuple[str, int], ir.Value] = {}
|
||||
@ -49,7 +53,7 @@ class Resolver:
|
||||
self.string_ids: set[int] = set()
|
||||
# Cache for repeated string length queries when immutably known
|
||||
self.length_cache: Dict[int, ir.Value] = {}
|
||||
|
||||
|
||||
# Type shortcuts
|
||||
self.i64 = ir.IntType(64)
|
||||
self.i8p = ir.IntType(8).as_pointer()
|
||||
@ -58,14 +62,39 @@ class Resolver:
|
||||
self._end_i64_cache: Dict[Tuple[int, int], ir.Value] = {}
|
||||
# Lifetime hint: value_id -> set(block_id) where it's known to be defined
|
||||
# Populated by the builder when available.
|
||||
# Phase 132-P1: Now managed via context, but kept for backward compatibility
|
||||
self.def_blocks = {}
|
||||
# Optional: block -> { dst_vid -> [(pred_bid, val_vid), ...] } for PHIs from MIR JSON
|
||||
self.block_phi_incomings = {}
|
||||
# P0-1: SSOT for end-of-block values (snapshots)
|
||||
# Phase 132-P1: Now managed via context, but kept for backward compatibility
|
||||
self.block_end_values = {}
|
||||
# P0-3: Circular reference detection (hang prevention)
|
||||
self._visited: Set[Tuple[int, int]] = set()
|
||||
|
||||
def bind_context(self, context):
|
||||
"""Phase 132-P1: Bind resolver to function-local context Box.
|
||||
|
||||
This connects the resolver's caches to the context's function-local storage,
|
||||
enabling automatic isolation between functions.
|
||||
|
||||
Args:
|
||||
context: FunctionLowerContext instance
|
||||
"""
|
||||
self.context = context
|
||||
# Redirect to context-managed storage
|
||||
self.i64_cache = context.resolver_i64_cache
|
||||
self.ptr_cache = context.resolver_ptr_cache
|
||||
self.f64_cache = context.resolver_f64_cache
|
||||
self._end_i64_cache = context.resolver_end_i64_cache
|
||||
self.string_ids = context.resolver_string_ids
|
||||
self.string_literals = context.resolver_string_literals
|
||||
self.string_ptrs = context.resolver_string_ptrs
|
||||
self.length_cache = context.resolver_length_cache
|
||||
self.def_blocks = context.def_blocks
|
||||
self.block_phi_incomings = context.block_phi_incomings
|
||||
# Note: block_end_values access goes through context methods
|
||||
|
||||
def mark_string(self, value_id: int) -> None:
|
||||
try:
|
||||
self.string_ids.add(int(value_id))
|
||||
@ -124,9 +153,9 @@ class Resolver:
|
||||
# Non-STRICT: fallback to 0
|
||||
return ir.Constant(ir.IntType(64), 0)
|
||||
|
||||
def resolve_incoming(self, pred_block_id: int, value_id: int, func_name: str = "unknown") -> ir.Value:
|
||||
def resolve_incoming(self, pred_block_id: int, value_id: int, context=None) -> ir.Value:
|
||||
"""P0-2: PHI incoming resolution (snapshot-only reference)
|
||||
Phase 132-P0: Use tuple-key (func_name, block_id) to prevent cross-function collision
|
||||
Phase 132-P1: Use context Box for function-local state isolation
|
||||
|
||||
Used for resolving PHI incoming values from predecessor blocks.
|
||||
Only looks at block_end_values snapshot, never vmap_cur.
|
||||
@ -134,19 +163,26 @@ class Resolver:
|
||||
Args:
|
||||
pred_block_id: Predecessor block ID
|
||||
value_id: Value ID to resolve from predecessor
|
||||
func_name: Function name for tuple-key (func_name, block_id) in block_end_values
|
||||
context: FunctionLowerContext Box (if None, uses self.context)
|
||||
|
||||
Returns:
|
||||
LLVM IR value (i64)
|
||||
"""
|
||||
# Phase 132-P0: Use tuple-key (func_name, block_id)
|
||||
snapshot = self.block_end_values.get((func_name, pred_block_id), {})
|
||||
# Phase 132-P1: Use context.get_block_snapshot (simple block_id key)
|
||||
ctx = context if context is not None else self.context
|
||||
if ctx is not None:
|
||||
snapshot = ctx.get_block_snapshot(pred_block_id)
|
||||
else:
|
||||
# Fallback for backward compatibility (legacy code path)
|
||||
snapshot = self.block_end_values.get(pred_block_id, {})
|
||||
|
||||
val = snapshot.get(value_id)
|
||||
if val is not None:
|
||||
return val
|
||||
|
||||
# Fail-Fast: snapshot miss → structural bug
|
||||
if os.environ.get('NYASH_LLVM_STRICT') == '1':
|
||||
func_name = ctx.func_name if ctx else "unknown"
|
||||
raise RuntimeError(
|
||||
f"[LLVM_PY/STRICT] resolve_incoming: v{value_id} not in {func_name}:bb{pred_block_id} snapshot. "
|
||||
f"Available: {sorted(snapshot.keys())}"
|
||||
|
||||
Reference in New Issue
Block a user