🔧 refactor(llvm-py): Fix resolver PHI handling and add trace improvements
Changes to resolver.py: - Improved PHI value tracking in _value_at_end_i64() (lines 268-285) - Added trace logging for snap hits with PHI detection - Fixed PHI placeholder reuse logic to preserve dominance - PHI values now returned directly from snapshots when valid Changes to llvm_builder.py: - Fixed externcall instruction parsing (line 522: 'func' instead of 'name') - Improved block snapshot tracing (line 439) - Added PHI incoming metadata tracking (lines 316-376) - Enhanced definition tracking for lifetime hints This should help debug the string carry=0 issue in esc_dirname_smoke where PHI values were being incorrectly coerced instead of preserved. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -4,7 +4,7 @@ Handles +, -, *, /, %, &, |, ^, <<, >>
|
||||
"""
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict
|
||||
from typing import Dict, Optional, Any
|
||||
from .compare import lower_compare
|
||||
import llvmlite.ir as ir
|
||||
|
||||
@ -19,7 +19,9 @@ def lower_binop(
|
||||
current_block: ir.Block,
|
||||
preds=None,
|
||||
block_end_values=None,
|
||||
bb_map=None
|
||||
bb_map=None,
|
||||
*,
|
||||
dst_type: Optional[Any] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Lower MIR BinOp instruction
|
||||
@ -73,6 +75,13 @@ def lower_binop(
|
||||
# pointer present?
|
||||
is_ptr_side = (hasattr(lhs_raw, 'type') and isinstance(lhs_raw.type, ir.PointerType)) or \
|
||||
(hasattr(rhs_raw, 'type') and isinstance(rhs_raw.type, ir.PointerType))
|
||||
# Explicit dst_type hint from MIR JSON?
|
||||
force_string = False
|
||||
try:
|
||||
if isinstance(dst_type, dict) and dst_type.get('kind') == 'handle' and dst_type.get('box_type') == 'StringBox':
|
||||
force_string = True
|
||||
except Exception:
|
||||
pass
|
||||
# tagged string handles?(どちらかが string-ish のとき)
|
||||
any_tagged = False
|
||||
try:
|
||||
@ -84,10 +93,36 @@ def lower_binop(
|
||||
any_tagged = (lhs in resolver.string_literals) or (rhs in resolver.string_literals)
|
||||
except Exception:
|
||||
pass
|
||||
is_str = is_ptr_side or any_tagged
|
||||
is_str = force_string or is_ptr_side or any_tagged
|
||||
if is_str:
|
||||
# Helper: convert raw or resolved value to string handle
|
||||
def to_handle(raw, val, tag: str, vid: int):
|
||||
# If we already have an i64 in vmap (raw), prefer it
|
||||
if raw is not None and hasattr(raw, 'type') and isinstance(raw.type, ir.IntType) and raw.type.width == 64:
|
||||
is_tag = False
|
||||
try:
|
||||
if resolver is not None and hasattr(resolver, 'is_stringish'):
|
||||
is_tag = resolver.is_stringish(vid)
|
||||
except Exception:
|
||||
is_tag = False
|
||||
if force_string or is_tag:
|
||||
return raw
|
||||
# Heuristic: PHI values in string concat are typically handles; prefer pass-through
|
||||
try:
|
||||
raw_is_phi = hasattr(raw, 'add_incoming')
|
||||
except Exception:
|
||||
raw_is_phi = False
|
||||
if raw_is_phi:
|
||||
return raw
|
||||
# Otherwise, box numeric i64 to IntegerBox handle
|
||||
cal = None
|
||||
for f in builder.module.functions:
|
||||
if f.name == 'nyash.box.from_i64':
|
||||
cal = f; break
|
||||
if cal is None:
|
||||
cal = ir.Function(builder.module, ir.FunctionType(i64, [i64]), name='nyash.box.from_i64')
|
||||
v64 = raw
|
||||
return builder.call(cal, [v64], name=f"int_i2h_{tag}_{dst}")
|
||||
if raw is not None and hasattr(raw, 'type') and isinstance(raw.type, ir.PointerType):
|
||||
# pointer-to-array -> GEP
|
||||
try:
|
||||
@ -112,9 +147,16 @@ def lower_binop(
|
||||
is_tag = resolver.is_stringish(vid)
|
||||
except Exception:
|
||||
is_tag = False
|
||||
if is_tag:
|
||||
if force_string or is_tag:
|
||||
return val
|
||||
# Box numeric i64 to IntegerBox handle
|
||||
# Heuristic: if vmap has a PHI placeholder for this vid, treat as handle
|
||||
try:
|
||||
maybe_phi = vmap.get(vid)
|
||||
if maybe_phi is not None and hasattr(maybe_phi, 'add_incoming'):
|
||||
return val
|
||||
except Exception:
|
||||
pass
|
||||
# Otherwise, box numeric i64 to IntegerBox handle
|
||||
cal = None
|
||||
for f in builder.module.functions:
|
||||
if f.name == 'nyash.box.from_i64':
|
||||
|
||||
@ -213,11 +213,10 @@ def lower_boxcall(
|
||||
callee = _declare(module, "nyash.console.log", i64, [i8p])
|
||||
_ = builder.call(callee, [arg0_ptr], name="console_log_ptr")
|
||||
else:
|
||||
# Fallback: resolve i64 and prefer pointer API via to_i8p_h bridge
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
arg0 = resolver.resolve_i64(args[0], builder.block, preds, block_end_values, vmap, bb_map) if args else None
|
||||
else:
|
||||
arg0 = vmap.get(args[0]) if args else None
|
||||
# Fallback: prefer raw vmap value; resolve only if missing (avoid synthesizing PHIs here)
|
||||
arg0 = vmap.get(args[0]) if args else None
|
||||
if arg0 is None and resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
arg0 = resolver.resolve_i64(args[0], builder.block, preds, block_end_values, vmap, bb_map)
|
||||
if arg0 is None:
|
||||
arg0 = ir.Constant(i64, 0)
|
||||
# If we have a handle (i64), convert to i8* via bridge and log via pointer API
|
||||
|
||||
@ -4,7 +4,7 @@ Handles comparison operations (<, >, <=, >=, ==, !=)
|
||||
"""
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict
|
||||
from typing import Dict, Optional, Any
|
||||
from .externcall import lower_externcall
|
||||
|
||||
def lower_compare(
|
||||
@ -18,7 +18,8 @@ def lower_compare(
|
||||
current_block=None,
|
||||
preds=None,
|
||||
block_end_values=None,
|
||||
bb_map=None
|
||||
bb_map=None,
|
||||
meta: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Lower MIR Compare instruction
|
||||
@ -32,20 +33,27 @@ def lower_compare(
|
||||
vmap: Value map
|
||||
"""
|
||||
# Get operands
|
||||
if resolver is not None and preds is not None and block_end_values is not None and current_block is not None:
|
||||
lhs_val = resolver.resolve_i64(lhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||
rhs_val = resolver.resolve_i64(rhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||
else:
|
||||
lhs_val = vmap.get(lhs)
|
||||
rhs_val = vmap.get(rhs)
|
||||
# Prefer same-block SSA from vmap; fallback to resolver for cross-block dominance
|
||||
lhs_val = vmap.get(lhs)
|
||||
rhs_val = vmap.get(rhs)
|
||||
if (lhs_val is None or rhs_val is None) and resolver is not None and preds is not None and block_end_values is not None and current_block is not None:
|
||||
if lhs_val is None:
|
||||
lhs_val = resolver.resolve_i64(lhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||
if rhs_val is None:
|
||||
rhs_val = resolver.resolve_i64(rhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||
|
||||
i64 = ir.IntType(64)
|
||||
i8p = ir.IntType(8).as_pointer()
|
||||
|
||||
# String-aware equality: if either side is a pointer or tagged as string-ish, compare via eq_hh
|
||||
# String-aware equality: if meta marks string or either side is tagged string-ish,
|
||||
# compare handles directly via nyash.string.eq_hh
|
||||
if op in ('==','!='):
|
||||
lhs_ptr = hasattr(lhs_val, 'type') and isinstance(lhs_val.type, ir.PointerType)
|
||||
rhs_ptr = hasattr(rhs_val, 'type') and isinstance(rhs_val.type, ir.PointerType)
|
||||
force_string = False
|
||||
try:
|
||||
if isinstance(meta, dict) and meta.get('cmp_kind') == 'string':
|
||||
force_string = True
|
||||
except Exception:
|
||||
pass
|
||||
lhs_tag = False
|
||||
rhs_tag = False
|
||||
try:
|
||||
@ -54,28 +62,30 @@ def lower_compare(
|
||||
rhs_tag = resolver.is_stringish(rhs)
|
||||
except Exception:
|
||||
pass
|
||||
if lhs_ptr or rhs_ptr or lhs_tag or rhs_tag:
|
||||
# Convert both to handles (i64) then nyash.string.eq_hh
|
||||
# nyash.box.from_i8_string(i8*) -> i64
|
||||
box_from = None
|
||||
for f in builder.module.functions:
|
||||
if f.name == 'nyash.box.from_i8_string':
|
||||
box_from = f
|
||||
break
|
||||
if not box_from:
|
||||
box_from = ir.Function(builder.module, ir.FunctionType(i64, [i8p]), name='nyash.box.from_i8_string')
|
||||
def to_h(v):
|
||||
if hasattr(v, 'type') and isinstance(v.type, ir.PointerType):
|
||||
return builder.call(box_from, [v])
|
||||
else:
|
||||
# assume i64 handle or number; zext/trunc to i64 if needed
|
||||
if hasattr(v, 'type') and isinstance(v.type, ir.IntType) and v.type.width != 64:
|
||||
return builder.zext(v, i64) if v.type.width < 64 else builder.trunc(v, i64)
|
||||
if hasattr(v, 'type') and isinstance(v.type, ir.PointerType):
|
||||
return builder.ptrtoint(v, i64)
|
||||
return v if hasattr(v, 'type') else ir.Constant(i64, 0)
|
||||
lh = to_h(lhs_val)
|
||||
rh = to_h(rhs_val)
|
||||
if force_string or lhs_tag or rhs_tag:
|
||||
try:
|
||||
import os
|
||||
if os.environ.get('NYASH_LLVM_TRACE_VALUES') == '1':
|
||||
print(f"[compare] string-eq path: lhs={lhs} rhs={rhs} force={force_string} tagL={lhs_tag} tagR={rhs_tag}", flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
# Prefer same-block SSA (vmap) since string handles are produced in-place; fallback to resolver
|
||||
lh = lhs_val if lhs_val is not None else (
|
||||
resolver.resolve_i64(lhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||
if (resolver is not None and preds is not None and block_end_values is not None and current_block is not None) else ir.Constant(i64, 0)
|
||||
)
|
||||
rh = rhs_val if rhs_val is not None else (
|
||||
resolver.resolve_i64(rhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||
if (resolver is not None and preds is not None and block_end_values is not None and current_block is not None) else ir.Constant(i64, 0)
|
||||
)
|
||||
try:
|
||||
import os
|
||||
if os.environ.get('NYASH_LLVM_TRACE_VALUES') == '1':
|
||||
lz = isinstance(lh, ir.Constant) and getattr(getattr(lh,'constant',None),'constant',None) == 0
|
||||
rz = isinstance(rh, ir.Constant) and getattr(getattr(rh,'constant',None),'constant',None) == 0
|
||||
print(f"[compare] string-eq args: lh_is_const={isinstance(lh, ir.Constant)} rh_is_const={isinstance(rh, ir.Constant)}", flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
eqf = None
|
||||
for f in builder.module.functions:
|
||||
if f.name == 'nyash.string.eq_hh':
|
||||
|
||||
Reference in New Issue
Block a user