diff --git a/src/llvm_py/builders/block_lower.py b/src/llvm_py/builders/block_lower.py index 15e6bab6..bec132bc 100644 --- a/src/llvm_py/builders/block_lower.py +++ b/src/llvm_py/builders/block_lower.py @@ -58,9 +58,9 @@ class DeferredTerminator(NamedTuple): vmap_snapshot: Dict[int, ir.Value] -def resolve_jump_only_snapshots(builder, block_by_id: Dict[int, Dict[str, Any]], func_name: str = "unknown"): +def resolve_jump_only_snapshots(builder, block_by_id: Dict[int, Dict[str, Any]], context): """Phase 131-14-B P0-2: Resolve jump-only block snapshots (Pass B). - 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. This function runs AFTER all blocks have been lowered (Pass A) but BEFORE PHI finalization. It resolves snapshots for jump-only blocks by following @@ -71,14 +71,14 @@ def resolve_jump_only_snapshots(builder, block_by_id: Dict[int, Dict[str, Any]], SSOT: Snapshots are based on CFG structure, not processing order. Args: - func_name: Function name for tuple-key (func_name, block_id) in block_end_values + context: FunctionLowerContext Box containing function-local state """ import sys strict_mode = os.environ.get('NYASH_LLVM_STRICT') == '1' trace_vmap = os.environ.get('NYASH_LLVM_TRACE_VMAP') == '1' - jump_only = getattr(builder, '_jump_only_blocks', {}) + jump_only = context.jump_only_blocks if not jump_only: if trace_vmap: print("[vmap/resolve/passB] No jump-only blocks to resolve", file=sys.stderr) @@ -114,9 +114,9 @@ def resolve_jump_only_snapshots(builder, block_by_id: Dict[int, Dict[str, Any]], return resolved[bid] # Normal block - already has snapshot from Pass A - # Phase 132-P0: Use tuple-key (func_name, block_id) - if (func_name, bid) in builder.block_end_values: - snapshot = builder.block_end_values[(func_name, bid)] + # Phase 132-P1: Use context.block_end_values (simple block_id key) + snapshot = context.get_block_snapshot(bid) + if snapshot: if trace_vmap: print( f"[vmap/resolve/passB] bb{bid} is normal block with snapshot " @@ -171,10 +171,10 @@ def resolve_jump_only_snapshots(builder, block_by_id: Dict[int, Dict[str, Any]], return {} # Resolve all jump-only blocks - # Phase 132-P0: Use tuple-key (func_name, block_id) + # Phase 132-P1: Use context.set_block_snapshot (simple block_id key) for bid in sorted(jump_only.keys()): snapshot = resolve(bid) - builder.block_end_values[(func_name, bid)] = snapshot + context.set_block_snapshot(bid, snapshot) if trace_vmap: print( @@ -187,12 +187,12 @@ def resolve_jump_only_snapshots(builder, block_by_id: Dict[int, Dict[str, Any]], print(f"[vmap/resolve/passB] Pass B complete: resolved {len(jump_only)} jump-only blocks", file=sys.stderr) -def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, Any]], order: List[int], loop_plan: Dict[str, Any] | None, func_name: str = "unknown"): +def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, Any]], order: List[int], loop_plan: Dict[str, Any] | None, context): """Lower blocks in multi-pass to ensure PHIs are always before terminators. Phase 131-4: Multi-pass block lowering architecture Phase 131-14-B: Two-pass snapshot resolution - Phase 132-P0: Tuple-key (func_name, block_id) to prevent cross-function collision + Phase 132-P1: Use context Box for function-local state isolation - Pass A: Lower non-terminator instructions only (terminators deferred) - jump-only blocks: record metadata only, NO snapshot resolution - Pass B: PHI finalization happens in function_lower.py @@ -203,7 +203,7 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An other instructions, and terminators must be last. Args: - func_name: Function name for tuple-key (func_name, block_id) in block_end_values + context: FunctionLowerContext Box containing function-local state """ skipped: set[int] = set() if loop_plan is not None: @@ -329,10 +329,10 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An else: body_ops.append(inst) # Per-block SSA map - # Phase 132-Post: Use PhiManager Box for PHI filtering (Box-First principle) + # Phase 132-P1: Use context.phi_manager for PHI filtering (Box-First principle) vmap_cur: Dict[int, ir.Value] = {} try: - vmap_cur = builder.phi_manager.filter_vmap_preserve_phis( + vmap_cur = context.phi_manager.filter_vmap_preserve_phis( builder.vmap or {}, int(bid) ) @@ -414,8 +414,9 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An created_ids.append(dst) # P0-1.5: Update def_blocks IMMEDIATELY after instruction lowering # This ensures resolver can detect defined_here for same-block uses + # Phase 132-P1: Use context.add_def_block try: - builder.def_blocks.setdefault(dst, set()).add(block_data.get("id", 0)) + context.add_def_block(dst, block_data.get("id", 0)) except Exception: pass except Exception: @@ -434,12 +435,12 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An print(f"[vmap/id] Pass A bb{bid} snapshot id={id(vmap_snapshot)} keys={sorted(vmap_snapshot.keys())[:10]}", file=sys.stderr) # Phase 131-7: Sync ALL created values to global vmap (not just PHIs) # This ensures Pass C (deferred terminators) can access values from Pass A - # Phase 132-Post: Use PhiManager Box for PHI protection (Box-First principle) + # Phase 132-P1: Use context.phi_manager for PHI protection (Box-First principle) try: # Create sync dict from created values only sync_dict = {vid: vmap_cur[vid] for vid in created_ids if vid in vmap_cur} # PhiManager.sync_protect_phis ensures PHIs are never overwritten (SSOT) - builder.phi_manager.sync_protect_phis(builder.vmap, sync_dict) + context.phi_manager.sync_protect_phis(builder.vmap, sync_dict) if os.environ.get('NYASH_LLVM_VMAP_TRACE') == '1': print(f"[vmap/sync] bb{bid} synced {len(sync_dict)} values to builder.vmap (PHIs protected)", file=sys.stderr) except Exception: @@ -449,10 +450,6 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An strict_mode = os.environ.get('NYASH_LLVM_STRICT') == '1' trace_vmap = os.environ.get('NYASH_LLVM_TRACE_VMAP') == '1' - # Initialize jump_only_blocks dict if not exists - if not hasattr(builder, '_jump_only_blocks'): - builder._jump_only_blocks = {} - is_jump_only = is_jump_only_block(block_data) if trace_vmap: print( @@ -483,7 +480,7 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An elif len(preds_list) == 1: # Single predecessor - record metadata for Pass B resolution pred_bid = preds_list[0] - builder._jump_only_blocks[bid] = pred_bid + context.register_jump_only_block(bid, pred_bid) # DO NOT create snapshot here - will be resolved in Pass B # Set snap to None to indicate "skip storing in block_end_values" @@ -516,7 +513,7 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An snap = dict(vmap_cur) # Phase 131-14-B: Only store snapshot if not deferred (snap is not None) - # Phase 132-P0: Use tuple-key (func_name, block_id) + # Phase 132-P1: Use context.set_block_snapshot (simple block_id key) if snap is not None: try: keys = sorted(list(snap.keys())) @@ -525,8 +522,8 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An trace_phi_json({"phi": "snapshot", "block": int(bid), "keys": [int(k) for k in keys[:20]]}) for vid in created_ids: if vid in vmap_cur: - builder.def_blocks.setdefault(vid, set()).add(block_data.get("id", 0)) - builder.block_end_values[(func_name, bid)] = snap + context.add_def_block(vid, block_data.get("id", 0)) + context.set_block_snapshot(bid, snap) else: # Jump-only block with deferred snapshot - don't store yet if trace_vmap: diff --git a/src/llvm_py/builders/function_lower.py b/src/llvm_py/builders/function_lower.py index 65e1aac1..16c33b3e 100644 --- a/src/llvm_py/builders/function_lower.py +++ b/src/llvm_py/builders/function_lower.py @@ -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 diff --git a/src/llvm_py/context/__init__.py b/src/llvm_py/context/__init__.py new file mode 100644 index 00000000..4cda5028 --- /dev/null +++ b/src/llvm_py/context/__init__.py @@ -0,0 +1,9 @@ +""" +Phase 132-P1: Function-local context module + +Box-First principle: Isolate function-level state to prevent cross-function collisions. +""" + +from .function_lower_context import FunctionLowerContext + +__all__ = ['FunctionLowerContext'] diff --git a/src/llvm_py/context/function_lower_context.py b/src/llvm_py/context/function_lower_context.py new file mode 100644 index 00000000..e2214ada --- /dev/null +++ b/src/llvm_py/context/function_lower_context.py @@ -0,0 +1,148 @@ +""" +Function-local state container for LLVM lowering. + +Phase 132-P1: Box-First isolation of function-level state. +Prevents cross-function state leakage and collisions. +""" + +from typing import Dict, Set, Any +from llvmlite import ir + + +class FunctionLowerContext: + """ + Box containing all function-local lowering state. + + Lifetime: Created at function entry, destroyed at function exit. + Scope: Single MIR function lowering. + + Benefits: + - Automatic state isolation (no manual clearing needed) + - Clear ownership (context belongs to one function) + - Easy to test (create context, lower, verify) + + Design Principle (Box-First): + All function-scoped state lives here. This Box is created fresh for each + function and automatically destroyed when the function lowering completes, + ensuring zero cross-function contamination. + """ + + def __init__(self, func_name: str): + """Initialize function-local context. + + Args: + func_name: Name of the function being lowered (for debugging/tracing) + """ + self.func_name = func_name + + # Block snapshot state (was: builder.block_end_values with tuple-key) + # Maps: block_id -> {value_id -> ir.Value} + # SSOT: End-of-block value snapshots for PHI wiring + self.block_end_values: Dict[int, Dict[int, ir.Value]] = {} + + # Definition tracking (was: builder.def_blocks) + # Maps: value_id -> set of block_ids where it's defined + # Used by resolver to determine if value is defined in current block + self.def_blocks: Dict[int, Set[int]] = {} + + # Jump-only blocks (was: builder._jump_only_blocks) + # Maps: jump_only_block_id -> predecessor_block_id + # Used for snapshot resolution in Pass B + self.jump_only_blocks: Dict[int, int] = {} + + # PHI management (was: builder.phi_manager) + # Will be set to PhiManager instance + self.phi_manager: Any = None # Type: PhiManager (avoid circular import) + + # Resolver caches (function-local) + # These caches are keyed by (block_name, value_id) and must be + # cleared between functions to prevent cross-function collisions + self.resolver_i64_cache: Dict = {} + self.resolver_ptr_cache: Dict = {} + self.resolver_f64_cache: Dict = {} + self.resolver_end_i64_cache: Dict = {} + + # String-related caches (function-local) + self.resolver_string_ids: Set[int] = set() + self.resolver_string_literals: Dict[int, str] = {} + self.resolver_string_ptrs: Dict[int, ir.Value] = {} + self.resolver_length_cache: Dict[int, ir.Value] = {} + + # NewBox→string-arg hints (function-local) + self.resolver_newbox_string_args: Dict = {} + + # PHI incomings metadata (function-local) + # Maps: block_id -> {value_id -> [(pred_bid, val_vid), ...]} + self.block_phi_incomings: Dict[int, Dict[int, Any]] = {} + + def get_block_snapshot(self, block_id: int) -> Dict[int, ir.Value]: + """Get end-of-block value snapshot for a block. + + Args: + block_id: Block ID to get snapshot for + + Returns: + Dictionary mapping value_id -> ir.Value (empty dict if not found) + """ + return self.block_end_values.get(block_id, {}) + + def set_block_snapshot(self, block_id: int, snapshot: Dict[int, ir.Value]) -> None: + """Set end-of-block value snapshot for a block. + + Args: + block_id: Block ID to set snapshot for + snapshot: Dictionary mapping value_id -> ir.Value + """ + self.block_end_values[block_id] = snapshot + + def register_jump_only_block(self, block_id: int, pred_id: int) -> None: + """Register a block as jump-only (trampoline block). + + Args: + block_id: ID of jump-only block + pred_id: ID of predecessor block to copy snapshot from + """ + self.jump_only_blocks[block_id] = pred_id + + def is_jump_only(self, block_id: int) -> bool: + """Check if a block is registered as jump-only. + + Args: + block_id: Block ID to check + + Returns: + True if block is jump-only, False otherwise + """ + return block_id in self.jump_only_blocks + + def add_def_block(self, value_id: int, block_id: int) -> None: + """Record that a value is defined in a block. + + Args: + value_id: Value ID + block_id: Block ID where value is defined + """ + if value_id not in self.def_blocks: + self.def_blocks[value_id] = set() + self.def_blocks[value_id].add(block_id) + + def is_defined_in_block(self, value_id: int, block_id: int) -> bool: + """Check if a value is defined in a specific block. + + Args: + value_id: Value ID to check + block_id: Block ID to check + + Returns: + True if value is defined in the block, False otherwise + """ + return block_id in self.def_blocks.get(value_id, set()) + + def __repr__(self) -> str: + """String representation for debugging.""" + return ( + f"FunctionLowerContext(func_name={self.func_name!r}, " + f"blocks={len(self.block_end_values)}, " + f"jump_only={len(self.jump_only_blocks)}, " + f"defs={len(self.def_blocks)})" + ) diff --git a/src/llvm_py/llvm_builder.py b/src/llvm_py/llvm_builder.py index acbcdbbb..e5b49917 100644 --- a/src/llvm_py/llvm_builder.py +++ b/src/llvm_py/llvm_builder.py @@ -112,8 +112,9 @@ class NyashLLVMBuilder: self.phi_deferrals: List[Tuple[int, int, List[Tuple[int, int]]]] = [] # Predecessor map and per-block end snapshots self.preds: Dict[int, List[int]] = {} - # Phase 132-P0: Tuple-key (func_name, block_id) to prevent cross-function collision - self.block_end_values: Dict[Tuple[str, int], Dict[int, ir.Value]] = {} + # Phase 132-P1: Legacy storage (replaced by FunctionLowerContext Box per-function) + # These are now only used as fallback/backward compatibility + self.block_end_values: Dict[int, Dict[int, ir.Value]] = {} # Definition map: value_id -> set(block_id) where the value is defined # Used as a lightweight lifetime hint to avoid over-localization self.def_blocks: Dict[int, set] = {} diff --git a/src/llvm_py/phi_wiring/tagging.py b/src/llvm_py/phi_wiring/tagging.py index 288ef8df..237ce595 100644 --- a/src/llvm_py/phi_wiring/tagging.py +++ b/src/llvm_py/phi_wiring/tagging.py @@ -18,7 +18,11 @@ def setup_phi_placeholders(builder, blocks: List[Dict[str, Any]]): """ try: produced_str = collect_produced_stringish(blocks) - builder.block_phi_incomings = analyze_incomings(blocks) + # Phase 132-P1: Update existing block_phi_incomings dict (points to context storage) + # Don't replace it with assignment, as that breaks the connection to context + analyzed = analyze_incomings(blocks) + builder.block_phi_incomings.clear() + builder.block_phi_incomings.update(analyzed) trace({"phi": "setup", "produced_str_keys": list(produced_str.keys())}) # Phase 132: Create all PHI placeholders FIRST, before any other operations diff --git a/src/llvm_py/phi_wiring/wiring.py b/src/llvm_py/phi_wiring/wiring.py index 313ddc35..3d74372e 100644 --- a/src/llvm_py/phi_wiring/wiring.py +++ b/src/llvm_py/phi_wiring/wiring.py @@ -137,12 +137,12 @@ def nearest_pred_on_path( return None -def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[int, int]], func_name: str = "unknown"): +def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[int, int]], context=None): """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. + Phase 132-P1: Use context Box for function-local state isolation. Args: - func_name: Function name for tuple-key (func_name, block_id) in block_end_values + context: FunctionLowerContext Box containing function-local state """ bb = builder.bb_map.get(block_id) if bb is None: @@ -150,9 +150,14 @@ def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[in # Prefer an existing PHI already materialized in this block (e.g., by resolver) phi = None try: - snap = getattr(builder, 'block_end_values', {}) or {} - # Phase 132-P0: Use tuple-key (func_name, block_id) - cur = (snap.get((func_name, int(block_id)), {}) or {}).get(int(dst_vid)) + # Phase 132-P1: Use context.get_block_snapshot (simple block_id key) + if context is not None: + snapshot = context.get_block_snapshot(int(block_id)) + cur = snapshot.get(int(dst_vid)) + else: + # Fallback for backward compatibility + snap = getattr(builder, 'block_end_values', {}) or {} + cur = snap.get(int(block_id), {}).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) @@ -216,8 +221,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 - # Phase 132-P0: Pass func_name for tuple-key resolution - val = builder.resolver.resolve_incoming(pred_match, vs, func_name=func_name) + # Phase 132-P1: Pass context for function-local state isolation + val = builder.resolver.resolve_incoming(pred_match, vs, context=context) 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)}) @@ -246,21 +251,21 @@ def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[in return wired -def finalize_phis(builder, func_name: str = "unknown"): +def finalize_phis(builder, context): """Finalize PHI nodes by wiring their incoming edges. - Phase 132-P0: Pass func_name for tuple-key (func_name, block_id) resolution. + Phase 132-P1: Use context Box for function-local state isolation. Args: - func_name: Function name for tuple-key (func_name, block_id) in block_end_values + context: FunctionLowerContext Box containing function-local state """ total_blocks = 0 total_dsts = 0 total_wired = 0 - for block_id, dst_map in (getattr(builder, "block_phi_incomings", {}) or {}).items(): + for block_id, dst_map in (context.block_phi_incomings or {}).items(): 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, func_name=func_name) + wired = wire_incomings(builder, int(block_id), int(dst_vid), incoming, context=context) 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)}) diff --git a/src/llvm_py/resolver.py b/src/llvm_py/resolver.py index 43a7f654..f1428ffc 100644 --- a/src/llvm_py/resolver.py +++ b/src/llvm_py/resolver.py @@ -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())}"