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:
@ -58,9 +58,9 @@ class DeferredTerminator(NamedTuple):
|
|||||||
vmap_snapshot: Dict[int, ir.Value]
|
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 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
|
This function runs AFTER all blocks have been lowered (Pass A) but BEFORE
|
||||||
PHI finalization. It resolves snapshots for jump-only blocks by following
|
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.
|
SSOT: Snapshots are based on CFG structure, not processing order.
|
||||||
|
|
||||||
Args:
|
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
|
import sys
|
||||||
|
|
||||||
strict_mode = os.environ.get('NYASH_LLVM_STRICT') == '1'
|
strict_mode = os.environ.get('NYASH_LLVM_STRICT') == '1'
|
||||||
trace_vmap = os.environ.get('NYASH_LLVM_TRACE_VMAP') == '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 not jump_only:
|
||||||
if trace_vmap:
|
if trace_vmap:
|
||||||
print("[vmap/resolve/passB] No jump-only blocks to resolve", file=sys.stderr)
|
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]
|
return resolved[bid]
|
||||||
|
|
||||||
# Normal block - already has snapshot from Pass A
|
# Normal block - already has snapshot from Pass A
|
||||||
# Phase 132-P0: Use tuple-key (func_name, block_id)
|
# Phase 132-P1: Use context.block_end_values (simple block_id key)
|
||||||
if (func_name, bid) in builder.block_end_values:
|
snapshot = context.get_block_snapshot(bid)
|
||||||
snapshot = builder.block_end_values[(func_name, bid)]
|
if snapshot:
|
||||||
if trace_vmap:
|
if trace_vmap:
|
||||||
print(
|
print(
|
||||||
f"[vmap/resolve/passB] bb{bid} is normal block with snapshot "
|
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 {}
|
return {}
|
||||||
|
|
||||||
# Resolve all jump-only blocks
|
# 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()):
|
for bid in sorted(jump_only.keys()):
|
||||||
snapshot = resolve(bid)
|
snapshot = resolve(bid)
|
||||||
builder.block_end_values[(func_name, bid)] = snapshot
|
context.set_block_snapshot(bid, snapshot)
|
||||||
|
|
||||||
if trace_vmap:
|
if trace_vmap:
|
||||||
print(
|
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)
|
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.
|
"""Lower blocks in multi-pass to ensure PHIs are always before terminators.
|
||||||
|
|
||||||
Phase 131-4: Multi-pass block lowering architecture
|
Phase 131-4: Multi-pass block lowering architecture
|
||||||
Phase 131-14-B: Two-pass snapshot resolution
|
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)
|
- Pass A: Lower non-terminator instructions only (terminators deferred)
|
||||||
- jump-only blocks: record metadata only, NO snapshot resolution
|
- jump-only blocks: record metadata only, NO snapshot resolution
|
||||||
- Pass B: PHI finalization happens in function_lower.py
|
- 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.
|
other instructions, and terminators must be last.
|
||||||
|
|
||||||
Args:
|
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()
|
skipped: set[int] = set()
|
||||||
if loop_plan is not None:
|
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:
|
else:
|
||||||
body_ops.append(inst)
|
body_ops.append(inst)
|
||||||
# Per-block SSA map
|
# 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] = {}
|
vmap_cur: Dict[int, ir.Value] = {}
|
||||||
try:
|
try:
|
||||||
vmap_cur = builder.phi_manager.filter_vmap_preserve_phis(
|
vmap_cur = context.phi_manager.filter_vmap_preserve_phis(
|
||||||
builder.vmap or {},
|
builder.vmap or {},
|
||||||
int(bid)
|
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)
|
created_ids.append(dst)
|
||||||
# P0-1.5: Update def_blocks IMMEDIATELY after instruction lowering
|
# P0-1.5: Update def_blocks IMMEDIATELY after instruction lowering
|
||||||
# This ensures resolver can detect defined_here for same-block uses
|
# This ensures resolver can detect defined_here for same-block uses
|
||||||
|
# Phase 132-P1: Use context.add_def_block
|
||||||
try:
|
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:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
except Exception:
|
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)
|
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)
|
# 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
|
# 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:
|
try:
|
||||||
# Create sync dict from created values only
|
# Create sync dict from created values only
|
||||||
sync_dict = {vid: vmap_cur[vid] for vid in created_ids if vid in vmap_cur}
|
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)
|
# 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':
|
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)
|
print(f"[vmap/sync] bb{bid} synced {len(sync_dict)} values to builder.vmap (PHIs protected)", file=sys.stderr)
|
||||||
except Exception:
|
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'
|
strict_mode = os.environ.get('NYASH_LLVM_STRICT') == '1'
|
||||||
trace_vmap = os.environ.get('NYASH_LLVM_TRACE_VMAP') == '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)
|
is_jump_only = is_jump_only_block(block_data)
|
||||||
if trace_vmap:
|
if trace_vmap:
|
||||||
print(
|
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:
|
elif len(preds_list) == 1:
|
||||||
# Single predecessor - record metadata for Pass B resolution
|
# Single predecessor - record metadata for Pass B resolution
|
||||||
pred_bid = preds_list[0]
|
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
|
# DO NOT create snapshot here - will be resolved in Pass B
|
||||||
# Set snap to None to indicate "skip storing in block_end_values"
|
# 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)
|
snap = dict(vmap_cur)
|
||||||
|
|
||||||
# Phase 131-14-B: Only store snapshot if not deferred (snap is not None)
|
# 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:
|
if snap is not None:
|
||||||
try:
|
try:
|
||||||
keys = sorted(list(snap.keys()))
|
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]]})
|
trace_phi_json({"phi": "snapshot", "block": int(bid), "keys": [int(k) for k in keys[:20]]})
|
||||||
for vid in created_ids:
|
for vid in created_ids:
|
||||||
if vid in vmap_cur:
|
if vid in vmap_cur:
|
||||||
builder.def_blocks.setdefault(vid, set()).add(block_data.get("id", 0))
|
context.add_def_block(vid, block_data.get("id", 0))
|
||||||
builder.block_end_values[(func_name, bid)] = snap
|
context.set_block_snapshot(bid, snap)
|
||||||
else:
|
else:
|
||||||
# Jump-only block with deferred snapshot - don't store yet
|
# Jump-only block with deferred snapshot - don't store yet
|
||||||
if trace_vmap:
|
if trace_vmap:
|
||||||
|
|||||||
@ -9,74 +9,8 @@ from phi_wiring import (
|
|||||||
finalize_phis as _finalize_phis,
|
finalize_phis as _finalize_phis,
|
||||||
build_succs as _build_succs,
|
build_succs as _build_succs,
|
||||||
)
|
)
|
||||||
|
from context import FunctionLowerContext
|
||||||
|
from phi_manager import PhiManager
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def lower_function(builder, func_data: Dict[str, Any]):
|
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()
|
builder.bb_map.clear()
|
||||||
except Exception:
|
except Exception:
|
||||||
builder.bb_map = {}
|
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
|
# Phase 131-15-P1: Load value_types metadata from JSON into resolver
|
||||||
try:
|
try:
|
||||||
@ -248,8 +198,9 @@ def lower_function(builder, func_data: Dict[str, Any]):
|
|||||||
if os.environ.get('NYASH_LLVM_PREPASS_IFMERGE') == '1':
|
if os.environ.get('NYASH_LLVM_PREPASS_IFMERGE') == '1':
|
||||||
plan = plan_ret_phi_predeclare(block_by_id)
|
plan = plan_ret_phi_predeclare(block_by_id)
|
||||||
if plan:
|
if plan:
|
||||||
if not hasattr(builder, 'block_phi_incomings') or builder.block_phi_incomings is None:
|
# Phase 132-P1: block_phi_incomings already points to context storage
|
||||||
builder.block_phi_incomings = {}
|
# No need to reassign - just ensure it exists
|
||||||
|
pass
|
||||||
for bbid, ret_vid in plan.items():
|
for bbid, ret_vid in plan.items():
|
||||||
try:
|
try:
|
||||||
preds_raw = [p for p in builder.preds.get(bbid, []) if p != bbid]
|
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:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return uses
|
return uses
|
||||||
if not hasattr(builder, 'block_phi_incomings') or builder.block_phi_incomings is None:
|
# Phase 132-P1: block_phi_incomings already points to context storage
|
||||||
builder.block_phi_incomings = {}
|
# No need to reassign - it's already initialized
|
||||||
for bid, blk in block_by_id.items():
|
for bid, blk in block_by_id.items():
|
||||||
try:
|
try:
|
||||||
preds_raw = [p for p in local_preds.get(int(bid), []) if p != int(bid)]
|
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
|
loop_plan = None
|
||||||
|
|
||||||
# Phase 131-4 Pass A: Lower non-terminator instructions (terminators deferred)
|
# 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
|
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 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
|
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
|
# Optional: capture lowering ctx for downstream helpers
|
||||||
try:
|
try:
|
||||||
@ -370,8 +321,8 @@ def lower_function(builder, func_data: Dict[str, Any]):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# Phase 131-4 Pass B (now Pass B2): Finalize PHIs (wires incoming edges)
|
# Phase 131-4 Pass B (now Pass B2): Finalize PHIs (wires incoming edges)
|
||||||
# Phase 132-P0: Pass func_name for tuple-key resolution
|
# Phase 132-P1: Pass context Box for function-local state isolation
|
||||||
_finalize_phis(builder, func_name=name)
|
_finalize_phis(builder, context)
|
||||||
|
|
||||||
# Phase 131-4 Pass C: Lower deferred terminators (after PHIs are placed)
|
# Phase 131-4 Pass C: Lower deferred terminators (after PHIs are placed)
|
||||||
from builders.block_lower import lower_terminators as _lower_terminators
|
from builders.block_lower import lower_terminators as _lower_terminators
|
||||||
|
|||||||
9
src/llvm_py/context/__init__.py
Normal file
9
src/llvm_py/context/__init__.py
Normal file
@ -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']
|
||||||
148
src/llvm_py/context/function_lower_context.py
Normal file
148
src/llvm_py/context/function_lower_context.py
Normal file
@ -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)})"
|
||||||
|
)
|
||||||
@ -112,8 +112,9 @@ class NyashLLVMBuilder:
|
|||||||
self.phi_deferrals: List[Tuple[int, int, List[Tuple[int, int]]]] = []
|
self.phi_deferrals: List[Tuple[int, int, List[Tuple[int, int]]]] = []
|
||||||
# Predecessor map and per-block end snapshots
|
# Predecessor map and per-block end snapshots
|
||||||
self.preds: Dict[int, List[int]] = {}
|
self.preds: Dict[int, List[int]] = {}
|
||||||
# Phase 132-P0: Tuple-key (func_name, block_id) to prevent cross-function collision
|
# Phase 132-P1: Legacy storage (replaced by FunctionLowerContext Box per-function)
|
||||||
self.block_end_values: Dict[Tuple[str, int], Dict[int, ir.Value]] = {}
|
# 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
|
# Definition map: value_id -> set(block_id) where the value is defined
|
||||||
# Used as a lightweight lifetime hint to avoid over-localization
|
# Used as a lightweight lifetime hint to avoid over-localization
|
||||||
self.def_blocks: Dict[int, set] = {}
|
self.def_blocks: Dict[int, set] = {}
|
||||||
|
|||||||
@ -18,7 +18,11 @@ def setup_phi_placeholders(builder, blocks: List[Dict[str, Any]]):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
produced_str = collect_produced_stringish(blocks)
|
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())})
|
trace({"phi": "setup", "produced_str_keys": list(produced_str.keys())})
|
||||||
|
|
||||||
# Phase 132: Create all PHI placeholders FIRST, before any other operations
|
# Phase 132: Create all PHI placeholders FIRST, before any other operations
|
||||||
|
|||||||
@ -137,12 +137,12 @@ def nearest_pred_on_path(
|
|||||||
return None
|
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.
|
"""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:
|
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)
|
bb = builder.bb_map.get(block_id)
|
||||||
if bb is None:
|
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)
|
# Prefer an existing PHI already materialized in this block (e.g., by resolver)
|
||||||
phi = None
|
phi = None
|
||||||
try:
|
try:
|
||||||
snap = getattr(builder, 'block_end_values', {}) or {}
|
# Phase 132-P1: Use context.get_block_snapshot (simple block_id key)
|
||||||
# Phase 132-P0: Use tuple-key (func_name, block_id)
|
if context is not None:
|
||||||
cur = (snap.get((func_name, int(block_id)), {}) or {}).get(int(dst_vid))
|
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'):
|
if cur is not None and hasattr(cur, 'add_incoming'):
|
||||||
# Ensure it belongs to the same block
|
# Ensure it belongs to the same block
|
||||||
cur_bb_name = getattr(getattr(cur, 'basic_block', None), 'name', None)
|
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})
|
trace({"phi": "wire_replaced_src", "original": original_vs, "replaced": vs})
|
||||||
try:
|
try:
|
||||||
# P0-4: Use resolve_incoming for PHI incoming values
|
# P0-4: Use resolve_incoming for PHI incoming values
|
||||||
# Phase 132-P0: Pass func_name for tuple-key resolution
|
# Phase 132-P1: Pass context for function-local state isolation
|
||||||
val = builder.resolver.resolve_incoming(pred_match, vs, func_name=func_name)
|
val = builder.resolver.resolve_incoming(pred_match, vs, context=context)
|
||||||
trace({"phi": "wire_resolved", "vs": vs, "pred": pred_match, "val_type": type(val).__name__})
|
trace({"phi": "wire_resolved", "vs": vs, "pred": pred_match, "val_type": type(val).__name__})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
trace({"phi": "wire_resolve_fail", "vs": vs, "pred": pred_match, "error": str(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
|
return wired
|
||||||
|
|
||||||
|
|
||||||
def finalize_phis(builder, func_name: str = "unknown"):
|
def finalize_phis(builder, context):
|
||||||
"""Finalize PHI nodes by wiring their incoming edges.
|
"""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:
|
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_blocks = 0
|
||||||
total_dsts = 0
|
total_dsts = 0
|
||||||
total_wired = 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
|
total_blocks += 1
|
||||||
for dst_vid, incoming in (dst_map or {}).items():
|
for dst_vid, incoming in (dst_map or {}).items():
|
||||||
total_dsts += 1
|
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)
|
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", "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)})
|
trace({"phi": "finalize_summary", "blocks": int(total_blocks), "dsts": int(total_dsts), "incoming_wired": int(total_wired)})
|
||||||
|
|||||||
@ -35,8 +35,12 @@ class Resolver:
|
|||||||
except Exception:
|
except Exception:
|
||||||
self.global_vmap = None
|
self.global_vmap = None
|
||||||
self.global_bb_map = 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
|
# 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.i64_cache: Dict[Tuple[str, int], ir.Value] = {}
|
||||||
self.ptr_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] = {}
|
self.f64_cache: Dict[Tuple[str, int], ir.Value] = {}
|
||||||
@ -49,7 +53,7 @@ class Resolver:
|
|||||||
self.string_ids: set[int] = set()
|
self.string_ids: set[int] = set()
|
||||||
# Cache for repeated string length queries when immutably known
|
# Cache for repeated string length queries when immutably known
|
||||||
self.length_cache: Dict[int, ir.Value] = {}
|
self.length_cache: Dict[int, ir.Value] = {}
|
||||||
|
|
||||||
# Type shortcuts
|
# Type shortcuts
|
||||||
self.i64 = ir.IntType(64)
|
self.i64 = ir.IntType(64)
|
||||||
self.i8p = ir.IntType(8).as_pointer()
|
self.i8p = ir.IntType(8).as_pointer()
|
||||||
@ -58,14 +62,39 @@ class Resolver:
|
|||||||
self._end_i64_cache: Dict[Tuple[int, int], ir.Value] = {}
|
self._end_i64_cache: Dict[Tuple[int, int], ir.Value] = {}
|
||||||
# Lifetime hint: value_id -> set(block_id) where it's known to be defined
|
# Lifetime hint: value_id -> set(block_id) where it's known to be defined
|
||||||
# Populated by the builder when available.
|
# Populated by the builder when available.
|
||||||
|
# Phase 132-P1: Now managed via context, but kept for backward compatibility
|
||||||
self.def_blocks = {}
|
self.def_blocks = {}
|
||||||
# Optional: block -> { dst_vid -> [(pred_bid, val_vid), ...] } for PHIs from MIR JSON
|
# Optional: block -> { dst_vid -> [(pred_bid, val_vid), ...] } for PHIs from MIR JSON
|
||||||
self.block_phi_incomings = {}
|
self.block_phi_incomings = {}
|
||||||
# P0-1: SSOT for end-of-block values (snapshots)
|
# 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 = {}
|
self.block_end_values = {}
|
||||||
# P0-3: Circular reference detection (hang prevention)
|
# P0-3: Circular reference detection (hang prevention)
|
||||||
self._visited: Set[Tuple[int, int]] = set()
|
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:
|
def mark_string(self, value_id: int) -> None:
|
||||||
try:
|
try:
|
||||||
self.string_ids.add(int(value_id))
|
self.string_ids.add(int(value_id))
|
||||||
@ -124,9 +153,9 @@ class Resolver:
|
|||||||
# Non-STRICT: fallback to 0
|
# Non-STRICT: fallback to 0
|
||||||
return ir.Constant(ir.IntType(64), 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)
|
"""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.
|
Used for resolving PHI incoming values from predecessor blocks.
|
||||||
Only looks at block_end_values snapshot, never vmap_cur.
|
Only looks at block_end_values snapshot, never vmap_cur.
|
||||||
@ -134,19 +163,26 @@ class Resolver:
|
|||||||
Args:
|
Args:
|
||||||
pred_block_id: Predecessor block ID
|
pred_block_id: Predecessor block ID
|
||||||
value_id: Value ID to resolve from predecessor
|
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:
|
Returns:
|
||||||
LLVM IR value (i64)
|
LLVM IR value (i64)
|
||||||
"""
|
"""
|
||||||
# Phase 132-P0: Use tuple-key (func_name, block_id)
|
# Phase 132-P1: Use context.get_block_snapshot (simple block_id key)
|
||||||
snapshot = self.block_end_values.get((func_name, pred_block_id), {})
|
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)
|
val = snapshot.get(value_id)
|
||||||
if val is not None:
|
if val is not None:
|
||||||
return val
|
return val
|
||||||
|
|
||||||
# Fail-Fast: snapshot miss → structural bug
|
# Fail-Fast: snapshot miss → structural bug
|
||||||
if os.environ.get('NYASH_LLVM_STRICT') == '1':
|
if os.environ.get('NYASH_LLVM_STRICT') == '1':
|
||||||
|
func_name = ctx.func_name if ctx else "unknown"
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"[LLVM_PY/STRICT] resolve_incoming: v{value_id} not in {func_name}:bb{pred_block_id} snapshot. "
|
f"[LLVM_PY/STRICT] resolve_incoming: v{value_id} not in {func_name}:bb{pred_block_id} snapshot. "
|
||||||
f"Available: {sorted(snapshot.keys())}"
|
f"Available: {sorted(snapshot.keys())}"
|
||||||
|
|||||||
Reference in New Issue
Block a user