2025-09-19 15:11:57 +09:00
|
|
|
|
from typing import Dict, Any, List
|
|
|
|
|
|
|
|
|
|
|
|
from llvmlite import ir
|
|
|
|
|
|
from trace import debug as trace_debug
|
|
|
|
|
|
from prepass.if_merge import plan_ret_phi_predeclare
|
|
|
|
|
|
from prepass.loops import detect_simple_while
|
2025-09-27 08:45:25 +09:00
|
|
|
|
from phi_wiring import (
|
|
|
|
|
|
setup_phi_placeholders as _setup_phi_placeholders,
|
|
|
|
|
|
finalize_phis as _finalize_phis,
|
|
|
|
|
|
build_succs as _build_succs,
|
|
|
|
|
|
)
|
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
|
|
|
|
from context import FunctionLowerContext
|
|
|
|
|
|
from phi_manager import PhiManager
|
feat(llvm): Phase 132-P1 - Function-local state clearing at function boundaries
## Problem
Multiple function-scoped caches used (bid, vid) or similar keys without
function namespace, causing cross-function collisions.
## Affected State (now cleared at function start)
1. **PhiManager.predeclared** - (bid, vid) -> phi_value
2. **Resolver caches**:
- i64_cache, ptr_cache, f64_cache - (bb_name, vid) -> value
- _end_i64_cache - (bid, vid) -> value
3. **String caches** - string_ids, string_literals, string_ptrs, etc.
4. **Control flow** - _jump_only_blocks, block_phi_incomings, def_blocks
## Solution (Box-First principle)
Added `_clear_function_local_state(builder)` called at start of each
`lower_function()`. This ensures:
- Each function starts with clean state
- No cross-function ValueId/BlockId collisions
- PHI generation determinism improved
## Implementation
**File**: src/llvm_py/builders/function_lower.py
```python
def _clear_function_local_state(builder):
"""Clear all function-local state at function boundary."""
if hasattr(builder, 'phi_manager'):
builder.phi_manager.predeclared.clear()
# ... clear all caches ...
```
## Design Principles
- **Box-First**: Each function is an isolated Box
- **SSOT**: Function-local state is scoped to function
- **Fail-Fast**: Collision structure eliminated by design
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-15 05:43:12 +09:00
|
|
|
|
|
|
|
|
|
|
|
2025-09-19 14:29:02 +09:00
|
|
|
|
def lower_function(builder, func_data: Dict[str, Any]):
|
|
|
|
|
|
"""Lower a single MIR function to LLVM IR using the given builder context.
|
2025-09-19 15:11:57 +09:00
|
|
|
|
This is a faithful extraction of NyashLLVMBuilder.lower_function.
|
2025-09-19 14:29:02 +09:00
|
|
|
|
"""
|
2025-09-19 15:11:57 +09:00
|
|
|
|
import os, re
|
|
|
|
|
|
|
2025-09-19 14:29:02 +09:00
|
|
|
|
name = func_data.get("name", "unknown")
|
|
|
|
|
|
builder.current_function_name = name
|
|
|
|
|
|
params = func_data.get("params", [])
|
|
|
|
|
|
blocks = func_data.get("blocks", [])
|
|
|
|
|
|
|
|
|
|
|
|
# Determine function signature
|
|
|
|
|
|
if name == "ny_main":
|
2025-11-09 23:40:36 +09:00
|
|
|
|
# Special case: ny_main returns i64 to match runtime (nyrt) expectations
|
|
|
|
|
|
func_ty = ir.FunctionType(builder.i64, [])
|
2025-09-19 14:29:02 +09:00
|
|
|
|
else:
|
2025-09-19 15:11:57 +09:00
|
|
|
|
# Default: i64(i64, ...) signature; derive arity from '/N' suffix when params missing
|
2025-09-19 14:29:02 +09:00
|
|
|
|
m = re.search(r"/(\d+)$", name)
|
|
|
|
|
|
arity = int(m.group(1)) if m else len(params)
|
2025-11-11 22:35:45 +09:00
|
|
|
|
# Dev fallback: when params are missing for global (Box.method) functions,
|
|
|
|
|
|
# use observed call-site arity if available (scanned in builder.build_from_mir)
|
|
|
|
|
|
if arity == 0 and '.' in name:
|
|
|
|
|
|
try:
|
|
|
|
|
|
arity = int(builder.call_arities.get(name, 0))
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
2025-09-19 14:29:02 +09:00
|
|
|
|
param_types = [builder.i64] * arity
|
2025-09-19 15:11:57 +09:00
|
|
|
|
func_ty = ir.FunctionType(builder.i64, param_types)
|
2025-09-19 14:29:02 +09:00
|
|
|
|
|
2025-09-19 15:11:57 +09:00
|
|
|
|
# Reset per-function maps and resolver caches to avoid cross-function collisions
|
2025-09-19 14:29:02 +09:00
|
|
|
|
try:
|
|
|
|
|
|
builder.vmap.clear()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
builder.vmap = {}
|
2025-09-19 15:11:57 +09:00
|
|
|
|
try:
|
|
|
|
|
|
builder.bb_map.clear()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
builder.bb_map = {}
|
2025-12-15 11:47:58 +09:00
|
|
|
|
# Phase 132-P1: Clear per-function predeclared PHI placeholders (avoid cross-function leakage)
|
|
|
|
|
|
try:
|
|
|
|
|
|
builder.predeclared_ret_phis.clear()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
try:
|
|
|
|
|
|
builder.predeclared_ret_phis = {}
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
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
|
|
|
|
|
|
|
|
|
|
# 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
|
2025-12-15 11:47:58 +09:00
|
|
|
|
builder.block_end_values = context.block_end_values
|
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
|
|
|
|
|
|
|
|
|
|
# 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
|
2025-09-19 14:29:02 +09:00
|
|
|
|
|
2025-12-15 01:36:34 +09:00
|
|
|
|
# Phase 131-15-P1: Load value_types metadata from JSON into resolver
|
|
|
|
|
|
try:
|
|
|
|
|
|
metadata = func_data.get('metadata', {})
|
|
|
|
|
|
value_types_json = metadata.get('value_types', {})
|
|
|
|
|
|
# Convert string keys to integers and store in resolver
|
|
|
|
|
|
builder.resolver.value_types = {}
|
|
|
|
|
|
for vid_str, vtype in value_types_json.items():
|
|
|
|
|
|
try:
|
|
|
|
|
|
vid = int(vid_str)
|
|
|
|
|
|
builder.resolver.value_types[vid] = vtype
|
|
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
|
|
pass
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
# If metadata loading fails, ensure value_types exists (empty fallback)
|
|
|
|
|
|
builder.resolver.value_types = {}
|
|
|
|
|
|
|
2025-09-19 15:11:57 +09:00
|
|
|
|
# Create or reuse function
|
|
|
|
|
|
func = None
|
2025-09-19 14:29:02 +09:00
|
|
|
|
for f in builder.module.functions:
|
|
|
|
|
|
if f.name == name:
|
2025-09-19 15:11:57 +09:00
|
|
|
|
func = f
|
|
|
|
|
|
break
|
|
|
|
|
|
if func is None:
|
|
|
|
|
|
func = ir.Function(builder.module, func_ty, name=name)
|
|
|
|
|
|
|
2025-11-11 22:35:45 +09:00
|
|
|
|
# Map parameters to vmap. Prefer mapping by referenced value-ids that have no
|
|
|
|
|
|
# local definition (common in v0 JSON where params appear as lhs/rhs ids).
|
2025-09-19 15:11:57 +09:00
|
|
|
|
try:
|
|
|
|
|
|
arity = len(func.args)
|
2025-11-11 22:35:45 +09:00
|
|
|
|
# Collect defined and used ids
|
|
|
|
|
|
defs = set()
|
|
|
|
|
|
uses = set()
|
|
|
|
|
|
for bb in (blocks or []):
|
|
|
|
|
|
for ins in (bb.get('instructions') or []):
|
|
|
|
|
|
try:
|
|
|
|
|
|
dstv = ins.get('dst')
|
|
|
|
|
|
if isinstance(dstv, int):
|
|
|
|
|
|
defs.add(int(dstv))
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
for k in ('lhs','rhs','value','cond','box_val'):
|
|
|
|
|
|
try:
|
|
|
|
|
|
v = ins.get(k)
|
|
|
|
|
|
if isinstance(v, int):
|
|
|
|
|
|
uses.add(int(v))
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
cand = [vid for vid in uses if vid not in defs]
|
|
|
|
|
|
cand.sort()
|
|
|
|
|
|
mapped = 0
|
|
|
|
|
|
for i in range(min(arity, len(cand))):
|
|
|
|
|
|
builder.vmap[int(cand[i])] = func.args[i]
|
|
|
|
|
|
mapped += 1
|
|
|
|
|
|
# Fallback: also map positional 0..arity-1 to args if not already mapped
|
2025-09-19 15:11:57 +09:00
|
|
|
|
for i in range(arity):
|
2025-11-11 22:35:45 +09:00
|
|
|
|
if i not in builder.vmap:
|
|
|
|
|
|
builder.vmap[i] = func.args[i]
|
2025-09-19 15:11:57 +09:00
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
# Build predecessor map from control-flow edges
|
|
|
|
|
|
builder.preds = {}
|
|
|
|
|
|
for block_data in blocks:
|
|
|
|
|
|
bid = block_data.get("id", 0)
|
|
|
|
|
|
builder.preds.setdefault(bid, [])
|
|
|
|
|
|
for block_data in blocks:
|
|
|
|
|
|
src = block_data.get("id", 0)
|
|
|
|
|
|
for inst in block_data.get("instructions", []):
|
|
|
|
|
|
op = inst.get("op")
|
|
|
|
|
|
if op == "jump":
|
|
|
|
|
|
t = inst.get("target")
|
|
|
|
|
|
if t is not None:
|
|
|
|
|
|
builder.preds.setdefault(t, []).append(src)
|
|
|
|
|
|
elif op == "branch":
|
|
|
|
|
|
th = inst.get("then")
|
|
|
|
|
|
el = inst.get("else")
|
|
|
|
|
|
if th is not None:
|
|
|
|
|
|
builder.preds.setdefault(th, []).append(src)
|
|
|
|
|
|
if el is not None:
|
|
|
|
|
|
builder.preds.setdefault(el, []).append(src)
|
|
|
|
|
|
|
|
|
|
|
|
# Create all blocks first
|
|
|
|
|
|
for block_data in blocks:
|
|
|
|
|
|
bid = block_data.get("id", 0)
|
|
|
|
|
|
block_name = f"bb{bid}"
|
|
|
|
|
|
bb = func.append_basic_block(block_name)
|
|
|
|
|
|
builder.bb_map[bid] = bb
|
|
|
|
|
|
|
|
|
|
|
|
# Build quick lookup for blocks by id
|
|
|
|
|
|
block_by_id: Dict[int, Dict[str, Any]] = {}
|
|
|
|
|
|
for block_data in blocks:
|
|
|
|
|
|
block_by_id[block_data.get("id", 0)] = block_data
|
|
|
|
|
|
|
|
|
|
|
|
# Determine entry block: first with no predecessors; fallback to first block
|
|
|
|
|
|
entry_bid = None
|
|
|
|
|
|
for bid, preds in builder.preds.items():
|
|
|
|
|
|
if len(preds) == 0:
|
|
|
|
|
|
entry_bid = bid
|
2025-09-19 14:29:02 +09:00
|
|
|
|
break
|
2025-09-19 15:11:57 +09:00
|
|
|
|
if entry_bid is None and blocks:
|
|
|
|
|
|
entry_bid = blocks[0].get("id", 0)
|
|
|
|
|
|
|
|
|
|
|
|
# Compute approx preds-first order
|
|
|
|
|
|
visited = set()
|
|
|
|
|
|
order: List[int] = []
|
|
|
|
|
|
|
|
|
|
|
|
def visit(bid: int):
|
|
|
|
|
|
if bid in visited:
|
|
|
|
|
|
return
|
|
|
|
|
|
visited.add(bid)
|
|
|
|
|
|
for p in builder.preds.get(bid, []):
|
|
|
|
|
|
visit(p)
|
|
|
|
|
|
order.append(bid)
|
|
|
|
|
|
|
|
|
|
|
|
if entry_bid is not None:
|
|
|
|
|
|
visit(entry_bid)
|
|
|
|
|
|
for bid in block_by_id.keys():
|
|
|
|
|
|
if bid not in visited:
|
|
|
|
|
|
visit(bid)
|
|
|
|
|
|
|
|
|
|
|
|
# Prepass: collect PHI metadata and placeholders
|
|
|
|
|
|
_setup_phi_placeholders(builder, blocks)
|
|
|
|
|
|
|
|
|
|
|
|
# Optional: if-merge prepass (gate NYASH_LLVM_PREPASS_IFMERGE)
|
|
|
|
|
|
try:
|
|
|
|
|
|
if os.environ.get('NYASH_LLVM_PREPASS_IFMERGE') == '1':
|
|
|
|
|
|
plan = plan_ret_phi_predeclare(block_by_id)
|
|
|
|
|
|
if plan:
|
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
|
|
|
|
# Phase 132-P1: block_phi_incomings already points to context storage
|
|
|
|
|
|
# No need to reassign - just ensure it exists
|
|
|
|
|
|
pass
|
2025-09-19 15:11:57 +09:00
|
|
|
|
for bbid, ret_vid in plan.items():
|
|
|
|
|
|
try:
|
|
|
|
|
|
preds_raw = [p for p in builder.preds.get(bbid, []) if p != bbid]
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
preds_raw = []
|
|
|
|
|
|
seen = set(); preds_list = []
|
|
|
|
|
|
for p in preds_raw:
|
|
|
|
|
|
if p not in seen:
|
|
|
|
|
|
preds_list.append(p); seen.add(p)
|
|
|
|
|
|
try:
|
|
|
|
|
|
builder.block_phi_incomings.setdefault(int(bbid), {})[int(ret_vid)] = [
|
|
|
|
|
|
(int(p), int(ret_vid)) for p in preds_list
|
|
|
|
|
|
]
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
try:
|
|
|
|
|
|
trace_debug(f"[prepass] if-merge: plan metadata at bb{bbid} for v{ret_vid} preds={preds_list}")
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
# Predeclare PHIs for used-in-block values defined in predecessors (multi-pred only)
|
|
|
|
|
|
try:
|
|
|
|
|
|
from cfg.utils import build_preds_succs
|
|
|
|
|
|
local_preds, _ = build_preds_succs(block_by_id)
|
|
|
|
|
|
def _collect_defs(block):
|
|
|
|
|
|
defs = set()
|
|
|
|
|
|
for ins in block.get('instructions') or []:
|
|
|
|
|
|
try:
|
|
|
|
|
|
dstv = ins.get('dst')
|
|
|
|
|
|
if isinstance(dstv, int):
|
|
|
|
|
|
defs.add(int(dstv))
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
return defs
|
|
|
|
|
|
def _collect_uses(block):
|
|
|
|
|
|
uses = set()
|
|
|
|
|
|
for ins in block.get('instructions') or []:
|
|
|
|
|
|
for k in ('lhs','rhs','value','cond','box_val'):
|
|
|
|
|
|
try:
|
|
|
|
|
|
v = ins.get(k)
|
|
|
|
|
|
if isinstance(v, int):
|
|
|
|
|
|
uses.add(int(v))
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
return uses
|
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
|
|
|
|
# Phase 132-P1: block_phi_incomings already points to context storage
|
|
|
|
|
|
# No need to reassign - it's already initialized
|
2025-09-19 15:11:57 +09:00
|
|
|
|
for bid, blk in block_by_id.items():
|
|
|
|
|
|
try:
|
|
|
|
|
|
preds_raw = [p for p in local_preds.get(int(bid), []) if p != int(bid)]
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
preds_raw = []
|
|
|
|
|
|
seen = set(); preds_list = []
|
|
|
|
|
|
for p in preds_raw:
|
|
|
|
|
|
if p not in seen:
|
|
|
|
|
|
preds_list.append(p); seen.add(p)
|
|
|
|
|
|
if len(preds_list) <= 1:
|
|
|
|
|
|
continue
|
|
|
|
|
|
defs = _collect_defs(blk)
|
|
|
|
|
|
uses = _collect_uses(blk)
|
|
|
|
|
|
need = [u for u in uses if u not in defs]
|
|
|
|
|
|
if not need:
|
|
|
|
|
|
continue
|
|
|
|
|
|
for vid in need:
|
|
|
|
|
|
try:
|
|
|
|
|
|
builder.block_phi_incomings.setdefault(int(bid), {}).setdefault(int(vid), [])
|
|
|
|
|
|
builder.block_phi_incomings[int(bid)][int(vid)] = [(int(p), int(vid)) for p in preds_list]
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
try:
|
|
|
|
|
|
builder.resolver.block_phi_incomings = builder.block_phi_incomings
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
2025-09-19 14:29:02 +09:00
|
|
|
|
|
2025-09-19 15:11:57 +09:00
|
|
|
|
# Optional: simple loop prepass
|
|
|
|
|
|
loop_plan = None
|
|
|
|
|
|
try:
|
|
|
|
|
|
if os.environ.get('NYASH_LLVM_PREPASS_LOOP') == '1':
|
|
|
|
|
|
loop_plan = detect_simple_while(block_by_id)
|
|
|
|
|
|
if loop_plan is not None:
|
|
|
|
|
|
trace_debug(f"[prepass] detect loop header=bb{loop_plan['header']} then=bb{loop_plan['then']} latch=bb{loop_plan['latch']} exit=bb{loop_plan['exit']}")
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
loop_plan = None
|
|
|
|
|
|
|
2025-12-14 06:12:31 +09:00
|
|
|
|
# Phase 131-4 Pass A: Lower non-terminator instructions (terminators deferred)
|
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
|
|
|
|
# Phase 132-P1: Pass context Box for function-local state isolation
|
2025-09-19 15:11:57 +09:00
|
|
|
|
from builders.block_lower import lower_blocks as _lower_blocks
|
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
|
|
|
|
_lower_blocks(builder, func, block_by_id, order, loop_plan, context)
|
2025-09-19 14:29:02 +09:00
|
|
|
|
|
2025-12-15 00:39:43 +09:00
|
|
|
|
# Phase 131-14-B Pass B: Resolve jump-only block snapshots (BEFORE PHI finalization)
|
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
|
|
|
|
# Phase 132-P1: Pass context Box for function-local state isolation
|
2025-12-15 00:39:43 +09:00
|
|
|
|
from builders.block_lower import resolve_jump_only_snapshots as _resolve_jump_only_snapshots
|
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
|
|
|
|
_resolve_jump_only_snapshots(builder, block_by_id, context)
|
2025-12-15 00:39:43 +09:00
|
|
|
|
|
2025-12-15 12:12:54 +09:00
|
|
|
|
# Phase 132-P2: Dict ctx removed; FunctionLowerContext is now SSOT
|
|
|
|
|
|
# All context access goes through owner.context (passed to instruction handlers)
|
2025-09-19 15:11:57 +09:00
|
|
|
|
|
2025-12-15 00:39:43 +09:00
|
|
|
|
# Phase 131-4 Pass B (now Pass B2): Finalize PHIs (wires incoming edges)
|
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
|
|
|
|
# Phase 132-P1: Pass context Box for function-local state isolation
|
|
|
|
|
|
_finalize_phis(builder, context)
|
2025-09-27 08:45:25 +09:00
|
|
|
|
|
2025-12-14 06:12:31 +09:00
|
|
|
|
# Phase 131-4 Pass C: Lower deferred terminators (after PHIs are placed)
|
|
|
|
|
|
from builders.block_lower import lower_terminators as _lower_terminators
|
|
|
|
|
|
_lower_terminators(builder, func)
|
|
|
|
|
|
|
2025-09-27 08:45:25 +09:00
|
|
|
|
# Safety pass: ensure every basic block ends with a terminator.
|
|
|
|
|
|
# This avoids llvmlite IR parse errors like "expected instruction opcode" on empty blocks.
|
|
|
|
|
|
try:
|
|
|
|
|
|
_enforce_terminators(builder, func, block_by_id)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
# Non-fatal in bring-up; better to emit IR than crash
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _enforce_terminators(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, Any]]):
|
|
|
|
|
|
import re
|
|
|
|
|
|
succs = _build_succs(getattr(builder, 'preds', {}) or {})
|
|
|
|
|
|
for bb in func.blocks:
|
|
|
|
|
|
try:
|
|
|
|
|
|
if bb.terminator is not None:
|
|
|
|
|
|
continue
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
# If property access fails, try to add a branch/ret anyway
|
|
|
|
|
|
pass
|
|
|
|
|
|
# Parse block id from name like "bb123"
|
|
|
|
|
|
bid = None
|
|
|
|
|
|
try:
|
|
|
|
|
|
m = re.match(r"bb(\d+)$", str(bb.name))
|
|
|
|
|
|
bid = int(m.group(1)) if m else None
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
bid = None
|
|
|
|
|
|
# Choose a reasonable successor if any
|
|
|
|
|
|
target_bb = None
|
|
|
|
|
|
if bid is not None:
|
|
|
|
|
|
for s in (succs.get(int(bid), []) or []):
|
|
|
|
|
|
try:
|
|
|
|
|
|
cand = builder.bb_map.get(int(s))
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
cand = None
|
|
|
|
|
|
if cand is not None and cand is not bb:
|
|
|
|
|
|
target_bb = cand
|
|
|
|
|
|
break
|
|
|
|
|
|
ib = ir.IRBuilder(bb)
|
|
|
|
|
|
if target_bb is not None:
|
|
|
|
|
|
try:
|
|
|
|
|
|
ib.position_at_end(bb)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
ib.branch(target_bb)
|
|
|
|
|
|
try:
|
|
|
|
|
|
trace_debug(f"[llvm-py] enforce_terminators: br from {bb.name} -> {target_bb.name}")
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
continue
|
|
|
|
|
|
# Fallback: insert a return of 0 matching function return type (i32 for ny_main, else i64)
|
|
|
|
|
|
try:
|
|
|
|
|
|
rty = func.function_type.return_type
|
|
|
|
|
|
if str(rty) == str(builder.i32):
|
|
|
|
|
|
ib.ret(ir.Constant(builder.i32, 0))
|
|
|
|
|
|
elif str(rty) == str(builder.i64):
|
|
|
|
|
|
ib.ret(ir.Constant(builder.i64, 0))
|
|
|
|
|
|
else:
|
|
|
|
|
|
# Unknown/void – synthesize a dummy br to self to keep parser happy (unreachable in practice)
|
|
|
|
|
|
ib.branch(bb)
|
|
|
|
|
|
try:
|
|
|
|
|
|
trace_debug(f"[llvm-py] enforce_terminators: ret/br injected in {bb.name}")
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
# Last resort: do nothing
|
|
|
|
|
|
pass
|