feat(llvm): Phase 131-11-H/12 - ループキャリアPHI型修正 & vmap snapshot SSOT
## Phase 131-11-H: ループキャリアPHI型修正 - PHI生成時に初期値(entry block)の型のみ使用 - backedge の値を型推論に使わない(循環依存回避) - NYASH_CARRIER_PHI_DEBUG=1 でトレース ## Phase 131-12-P0: def_blocks 登録 & STRICT エラー化 - safe_vmap_write() で PHI 上書き保護 - resolver miss を STRICT でエラー化(フォールバック 0 禁止) - def_blocks 自動登録 ## Phase 131-12-P1: vmap_cur スナップショット実装 - DeferredTerminator 構造体(block, term_ops, vmap_snapshot) - Pass A で vmap_cur をスナップショット - Pass C でスナップショット復元(try-finally) - STRICT モード assert ## 結果 - ✅ MIR PHI型: Integer(正しい) - ✅ VM: Result: 3 - ✅ vmap snapshot 機構: 動作確認 - ⚠️ LLVM: Result: 0(別のバグ、次Phase で調査) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -1,9 +1,20 @@
|
||||
from typing import Dict, Any, List, Tuple
|
||||
from typing import Dict, Any, List, Tuple, NamedTuple
|
||||
from llvmlite import ir
|
||||
from trace import debug as trace_debug
|
||||
from trace import phi_json as trace_phi_json
|
||||
|
||||
|
||||
class DeferredTerminator(NamedTuple):
|
||||
"""Phase 131-12-P1: Deferred terminator with vmap snapshot.
|
||||
|
||||
This structure captures the terminator operations along with the vmap state
|
||||
at the end of Pass A, ensuring Pass C uses the correct SSA context.
|
||||
"""
|
||||
bb: ir.Block
|
||||
term_ops: List[Dict[str, Any]]
|
||||
vmap_snapshot: Dict[int, ir.Value]
|
||||
|
||||
|
||||
def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, Any]], order: List[int], loop_plan: Dict[str, Any] | None):
|
||||
"""Lower blocks in multi-pass to ensure PHIs are always before terminators.
|
||||
|
||||
@ -34,6 +45,8 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An
|
||||
try:
|
||||
builder.resolver.builder = ib
|
||||
builder.resolver.module = builder.module
|
||||
# P0-1: Set current block_id for def_blocks tracking
|
||||
builder.resolver.current_block_id = bid
|
||||
except Exception:
|
||||
pass
|
||||
builder.loop_count += 1
|
||||
@ -116,6 +129,8 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An
|
||||
try:
|
||||
builder.resolver.builder = ib
|
||||
builder.resolver.module = builder.module
|
||||
# P0-1: Set current block_id for def_blocks tracking
|
||||
builder.resolver.current_block_id = bid
|
||||
except Exception:
|
||||
pass
|
||||
block_data = block_by_id.get(bid, {})
|
||||
@ -162,6 +177,10 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An
|
||||
except Exception:
|
||||
vmap_cur = dict(builder.vmap)
|
||||
builder._current_vmap = vmap_cur
|
||||
# Phase 131-12-P1: Object identity trace for vmap_cur investigation
|
||||
import os, sys
|
||||
if os.environ.get('NYASH_LLVM_VMAP_TRACE') == '1':
|
||||
print(f"[vmap/id] bb{bid} vmap_cur id={id(vmap_cur)} keys={sorted(vmap_cur.keys())[:10]}", file=sys.stderr)
|
||||
created_ids: List[int] = []
|
||||
defined_here_all: set = set()
|
||||
for _inst in body_ops:
|
||||
@ -229,11 +248,17 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An
|
||||
except Exception:
|
||||
pass
|
||||
# Phase 131-4 Pass A: DEFER terminators until after PHI finalization
|
||||
# Store terminators for Pass C (will be lowered in lower_terminators)
|
||||
# Phase 131-12-P1 P0-2: Store terminators WITH vmap_cur snapshot for Pass C
|
||||
if not hasattr(builder, '_deferred_terminators'):
|
||||
builder._deferred_terminators = {}
|
||||
if term_ops:
|
||||
builder._deferred_terminators[bid] = (bb, term_ops)
|
||||
# CRITICAL: dict(vmap_cur) creates a snapshot copy to prevent mutation issues
|
||||
vmap_snapshot = dict(vmap_cur)
|
||||
builder._deferred_terminators[bid] = DeferredTerminator(bb, term_ops, vmap_snapshot)
|
||||
# Phase 131-12-P1: Trace snapshot creation
|
||||
import os, sys
|
||||
if os.environ.get('NYASH_LLVM_VMAP_TRACE') == '1':
|
||||
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
|
||||
try:
|
||||
@ -265,9 +290,11 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An
|
||||
|
||||
def lower_terminators(builder, func: ir.Function):
|
||||
"""Phase 131-4 Pass C: Lower deferred terminators after PHI finalization.
|
||||
Phase 131-12-P1 P0-3: Restore vmap_cur snapshot for each block's terminator lowering.
|
||||
|
||||
This ensures PHI nodes are always at block heads before terminators are added,
|
||||
maintaining LLVM IR's invariant: PHIs first, then other instructions, then terminators.
|
||||
The vmap snapshot ensures terminators see the SSA context from Pass A, not later mutations.
|
||||
"""
|
||||
if not hasattr(builder, '_deferred_terminators'):
|
||||
return
|
||||
@ -275,31 +302,65 @@ def lower_terminators(builder, func: ir.Function):
|
||||
deferred = builder._deferred_terminators
|
||||
trace_debug(f"[llvm-py/pass-c] Lowering {len(deferred)} blocks with deferred terminators")
|
||||
|
||||
for bid, (bb, term_ops) in deferred.items():
|
||||
ib = ir.IRBuilder(bb)
|
||||
try:
|
||||
builder.resolver.builder = ib
|
||||
builder.resolver.module = builder.module
|
||||
# Phase 131-4: Disable PHI synthesis during terminator lowering
|
||||
# Terminators should only use values that already exist (from Pass A/B)
|
||||
builder.resolver._disable_phi_synthesis = True
|
||||
except Exception:
|
||||
pass
|
||||
import os, sys
|
||||
strict_mode = os.environ.get('NYASH_LLVM_STRICT') == '1'
|
||||
|
||||
for inst in term_ops:
|
||||
for bid, deferred_term in deferred.items():
|
||||
# Phase 131-12-P1: Unpack DeferredTerminator with vmap snapshot
|
||||
bb = deferred_term.bb
|
||||
term_ops = deferred_term.term_ops
|
||||
vmap_snapshot = deferred_term.vmap_snapshot
|
||||
|
||||
# Phase 131-12-P1 P0-4: STRICT mode assertion
|
||||
if strict_mode:
|
||||
assert vmap_snapshot is not None, f"STRICT: vmap_snapshot must exist for bb{bid}"
|
||||
trace_debug(f"[llvm-py/pass-c/strict] bb{bid} vmap_snapshot id={id(vmap_snapshot)}")
|
||||
|
||||
# Phase 131-12-P1 P0-3: Save and restore _current_vmap
|
||||
old_current_vmap = getattr(builder, '_current_vmap', None)
|
||||
builder._current_vmap = vmap_snapshot
|
||||
|
||||
# Trace snapshot restoration
|
||||
if os.environ.get('NYASH_LLVM_VMAP_TRACE') == '1':
|
||||
print(f"[vmap/id] Pass C bb{bid} restored snapshot id={id(vmap_snapshot)} keys={sorted(vmap_snapshot.keys())[:10]}", file=sys.stderr)
|
||||
|
||||
# Phase 131-12-P1 P0-4: STRICT mode verification
|
||||
if strict_mode:
|
||||
assert hasattr(builder, '_current_vmap'), f"STRICT: _current_vmap must be set for bb{bid} terminator lowering"
|
||||
assert id(builder._current_vmap) == id(vmap_snapshot), f"STRICT: _current_vmap must match snapshot for bb{bid}"
|
||||
|
||||
try:
|
||||
ib = ir.IRBuilder(bb)
|
||||
try:
|
||||
trace_debug(f"[llvm-py/pass-c] term op: {inst.get('op')} dst={inst.get('dst')} in bb{bid}")
|
||||
builder.resolver.builder = ib
|
||||
builder.resolver.module = builder.module
|
||||
# Phase 131-4: Disable PHI synthesis during terminator lowering
|
||||
# Terminators should only use values that already exist (from Pass A/B)
|
||||
builder.resolver._disable_phi_synthesis = True
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if bb.terminator is not None:
|
||||
# Terminator already exists (e.g., from loop lowering), skip
|
||||
trace_debug(f"[llvm-py/pass-c] bb{bid} already has terminator, skipping")
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
ib.position_at_end(bb)
|
||||
builder.lower_instruction(ib, inst, func)
|
||||
|
||||
for inst in term_ops:
|
||||
try:
|
||||
trace_debug(f"[llvm-py/pass-c] term op: {inst.get('op')} dst={inst.get('dst')} in bb{bid}")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if bb.terminator is not None:
|
||||
# Terminator already exists (e.g., from loop lowering), skip
|
||||
trace_debug(f"[llvm-py/pass-c] bb{bid} already has terminator, skipping")
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
ib.position_at_end(bb)
|
||||
builder.lower_instruction(ib, inst, func)
|
||||
finally:
|
||||
# Phase 131-12-P1 P0-3: Restore previous _current_vmap state (prevent side effects)
|
||||
if old_current_vmap is None:
|
||||
if hasattr(builder, '_current_vmap'):
|
||||
delattr(builder, '_current_vmap')
|
||||
else:
|
||||
builder._current_vmap = old_current_vmap
|
||||
|
||||
# Clean up deferred state
|
||||
try:
|
||||
|
||||
@ -31,6 +31,10 @@ def lower_instruction(owner, builder: ir.IRBuilder, inst: Dict[str, Any], func:
|
||||
op = inst.get("op")
|
||||
# Pick current vmap context (per-block context during lowering)
|
||||
vmap_ctx = getattr(owner, '_current_vmap', owner.vmap)
|
||||
# Phase 131-12-P1: Object identity trace for vmap_ctx investigation
|
||||
import os, sys
|
||||
if os.environ.get('NYASH_LLVM_VMAP_TRACE') == '1':
|
||||
print(f"[vmap/id] instruction op={op} vmap_ctx id={id(vmap_ctx)}", file=sys.stderr)
|
||||
|
||||
if op == "const":
|
||||
dst = inst.get("dst")
|
||||
|
||||
@ -5,7 +5,7 @@ Handles +, -, *, /, %, &, |, ^, <<, >>
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict, Optional, Any
|
||||
from utils.values import resolve_i64_strict
|
||||
from utils.values import resolve_i64_strict, safe_vmap_write
|
||||
import os
|
||||
from .compare import lower_compare
|
||||
|
||||
@ -28,7 +28,7 @@ def _canonicalize_i64(builder: ir.IRBuilder, value, vid, vmap: Dict[int, ir.Valu
|
||||
elif width > 64:
|
||||
value = builder.trunc(value, target, name=f"{hint}_trunc_{vid}")
|
||||
if isinstance(vid, int):
|
||||
vmap[vid] = value
|
||||
safe_vmap_write(vmap, vid, value, f"canonicalize_{hint}")
|
||||
return value
|
||||
|
||||
def lower_binop(
|
||||
@ -233,7 +233,7 @@ def lower_binop(
|
||||
if callee is None:
|
||||
callee = ir.Function(builder.module, hh_fnty, name='nyash.string.concat_hh')
|
||||
res = builder.call(callee, [hl, hr], name=f"concat_hh_{dst}")
|
||||
vmap[dst] = res
|
||||
safe_vmap_write(vmap, dst, res, "binop_concat_hh", resolver=resolver)
|
||||
else:
|
||||
# Mixed string + non-string (e.g., "len=" + 5). Use pointer concat helpers then box.
|
||||
i32 = ir.IntType(32); i8p = ir.IntType(8).as_pointer(); i64 = ir.IntType(64)
|
||||
@ -290,7 +290,7 @@ def lower_binop(
|
||||
boxer = f; break
|
||||
if boxer is None:
|
||||
boxer = ir.Function(builder.module, ir.FunctionType(i64, [i8p]), name='nyash.box.from_i8_string')
|
||||
vmap[dst] = builder.call(boxer, [p], name=f"concat_box_{dst}")
|
||||
safe_vmap_write(vmap, dst, builder.call(boxer, [p], name=f"concat_box_{dst}"), "binop_concat_si")
|
||||
else:
|
||||
li = as_i64(lhs_val)
|
||||
rp = to_i8p_from_vid(rhs, rhs_raw, rhs_val, 'r')
|
||||
@ -307,7 +307,7 @@ def lower_binop(
|
||||
boxer = f; break
|
||||
if boxer is None:
|
||||
boxer = ir.Function(builder.module, ir.FunctionType(i64, [i8p]), name='nyash.box.from_i8_string')
|
||||
vmap[dst] = builder.call(boxer, [p], name=f"concat_box_{dst}")
|
||||
safe_vmap_write(vmap, dst, builder.call(boxer, [p], name=f"concat_box_{dst}"), "binop_concat_is")
|
||||
# Tag result as string handle so subsequent '+' stays in string domain
|
||||
try:
|
||||
if resolver is not None and hasattr(resolver, 'mark_string'):
|
||||
@ -353,6 +353,10 @@ def lower_binop(
|
||||
else:
|
||||
# Unknown op - return zero
|
||||
result = ir.Constant(i64, 0)
|
||||
|
||||
|
||||
# Phase 131-12-P1: Object identity trace before write
|
||||
import sys # os already imported at module level
|
||||
if os.environ.get('NYASH_LLVM_VMAP_TRACE') == '1':
|
||||
print(f"[vmap/id] binop op={op} dst={dst} vmap id={id(vmap)} before_write", file=sys.stderr)
|
||||
# Store result
|
||||
vmap[dst] = result
|
||||
safe_vmap_write(vmap, dst, result, f"binop_{op}")
|
||||
|
||||
@ -5,7 +5,7 @@ Handles comparison operations (<, >, <=, >=, ==, !=)
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict, Optional, Any
|
||||
from utils.values import resolve_i64_strict
|
||||
from utils.values import resolve_i64_strict, safe_vmap_write
|
||||
import os
|
||||
from .externcall import lower_externcall
|
||||
from trace import values as trace_values
|
||||
@ -28,7 +28,7 @@ def _canonicalize_i64(builder: ir.IRBuilder, value, vid, vmap: Dict[int, ir.Valu
|
||||
elif width > 64:
|
||||
value = builder.trunc(value, target, name=f"{hint}_trunc_{vid}")
|
||||
if isinstance(vid, int):
|
||||
vmap[vid] = value
|
||||
safe_vmap_write(vmap, vid, value, f"canonicalize_{hint}")
|
||||
return value
|
||||
|
||||
def lower_compare(
|
||||
@ -126,11 +126,11 @@ def lower_compare(
|
||||
eqf = ir.Function(builder.module, ir.FunctionType(i64, [i64, i64]), name='nyash.string.eq_hh')
|
||||
eq = builder.call(eqf, [lh, rh], name='str_eq')
|
||||
if op == '==':
|
||||
vmap[dst] = eq
|
||||
safe_vmap_write(vmap, dst, eq, "compare_str_eq", resolver=resolver)
|
||||
else:
|
||||
one = ir.Constant(i64, 1)
|
||||
ne = builder.sub(one, eq, name='str_ne')
|
||||
vmap[dst] = ne
|
||||
safe_vmap_write(vmap, dst, ne, "compare_str_ne", resolver=resolver)
|
||||
return
|
||||
|
||||
# Default integer compare path
|
||||
@ -159,7 +159,7 @@ def lower_compare(
|
||||
# should explicitly cast at their use site (e.g., via resolver or
|
||||
# instruction-specific lowering) to avoid emitting casts after
|
||||
# terminators when used as branch conditions.
|
||||
vmap[dst] = cmp_result
|
||||
safe_vmap_write(vmap, dst, cmp_result, f"compare_{pred}")
|
||||
|
||||
def lower_fcmp(
|
||||
builder: ir.IRBuilder,
|
||||
@ -188,10 +188,10 @@ def lower_fcmp(
|
||||
# Perform ordered comparison using canonical predicates
|
||||
pred = op if op in ('<','>','<=','>=','==','!=') else '=='
|
||||
cmp_result = builder.fcmp_ordered(pred, lhs_val, rhs_val, name=f"fcmp_{dst}")
|
||||
|
||||
|
||||
# Convert i1 to i64
|
||||
i64 = ir.IntType(64)
|
||||
result = builder.zext(cmp_result, i64, name=f"fcmp_i64_{dst}")
|
||||
|
||||
|
||||
# Store result
|
||||
vmap[dst] = result
|
||||
safe_vmap_write(vmap, dst, result, f"fcmp_{pred}")
|
||||
|
||||
@ -5,6 +5,7 @@ Handles integer, float, string, and void constants
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict, Any
|
||||
from utils.values import safe_vmap_write
|
||||
|
||||
def lower_const(
|
||||
builder: ir.IRBuilder,
|
||||
@ -31,13 +32,17 @@ def lower_const(
|
||||
# Integer constant
|
||||
i64 = ir.IntType(64)
|
||||
llvm_val = ir.Constant(i64, int(const_val))
|
||||
vmap[dst] = llvm_val
|
||||
|
||||
# Phase 131-12-P1: Object identity trace before write
|
||||
import os, sys
|
||||
if os.environ.get('NYASH_LLVM_VMAP_TRACE') == '1':
|
||||
print(f"[vmap/id] const dst={dst} vmap id={id(vmap)} before_write", file=sys.stderr)
|
||||
safe_vmap_write(vmap, dst, llvm_val, "const_i64", resolver=resolver)
|
||||
|
||||
elif const_type == 'f64':
|
||||
# Float constant
|
||||
f64 = ir.DoubleType()
|
||||
llvm_val = ir.Constant(f64, float(const_val))
|
||||
vmap[dst] = llvm_val
|
||||
safe_vmap_write(vmap, dst, llvm_val, "const_f64", resolver=resolver)
|
||||
|
||||
elif const_type == 'string' or (isinstance(const_type, dict) and const_type.get('kind') in ('handle','ptr') and const_type.get('box_type') == 'StringBox'):
|
||||
# String constant - create global and immediately box to i64 handle
|
||||
@ -75,7 +80,7 @@ def lower_const(
|
||||
if boxer is None:
|
||||
boxer = ir.Function(module, boxer_ty, name='nyash.box.from_i8_string')
|
||||
handle = builder.call(boxer, [gep], name=f"const_str_h_{dst}")
|
||||
vmap[dst] = handle
|
||||
safe_vmap_write(vmap, dst, handle, "const_string", resolver=resolver)
|
||||
if resolver is not None:
|
||||
if hasattr(resolver, 'string_literals'):
|
||||
resolver.string_literals[dst] = str_val
|
||||
@ -87,13 +92,13 @@ def lower_const(
|
||||
resolver.string_ptrs[dst] = gep
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
elif const_type == 'void':
|
||||
# Void/null constant - use i64 zero
|
||||
i64 = ir.IntType(64)
|
||||
vmap[dst] = ir.Constant(i64, 0)
|
||||
|
||||
safe_vmap_write(vmap, dst, ir.Constant(i64, 0), "const_void", resolver=resolver)
|
||||
|
||||
else:
|
||||
# Unknown type - default to i64 zero
|
||||
i64 = ir.IntType(64)
|
||||
vmap[dst] = ir.Constant(i64, 0)
|
||||
safe_vmap_write(vmap, dst, ir.Constant(i64, 0), "const_unknown", resolver=resolver)
|
||||
|
||||
@ -5,7 +5,7 @@ MIR13 PHI-off uses explicit copies along edges/blocks to model merges.
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict, Optional, Any
|
||||
from utils.values import resolve_i64_strict
|
||||
from utils.values import resolve_i64_strict, safe_vmap_write
|
||||
|
||||
def lower_copy(
|
||||
builder: ir.IRBuilder,
|
||||
@ -43,4 +43,8 @@ def lower_copy(
|
||||
val = resolve_i64_strict(resolver, src, current_block, preds, block_end_values, vmap, bb_map)
|
||||
if val is None:
|
||||
val = ir.Constant(ir.IntType(64), 0)
|
||||
vmap[dst] = val
|
||||
# Phase 131-12-P1: Object identity trace before write
|
||||
import os, sys
|
||||
if os.environ.get('NYASH_LLVM_VMAP_TRACE') == '1':
|
||||
print(f"[vmap/id] copy dst={dst} src={src} vmap id={id(vmap)} before_write", file=sys.stderr)
|
||||
safe_vmap_write(vmap, dst, val, "copy", resolver=resolver)
|
||||
|
||||
@ -6,6 +6,7 @@ Critical for SSA form - handles value merging from different control flow paths
|
||||
import llvmlite.ir as ir
|
||||
from phi_wiring import phi_at_block_head
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
from utils.values import safe_vmap_write
|
||||
|
||||
def lower_phi(
|
||||
builder: ir.IRBuilder,
|
||||
@ -131,6 +132,18 @@ def lower_phi(
|
||||
|
||||
# Store PHI result
|
||||
vmap[dst_vid] = phi
|
||||
|
||||
# Register PHI definition in def_blocks (critical for resolver dominance tracking)
|
||||
if resolver is not None and hasattr(resolver, 'def_blocks') and cur_bid is not None:
|
||||
resolver.def_blocks.setdefault(dst_vid, set()).add(cur_bid)
|
||||
import os
|
||||
if os.environ.get('NYASH_LLVM_PHI_DEBUG') == '1':
|
||||
try:
|
||||
from trace import phi as trace_phi_debug
|
||||
trace_phi_debug(f"[PHI_DEBUG] Registered dst_vid={dst_vid} in def_blocks for block={cur_bid}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Strict mode: fail fast on synthesized zeros (indicates incomplete incoming or dominance issue)
|
||||
import os
|
||||
if used_default_zero and os.environ.get('NYASH_LLVM_PHI_STRICT') == '1':
|
||||
|
||||
@ -5,6 +5,7 @@ Handles type conversions and type checks
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict, Optional, Any
|
||||
from utils.values import safe_vmap_write
|
||||
|
||||
def lower_typeop(
|
||||
builder: ir.IRBuilder,
|
||||
@ -57,8 +58,8 @@ def lower_typeop(
|
||||
if op == "cast":
|
||||
# Type casting - for now just pass through
|
||||
# In real implementation, would check/convert box types
|
||||
vmap[dst_vid] = src_val
|
||||
|
||||
safe_vmap_write(vmap, dst_vid, src_val, "typeop_cast", resolver=resolver)
|
||||
|
||||
elif op == "is":
|
||||
# Type check - returns boolean (i64: 1 or 0)
|
||||
# For now, simplified implementation
|
||||
@ -75,17 +76,17 @@ def lower_typeop(
|
||||
else:
|
||||
# For other types, would need runtime type info
|
||||
result = ir.Constant(ir.IntType(64), 0)
|
||||
|
||||
vmap[dst_vid] = result
|
||||
|
||||
|
||||
safe_vmap_write(vmap, dst_vid, result, "typeop_is", resolver=resolver)
|
||||
|
||||
elif op == "as":
|
||||
# Safe cast - returns value or null/0
|
||||
# For now, same as cast
|
||||
vmap[dst_vid] = src_val
|
||||
|
||||
safe_vmap_write(vmap, dst_vid, src_val, "typeop_as", resolver=resolver)
|
||||
|
||||
else:
|
||||
# Unknown operation
|
||||
vmap[dst_vid] = ir.Constant(ir.IntType(64), 0)
|
||||
safe_vmap_write(vmap, dst_vid, ir.Constant(ir.IntType(64), 0), "typeop_unknown")
|
||||
|
||||
def lower_convert(
|
||||
builder: ir.IRBuilder,
|
||||
@ -134,12 +135,12 @@ def lower_convert(
|
||||
if not src_val:
|
||||
# Default based on target type
|
||||
if to_type == "f64":
|
||||
vmap[dst_vid] = ir.Constant(ir.DoubleType(), 0.0)
|
||||
safe_vmap_write(vmap, dst_vid, ir.Constant(ir.DoubleType(), 0.0), "convert_default_f64")
|
||||
elif to_type == "ptr":
|
||||
i8 = ir.IntType(8)
|
||||
vmap[dst_vid] = ir.Constant(i8.as_pointer(), None)
|
||||
safe_vmap_write(vmap, dst_vid, ir.Constant(i8.as_pointer(), None), "convert_default_ptr")
|
||||
else:
|
||||
vmap[dst_vid] = ir.Constant(ir.IntType(64), 0)
|
||||
safe_vmap_write(vmap, dst_vid, ir.Constant(ir.IntType(64), 0), "convert_default_i64")
|
||||
return
|
||||
|
||||
# Perform conversion
|
||||
@ -165,5 +166,5 @@ def lower_convert(
|
||||
else:
|
||||
# Unknown conversion - pass through
|
||||
result = src_val
|
||||
|
||||
vmap[dst_vid] = result
|
||||
|
||||
safe_vmap_write(vmap, dst_vid, result, f"convert_{from_type}_to_{to_type}")
|
||||
|
||||
@ -114,6 +114,9 @@ def setup_phi_placeholders(builder, blocks: List[Dict[str, Any]]):
|
||||
# Definition hint: PHI defines dst in this block
|
||||
try:
|
||||
builder.def_blocks.setdefault(int(dst0), set()).add(int(bid0))
|
||||
import os
|
||||
if os.environ.get('NYASH_LLVM_PHI_DEBUG') == '1':
|
||||
trace({"phi_def_blocks": "registered", "dst": int(dst0), "block": int(bid0)})
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
|
||||
@ -88,7 +88,20 @@ class Resolver:
|
||||
Creates PHI at block start if needed, caches the result.
|
||||
"""
|
||||
cache_key = (current_block.name, value_id)
|
||||
|
||||
|
||||
import os
|
||||
if os.environ.get('NYASH_LLVM_PHI_DEBUG') == '1':
|
||||
try:
|
||||
bid = int(str(current_block.name).replace('bb',''))
|
||||
in_def_blocks = value_id in self.def_blocks
|
||||
if in_def_blocks:
|
||||
def_in_blocks = list(self.def_blocks.get(value_id, set()))
|
||||
else:
|
||||
def_in_blocks = []
|
||||
trace_phi(f"[resolve_i64/entry] bb{bid} v{value_id} in_def_blocks={in_def_blocks} def_in={def_in_blocks}")
|
||||
except Exception as e:
|
||||
trace_phi(f"[resolve_i64/entry] ERROR: {e}")
|
||||
|
||||
# Check cache
|
||||
if cache_key in self.i64_cache:
|
||||
return self.i64_cache[cache_key]
|
||||
@ -158,10 +171,18 @@ class Resolver:
|
||||
defined_here = False
|
||||
if defined_here:
|
||||
existing = vmap.get(value_id)
|
||||
import os
|
||||
if os.environ.get('NYASH_LLVM_PHI_DEBUG') == '1':
|
||||
existing_type = type(existing).__name__ if existing is not None else "None"
|
||||
trace_phi(f"[resolve/def_here] bb{bid} v{value_id} existing={existing_type} in vmap={value_id in vmap}")
|
||||
if existing is not None and hasattr(existing, 'type') and isinstance(existing.type, ir.IntType) and existing.type.width == 64:
|
||||
trace_values(f"[resolve] local reuse: bb{bid} v{value_id}")
|
||||
self.i64_cache[cache_key] = existing
|
||||
return existing
|
||||
elif existing is not None:
|
||||
if os.environ.get('NYASH_LLVM_PHI_DEBUG') == '1':
|
||||
existing_llvm_type = str(existing.type) if hasattr(existing, 'type') else "no_type"
|
||||
trace_phi(f"[resolve/def_here] bb{bid} v{value_id} existing has wrong type: {existing_llvm_type}")
|
||||
else:
|
||||
# Do NOT blindly reuse vmap across blocks: it may reference values defined
|
||||
# in non-dominating predecessors (e.g., other branches). Only reuse when
|
||||
@ -235,6 +256,24 @@ class Resolver:
|
||||
result = placeholder if (placeholder is not None and hasattr(placeholder, 'add_incoming')) else ir.Constant(self.i64, 0)
|
||||
else:
|
||||
# No declared PHI and multi-pred: do not synthesize; fallback to zero
|
||||
import os
|
||||
if os.environ.get('NYASH_LLVM_STRICT') == '1':
|
||||
# P0-2: STRICT mode - fail fast on undeclared PHI in multi-pred context
|
||||
def_blocks_info = "not_in_def_blocks"
|
||||
try:
|
||||
if value_id in self.def_blocks:
|
||||
def_blocks_info = f"def_blocks={sorted(list(self.def_blocks[value_id]))}"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
raise RuntimeError(
|
||||
f"[LLVM_PY/STRICT] Undeclared PHI in multi-pred block:\n"
|
||||
f" ValueId: {value_id}\n"
|
||||
f" Block: bb{cur_bid}\n"
|
||||
f" Predecessors: {pred_ids}\n"
|
||||
f" {def_blocks_info}\n"
|
||||
f" Hint: Value needs PHI but not declared in block_phi_incomings"
|
||||
)
|
||||
result = ir.Constant(self.i64, 0)
|
||||
|
||||
# Cache and return
|
||||
@ -342,6 +381,31 @@ class Resolver:
|
||||
|
||||
preds_s = ','.join(str(x) for x in pred_ids)
|
||||
trace_phi(f"[resolve] end_i64 miss: bb{block_id} v{value_id} preds=[{preds_s}] → 0")
|
||||
|
||||
# P0-2: STRICT mode - fail fast on resolution miss
|
||||
import os
|
||||
if os.environ.get('NYASH_LLVM_STRICT') == '1':
|
||||
# Collect diagnostic information
|
||||
def_blocks_info = "not_in_def_blocks"
|
||||
try:
|
||||
if value_id in self.def_blocks:
|
||||
def_blocks_info = f"def_blocks={sorted(list(self.def_blocks[value_id]))}"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Build search path for diagnostics
|
||||
search_path = [block_id] + pred_ids
|
||||
|
||||
raise RuntimeError(
|
||||
f"[LLVM_PY/STRICT] PHI resolution miss:\n"
|
||||
f" ValueId: {value_id}\n"
|
||||
f" Target block: {block_id}\n"
|
||||
f" Predecessors: [{preds_s}]\n"
|
||||
f" Search path: {search_path}\n"
|
||||
f" {def_blocks_info}\n"
|
||||
f" Hint: PHI dst may not be registered in def_blocks, or dominance issue"
|
||||
)
|
||||
|
||||
z = ir.Constant(self.i64, 0)
|
||||
self._end_i64_cache[key] = z
|
||||
return z
|
||||
|
||||
@ -5,6 +5,8 @@ Centralize policies like "prefer same-block SSA; otherwise resolve with dominanc
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
import llvmlite.ir as ir
|
||||
import sys
|
||||
import os
|
||||
|
||||
def resolve_i64_strict(
|
||||
resolver,
|
||||
@ -21,19 +23,87 @@ def resolve_i64_strict(
|
||||
- If prefer_local and vmap has a same-block definition, reuse it.
|
||||
- Otherwise, delegate to resolver to localize with PHI/casts as needed.
|
||||
"""
|
||||
import os
|
||||
debug = os.environ.get('NYASH_LLVM_PHI_DEBUG') == '1'
|
||||
|
||||
# Prefer current vmap SSA first (block-local map is passed in vmap)
|
||||
val = vmap.get(value_id)
|
||||
if debug:
|
||||
val_type = type(val).__name__ if val is not None else "None"
|
||||
from trace import phi as trace_phi
|
||||
trace_phi(f"[resolve_i64_strict] v{value_id} vmap={val_type}")
|
||||
if prefer_local and val is not None:
|
||||
if debug:
|
||||
trace_phi(f"[resolve_i64_strict] v{value_id} -> local vmap")
|
||||
return val
|
||||
# If local map misses, try builder-global vmap (e.g., predeclared PHIs)
|
||||
try:
|
||||
if hasattr(resolver, 'global_vmap') and isinstance(resolver.global_vmap, dict):
|
||||
gval = resolver.global_vmap.get(value_id)
|
||||
if gval is not None:
|
||||
if debug:
|
||||
trace_phi(f"[resolve_i64_strict] v{value_id} -> global_vmap")
|
||||
return gval
|
||||
except Exception:
|
||||
pass
|
||||
# Fallback to resolver
|
||||
if resolver is None:
|
||||
if debug:
|
||||
trace_phi(f"[resolve_i64_strict] v{value_id} -> 0 (no resolver)")
|
||||
return ir.Constant(ir.IntType(64), 0)
|
||||
if debug:
|
||||
trace_phi(f"[resolve_i64_strict] v{value_id} -> resolver.resolve_i64")
|
||||
return resolver.resolve_i64(value_id, current_block, preds, block_end_values, vmap, bb_map)
|
||||
|
||||
def safe_vmap_write(vmap: Dict[int, Any], dst: int, value: Any, context: str = "", resolver=None, block_id: Optional[int] = None) -> None:
|
||||
"""
|
||||
PHI overwrite protection for vmap writes + def_blocks registration (P0-1 unified).
|
||||
|
||||
Implements fail-fast protection against ValueId namespace collisions.
|
||||
|
||||
Args:
|
||||
vmap: Value map to write to
|
||||
dst: Destination ValueId
|
||||
value: LLVM IR value to write
|
||||
context: Context string for error messages (e.g., "const", "binop")
|
||||
resolver: Optional resolver for def_blocks tracking (P0-1)
|
||||
block_id: Optional block ID for def_blocks registration (P0-1)
|
||||
|
||||
Behavior:
|
||||
- STRICT mode (NYASH_LLVM_STRICT=1): Raises error if overwriting PHI
|
||||
- TRACE mode (NYASH_LLVM_TRACE_VMAP=1): Logs warning but skips overwrite
|
||||
- Default: Silently skips PHI overwrite (SSOT: PHI defined once)
|
||||
- P0-1: If resolver and block_id provided, register in def_blocks
|
||||
"""
|
||||
existing = vmap.get(dst)
|
||||
if existing is not None and hasattr(existing, 'add_incoming'):
|
||||
# PHI node detected - overwrite forbidden (SSOT principle)
|
||||
if os.environ.get('NYASH_LLVM_STRICT') == '1':
|
||||
raise RuntimeError(
|
||||
f"[LLVM_PY/{context}] Cannot overwrite PHI dst={dst}. "
|
||||
f"ValueId namespace collision detected. "
|
||||
f"Existing: PHI node, Attempted: {type(value).__name__}"
|
||||
)
|
||||
# STRICT not enabled - warn and skip
|
||||
if os.environ.get('NYASH_LLVM_TRACE_VMAP') == '1':
|
||||
print(f"[vmap/warn] Skipping overwrite of PHI dst={dst} in context={context}", file=sys.stderr)
|
||||
return # Do not overwrite PHI
|
||||
|
||||
# Safe to write
|
||||
vmap[dst] = value
|
||||
|
||||
# Phase 131-12-P1: Trace successful write
|
||||
if os.environ.get('NYASH_LLVM_VMAP_TRACE') == '1':
|
||||
print(f"[vmap/write] dst={dst} written, vmap.keys()={sorted(vmap.keys())[:20]}", file=sys.stderr)
|
||||
|
||||
# P0-1: Register definition in def_blocks for dominance tracking (SSOT for all instructions)
|
||||
if resolver is not None and hasattr(resolver, 'def_blocks'):
|
||||
# Auto-detect block_id from resolver if not provided explicitly
|
||||
bid = block_id
|
||||
if bid is None and hasattr(resolver, 'current_block_id'):
|
||||
bid = resolver.current_block_id
|
||||
|
||||
if bid is not None:
|
||||
resolver.def_blocks.setdefault(dst, set()).add(bid)
|
||||
if os.environ.get('NYASH_LLVM_TRACE_VMAP') == '1':
|
||||
print(f"[vmap/def_blocks] Registered v{dst} in block {bid} (context={context})", file=sys.stderr)
|
||||
|
||||
Reference in New Issue
Block a user