Files
hakorune/src/llvm_py/context/function_lower_context.py
nyash-codex e0fb6fecf6 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>
2025-12-15 11:26:10 +09:00

149 lines
5.2 KiB
Python

"""
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)})"
)