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:
@ -9,74 +9,8 @@ from phi_wiring import (
|
||||
finalize_phis as _finalize_phis,
|
||||
build_succs as _build_succs,
|
||||
)
|
||||
|
||||
|
||||
def _clear_function_local_state(builder):
|
||||
"""
|
||||
Phase 132-P1: Clear all function-local state at function boundary.
|
||||
|
||||
Box-First Principle: Prevents cross-function ValueId/BlockId collisions
|
||||
by clearing all function-scoped state when entering a new function.
|
||||
|
||||
Cleared state:
|
||||
1. PhiManager.predeclared - (bid, vid) -> phi_value
|
||||
2. Resolver caches - i64_cache, ptr_cache, f64_cache, _end_i64_cache
|
||||
3. Jump-only blocks - _jump_only_blocks
|
||||
4. PHI incomings - block_phi_incomings, def_blocks
|
||||
5. String/NewBox caches - function-local string handling
|
||||
"""
|
||||
try:
|
||||
# 1. PhiManager predeclared PHIs (bid, vid) -> phi_value
|
||||
if hasattr(builder, 'phi_manager') and hasattr(builder.phi_manager, 'predeclared'):
|
||||
builder.phi_manager.predeclared.clear()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
# 2. Resolver caches (all keyed by block/value IDs)
|
||||
builder.resolver.i64_cache.clear()
|
||||
builder.resolver.ptr_cache.clear()
|
||||
builder.resolver.f64_cache.clear()
|
||||
if hasattr(builder.resolver, '_end_i64_cache'):
|
||||
builder.resolver._end_i64_cache.clear()
|
||||
|
||||
# 3. String-related caches (function-local)
|
||||
if hasattr(builder.resolver, 'string_ids'):
|
||||
builder.resolver.string_ids.clear()
|
||||
if hasattr(builder.resolver, 'string_literals'):
|
||||
builder.resolver.string_literals.clear()
|
||||
if hasattr(builder.resolver, 'string_ptrs'):
|
||||
builder.resolver.string_ptrs.clear()
|
||||
if hasattr(builder.resolver, 'length_cache'):
|
||||
builder.resolver.length_cache.clear()
|
||||
|
||||
# 4. NewBox→string-arg hints (function-local)
|
||||
if hasattr(builder.resolver, 'newbox_string_args') and isinstance(builder.resolver.newbox_string_args, dict):
|
||||
builder.resolver.newbox_string_args.clear()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
# 5. Jump-only blocks (bid -> pred_bid)
|
||||
if hasattr(builder, '_jump_only_blocks'):
|
||||
builder._jump_only_blocks.clear()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
# 6. PHI incomings and def_blocks (function-local)
|
||||
if hasattr(builder, 'block_phi_incomings'):
|
||||
builder.block_phi_incomings.clear()
|
||||
if hasattr(builder, 'def_blocks'):
|
||||
builder.def_blocks.clear()
|
||||
|
||||
# Also clear resolver's references to these
|
||||
if hasattr(builder.resolver, 'block_phi_incomings'):
|
||||
builder.resolver.block_phi_incomings = {}
|
||||
if hasattr(builder.resolver, 'def_blocks'):
|
||||
builder.resolver.def_blocks.clear()
|
||||
except Exception:
|
||||
pass
|
||||
from context import FunctionLowerContext
|
||||
from phi_manager import PhiManager
|
||||
|
||||
|
||||
def lower_function(builder, func_data: Dict[str, Any]):
|
||||
@ -117,8 +51,24 @@ def lower_function(builder, func_data: Dict[str, Any]):
|
||||
builder.bb_map.clear()
|
||||
except Exception:
|
||||
builder.bb_map = {}
|
||||
# Phase 132-P1: Clear all function-local state (prevent cross-function collision)
|
||||
_clear_function_local_state(builder)
|
||||
|
||||
# Phase 132-P1: Create function-local context Box
|
||||
# This automatically isolates all function-scoped state
|
||||
context = FunctionLowerContext(name)
|
||||
|
||||
# Initialize PHI manager within context
|
||||
context.phi_manager = PhiManager()
|
||||
|
||||
# Connect builder attributes to context storage (for backward compatibility)
|
||||
builder.phi_manager = context.phi_manager
|
||||
builder.block_phi_incomings = context.block_phi_incomings
|
||||
builder.def_blocks = context.def_blocks
|
||||
|
||||
# Bind resolver to context (redirects caches to context storage)
|
||||
builder.resolver.bind_context(context)
|
||||
|
||||
# Store context in builder for access by sub-components
|
||||
builder.context = context
|
||||
|
||||
# Phase 131-15-P1: Load value_types metadata from JSON into resolver
|
||||
try:
|
||||
@ -248,8 +198,9 @@ def lower_function(builder, func_data: Dict[str, Any]):
|
||||
if os.environ.get('NYASH_LLVM_PREPASS_IFMERGE') == '1':
|
||||
plan = plan_ret_phi_predeclare(block_by_id)
|
||||
if plan:
|
||||
if not hasattr(builder, 'block_phi_incomings') or builder.block_phi_incomings is None:
|
||||
builder.block_phi_incomings = {}
|
||||
# Phase 132-P1: block_phi_incomings already points to context storage
|
||||
# No need to reassign - just ensure it exists
|
||||
pass
|
||||
for bbid, ret_vid in plan.items():
|
||||
try:
|
||||
preds_raw = [p for p in builder.preds.get(bbid, []) if p != bbid]
|
||||
@ -297,8 +248,8 @@ def lower_function(builder, func_data: Dict[str, Any]):
|
||||
except Exception:
|
||||
pass
|
||||
return uses
|
||||
if not hasattr(builder, 'block_phi_incomings') or builder.block_phi_incomings is None:
|
||||
builder.block_phi_incomings = {}
|
||||
# Phase 132-P1: block_phi_incomings already points to context storage
|
||||
# No need to reassign - it's already initialized
|
||||
for bid, blk in block_by_id.items():
|
||||
try:
|
||||
preds_raw = [p for p in local_preds.get(int(bid), []) if p != int(bid)]
|
||||
@ -339,14 +290,14 @@ def lower_function(builder, func_data: Dict[str, Any]):
|
||||
loop_plan = None
|
||||
|
||||
# Phase 131-4 Pass A: Lower non-terminator instructions (terminators deferred)
|
||||
# Phase 132-P0: Pass func.name for tuple-key (func_name, block_id)
|
||||
# Phase 132-P1: Pass context Box for function-local state isolation
|
||||
from builders.block_lower import lower_blocks as _lower_blocks
|
||||
_lower_blocks(builder, func, block_by_id, order, loop_plan, func_name=name)
|
||||
_lower_blocks(builder, func, block_by_id, order, loop_plan, context)
|
||||
|
||||
# Phase 131-14-B Pass B: Resolve jump-only block snapshots (BEFORE PHI finalization)
|
||||
# Phase 132-P0: Pass func_name for tuple-key
|
||||
# Phase 132-P1: Pass context Box for function-local state isolation
|
||||
from builders.block_lower import resolve_jump_only_snapshots as _resolve_jump_only_snapshots
|
||||
_resolve_jump_only_snapshots(builder, block_by_id, func_name=name)
|
||||
_resolve_jump_only_snapshots(builder, block_by_id, context)
|
||||
|
||||
# Optional: capture lowering ctx for downstream helpers
|
||||
try:
|
||||
@ -370,8 +321,8 @@ def lower_function(builder, func_data: Dict[str, Any]):
|
||||
pass
|
||||
|
||||
# Phase 131-4 Pass B (now Pass B2): Finalize PHIs (wires incoming edges)
|
||||
# Phase 132-P0: Pass func_name for tuple-key resolution
|
||||
_finalize_phis(builder, func_name=name)
|
||||
# Phase 132-P1: Pass context Box for function-local state isolation
|
||||
_finalize_phis(builder, context)
|
||||
|
||||
# Phase 131-4 Pass C: Lower deferred terminators (after PHIs are placed)
|
||||
from builders.block_lower import lower_terminators as _lower_terminators
|
||||
|
||||
Reference in New Issue
Block a user