stage3: unify to cleanup; MIR return-defer; docs+smokes updated; LLVM(harness): finalize_phis ownership, ret.py simplified, uses-predeclare; cleanup return override green; method-postfix cleanup return WIP (PHI head)
This commit is contained in:
@ -9,7 +9,8 @@ from typing import Dict, Optional
|
||||
def lower_barrier(
|
||||
builder: ir.IRBuilder,
|
||||
barrier_type: str,
|
||||
ordering: Optional[str] = None
|
||||
ordering: Optional[str] = None,
|
||||
ctx=None,
|
||||
) -> None:
|
||||
"""
|
||||
Lower MIR Barrier instruction
|
||||
|
||||
@ -5,6 +5,7 @@ Experimental loop normalization following paper-e-loop-signal-ir
|
||||
|
||||
import os
|
||||
import llvmlite.ir as ir
|
||||
from phi_wiring import phi_at_block_head
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Tuple, List, Optional, Any
|
||||
from instructions.safepoint import insert_automatic_safepoint
|
||||
@ -109,9 +110,9 @@ def lower_while_loopform(
|
||||
i8 = ir.IntType(8)
|
||||
i64 = ir.IntType(64)
|
||||
|
||||
# Create PHI nodes
|
||||
tag_phi = builder.phi(i8, name=f"lf{loop_id}_tag")
|
||||
payload_phi = builder.phi(i64, name=f"lf{loop_id}_payload")
|
||||
# Create PHI nodes at the block head (LLVM requires PHIs grouped at top)
|
||||
tag_phi = phi_at_block_head(lf.dispatch, i8, name=f"lf{loop_id}_tag")
|
||||
payload_phi = phi_at_block_head(lf.dispatch, i64, name=f"lf{loop_id}_payload")
|
||||
|
||||
# Add incoming values
|
||||
# From header (condition false): Break signal
|
||||
|
||||
@ -4,6 +4,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
|
||||
|
||||
def lower_phi(
|
||||
@ -123,8 +124,8 @@ def lower_phi(
|
||||
vmap[dst_vid] = ir.Constant(phi_type, 0)
|
||||
return
|
||||
|
||||
# Create PHI instruction now and add incoming
|
||||
phi = builder.phi(phi_type, name=f"phi_{dst_vid}")
|
||||
# Create PHI instruction at the block head and add incoming
|
||||
phi = phi_at_block_head(current_block, phi_type, name=f"phi_{dst_vid}")
|
||||
for block, val in incoming_pairs:
|
||||
phi.add_incoming(val, block)
|
||||
|
||||
|
||||
@ -45,83 +45,73 @@ def lower_return(
|
||||
else:
|
||||
# Get return value (prefer resolver)
|
||||
ret_val = None
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
# Fast path: if vmap has a concrete non-PHI value defined in this block, use it directly
|
||||
if isinstance(value_id, int):
|
||||
tmp0 = vmap.get(value_id)
|
||||
try:
|
||||
# If this block has a declared PHI for the return value, force using the
|
||||
# local PHI placeholder to ensure dominance and let finalize_phis wire it.
|
||||
try:
|
||||
block_name = builder.block.name
|
||||
cur_bid = int(str(block_name).replace('bb',''))
|
||||
except Exception:
|
||||
cur_bid = -1
|
||||
try:
|
||||
bm = getattr(resolver, 'block_phi_incomings', {}) or {}
|
||||
except Exception:
|
||||
bm = {}
|
||||
if isinstance(value_id, int) and isinstance(bm.get(cur_bid), dict) and value_id in bm.get(cur_bid):
|
||||
# Reuse predeclared ret-phi when available
|
||||
cur = None
|
||||
is_phi0 = hasattr(tmp0, 'add_incoming')
|
||||
except Exception:
|
||||
is_phi0 = False
|
||||
if tmp0 is not None and not is_phi0:
|
||||
ret_val = tmp0
|
||||
if ret_val is None:
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
# Multi-pred special case: construct a PHI at block head and wire per-pred values
|
||||
# to keep PHIs grouped at top and avoid late synthesis that violates ordering.
|
||||
cur_bid = int(str(builder.block.name).replace('bb','')) if hasattr(builder.block, 'name') else -1
|
||||
pred_ids = [p for p in preds.get(cur_bid, []) if p != cur_bid] if isinstance(preds, dict) else []
|
||||
if isinstance(value_id, int) and len(pred_ids) > 1 and isinstance(return_type, ir.IntType):
|
||||
# Create PHI at block head
|
||||
btop = ir.IRBuilder(builder.block)
|
||||
try:
|
||||
rp = getattr(resolver, 'ret_phi_map', {}) or {}
|
||||
key = (int(cur_bid), int(value_id))
|
||||
if key in rp:
|
||||
cur = rp[key]
|
||||
except Exception:
|
||||
cur = None
|
||||
if cur is None:
|
||||
btop = ir.IRBuilder(builder.block)
|
||||
try:
|
||||
btop.position_at_start(builder.block)
|
||||
except Exception:
|
||||
pass
|
||||
# Reuse existing local phi if present; otherwise create
|
||||
cur = vmap.get(value_id)
|
||||
need_new = True
|
||||
try:
|
||||
need_new = not (cur is not None and hasattr(cur, 'add_incoming') and getattr(getattr(cur, 'basic_block', None), 'name', None) == builder.block.name)
|
||||
except Exception:
|
||||
need_new = True
|
||||
if need_new:
|
||||
cur = btop.phi(ir.IntType(64), name=f"phi_ret_{value_id}")
|
||||
# Bind to maps
|
||||
vmap[value_id] = cur
|
||||
try:
|
||||
if hasattr(resolver, 'global_vmap') and isinstance(resolver.global_vmap, dict):
|
||||
resolver.global_vmap[value_id] = cur
|
||||
btop.position_at_start(builder.block)
|
||||
except Exception:
|
||||
pass
|
||||
ret_val = cur
|
||||
if ret_val is not None:
|
||||
builder.ret(ret_val)
|
||||
return
|
||||
if isinstance(return_type, ir.PointerType):
|
||||
ret_val = resolver.resolve_ptr(value_id, builder.block, preds, block_end_values, vmap)
|
||||
ph = btop.phi(return_type, name=f"res_phi_{value_id}_{cur_bid}")
|
||||
# Wire per-pred end values
|
||||
for pred_bid in pred_ids:
|
||||
pred_bb = bb_map.get(pred_bid) if isinstance(bb_map, dict) else None
|
||||
if pred_bb is None:
|
||||
continue
|
||||
val = resolver._value_at_end_i64(value_id, pred_bid, preds, block_end_values, vmap, bb_map)
|
||||
if not hasattr(val, 'type'):
|
||||
val = ir.Constant(return_type, 0)
|
||||
ph.add_incoming(val, pred_bb)
|
||||
ret_val = ph
|
||||
else:
|
||||
# Prefer pointer→handle reboxing for string-ish returns even if function return type is i64
|
||||
is_stringish = False
|
||||
if hasattr(resolver, 'is_stringish'):
|
||||
try:
|
||||
is_stringish = resolver.is_stringish(int(value_id))
|
||||
except Exception:
|
||||
is_stringish = False
|
||||
if is_stringish and hasattr(resolver, 'string_ptrs') and int(value_id) in getattr(resolver, 'string_ptrs'):
|
||||
# Re-box known string pointer to handle
|
||||
p = resolver.string_ptrs[int(value_id)]
|
||||
i8p = ir.IntType(8).as_pointer()
|
||||
i64 = ir.IntType(64)
|
||||
boxer = None
|
||||
for f in builder.module.functions:
|
||||
if f.name == 'nyash.box.from_i8_string':
|
||||
boxer = f; break
|
||||
if boxer is None:
|
||||
boxer = ir.Function(builder.module, ir.FunctionType(i64, [i8p]), name='nyash.box.from_i8_string')
|
||||
ret_val = builder.call(boxer, [p], name='ret_ptr2h')
|
||||
# Resolve direct value
|
||||
if isinstance(return_type, ir.PointerType):
|
||||
ret_val = resolver.resolve_ptr(value_id, builder.block, preds, block_end_values, vmap)
|
||||
else:
|
||||
ret_val = resolver.resolve_i64(value_id, builder.block, preds, block_end_values, vmap, bb_map)
|
||||
except Exception:
|
||||
ret_val = None
|
||||
is_stringish = False
|
||||
if hasattr(resolver, 'is_stringish'):
|
||||
try:
|
||||
is_stringish = resolver.is_stringish(int(value_id))
|
||||
except Exception:
|
||||
is_stringish = False
|
||||
if is_stringish and hasattr(resolver, 'string_ptrs') and int(value_id) in getattr(resolver, 'string_ptrs'):
|
||||
p = resolver.string_ptrs[int(value_id)]
|
||||
i8p = ir.IntType(8).as_pointer()
|
||||
i64 = ir.IntType(64)
|
||||
boxer = None
|
||||
for f in builder.module.functions:
|
||||
if f.name == 'nyash.box.from_i8_string':
|
||||
boxer = f; break
|
||||
if boxer is None:
|
||||
boxer = ir.Function(builder.module, ir.FunctionType(i64, [i8p]), name='nyash.box.from_i8_string')
|
||||
ret_val = builder.call(boxer, [p], name='ret_ptr2h')
|
||||
else:
|
||||
ret_val = resolver.resolve_i64(value_id, builder.block, preds, block_end_values, vmap, bb_map)
|
||||
|
||||
if ret_val is None:
|
||||
ret_val = vmap.get(value_id)
|
||||
# Default to vmap (non-PHI) if available
|
||||
tmp = vmap.get(value_id)
|
||||
try:
|
||||
is_phi = hasattr(tmp, 'add_incoming')
|
||||
except Exception:
|
||||
is_phi = False
|
||||
if tmp is not None and not is_phi:
|
||||
ret_val = tmp
|
||||
if not ret_val:
|
||||
# Default based on return type
|
||||
if isinstance(return_type, ir.IntType):
|
||||
|
||||
@ -32,6 +32,12 @@ from instructions.controlflow.while_ import lower_while_regular
|
||||
from phi_wiring import setup_phi_placeholders as _setup_phi_placeholders, finalize_phis as _finalize_phis
|
||||
from trace import debug as trace_debug
|
||||
from trace import phi as trace_phi
|
||||
try:
|
||||
# Structured JSON trace for PHI wiring (shared with phi_wiring)
|
||||
from phi_wiring.common import trace as trace_phi_json
|
||||
except Exception:
|
||||
def trace_phi_json(_msg):
|
||||
pass
|
||||
from prepass.loops import detect_simple_while
|
||||
from prepass.if_merge import plan_ret_phi_predeclare
|
||||
from build_ctx import BuildCtx
|
||||
@ -397,6 +403,90 @@ class NyashLLVMBuilder:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Predeclare PHIs for values used in a block but defined in predecessors (multi-pred only).
|
||||
# This keeps PHI nodes grouped at the top and avoids late synthesis during operand resolution.
|
||||
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 []:
|
||||
# Minimal keys: lhs/rhs (binop), value (ret/copy), cond (branch), box_val (boxcall)
|
||||
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
|
||||
# Ensure map for declared incomings exists
|
||||
if not hasattr(self, 'block_phi_incomings') or self.block_phi_incomings is None:
|
||||
self.block_phi_incomings = {}
|
||||
for bid, blk in block_by_id.items():
|
||||
# Only multi-pred blocks need PHIs
|
||||
try:
|
||||
preds_raw = [p for p in local_preds.get(int(bid), []) if p != int(bid)]
|
||||
except Exception:
|
||||
preds_raw = []
|
||||
# Dedup preds preserve order
|
||||
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
|
||||
bb0 = self.bb_map.get(int(bid))
|
||||
if bb0 is None:
|
||||
continue
|
||||
b0 = ir.IRBuilder(bb0)
|
||||
try:
|
||||
b0.position_at_start(bb0)
|
||||
except Exception:
|
||||
pass
|
||||
for vid in need:
|
||||
# Skip if we already have a PHI mapped for (bid, vid)
|
||||
cur = self.vmap.get(int(vid))
|
||||
has_phi_here = False
|
||||
try:
|
||||
has_phi_here = (
|
||||
cur is not None and hasattr(cur, 'add_incoming') and
|
||||
getattr(getattr(cur, 'basic_block', None), 'name', None) == bb0.name
|
||||
)
|
||||
except Exception:
|
||||
has_phi_here = False
|
||||
if not has_phi_here:
|
||||
ph = b0.phi(self.i64, name=f"phi_{vid}")
|
||||
self.vmap[int(vid)] = ph
|
||||
# Record incoming metadata for finalize_phis (pred -> same vid)
|
||||
try:
|
||||
self.block_phi_incomings.setdefault(int(bid), {}).setdefault(int(vid), [])
|
||||
# Overwrite with dedup list of (pred, vid)
|
||||
self.block_phi_incomings[int(bid)][int(vid)] = [(int(p), int(vid)) for p in preds_list]
|
||||
except Exception:
|
||||
pass
|
||||
# Expose to resolver
|
||||
try:
|
||||
self.resolver.block_phi_incomings = self.block_phi_incomings
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Optional: simple loop prepass → synthesize a structured while body
|
||||
loop_plan = None
|
||||
try:
|
||||
@ -444,9 +534,19 @@ class NyashLLVMBuilder:
|
||||
try:
|
||||
# Use a clean per-while vmap context seeded from global placeholders
|
||||
self._current_vmap = dict(self.vmap)
|
||||
ok = lower_while_loopform(builder, func, cond_vid, body_insts,
|
||||
self.loop_count, self.vmap, self.bb_map,
|
||||
self.resolver, self.preds, self.block_end_values)
|
||||
ok = lower_while_loopform(
|
||||
builder,
|
||||
func,
|
||||
cond_vid,
|
||||
body_insts,
|
||||
self.loop_count,
|
||||
self.vmap,
|
||||
self.bb_map,
|
||||
self.resolver,
|
||||
self.preds,
|
||||
self.block_end_values,
|
||||
getattr(self, 'ctx', None),
|
||||
)
|
||||
except Exception:
|
||||
ok = False
|
||||
if not ok:
|
||||
@ -810,7 +910,11 @@ class NyashLLVMBuilder:
|
||||
try:
|
||||
import os
|
||||
keys = sorted(list(snap.keys()))
|
||||
trace_phi(f"[builder] snapshot bb{bid} keys={keys[:20]}...")
|
||||
# Emit structured snapshot event for up to first 20 keys
|
||||
try:
|
||||
trace_phi_json({"phi": "snapshot", "block": int(bid), "keys": [int(k) for k in keys[:20]]})
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
# Record block-local definitions for lifetime hinting
|
||||
@ -941,7 +1045,7 @@ class NyashLLVMBuilder:
|
||||
|
||||
elif op == "barrier":
|
||||
barrier_type = inst.get("type", "memory")
|
||||
lower_barrier(builder, barrier_type)
|
||||
lower_barrier(builder, barrier_type, ctx=getattr(self, 'ctx', None))
|
||||
|
||||
elif op == "while":
|
||||
# Experimental LoopForm lowering
|
||||
@ -1001,7 +1105,10 @@ class NyashLLVMBuilder:
|
||||
for fr in from_list:
|
||||
succs.setdefault(fr, []).append(to_bid)
|
||||
for block_id, dst_map in (getattr(self, 'block_phi_incomings', {}) or {}).items():
|
||||
trace_phi(f"[finalize] bb{block_id} dsts={list(dst_map.keys())}")
|
||||
try:
|
||||
trace_phi_json({"phi": "finalize_begin", "block": int(block_id), "dsts": [int(k) for k in (dst_map or {}).keys()]})
|
||||
except Exception:
|
||||
pass
|
||||
bb = self.bb_map.get(block_id)
|
||||
if bb is None:
|
||||
continue
|
||||
@ -1011,7 +1118,10 @@ class NyashLLVMBuilder:
|
||||
except Exception:
|
||||
pass
|
||||
for dst_vid, incoming in (dst_map or {}).items():
|
||||
trace_phi(f"[finalize] dst v{dst_vid} incoming={incoming}")
|
||||
try:
|
||||
trace_phi_json({"phi": "finalize_dst", "block": int(block_id), "dst": int(dst_vid), "incoming": [(int(v), int(b)) for (b, v) in [(b, v) for (v, b) in (incoming or [])]]})
|
||||
except Exception:
|
||||
pass
|
||||
# Ensure placeholder exists at block head
|
||||
# Prefer predeclared ret-phi when available and force using it.
|
||||
predecl = getattr(self, 'predeclared_ret_phis', {}) if hasattr(self, 'predeclared_ret_phis') else {}
|
||||
@ -1038,7 +1148,10 @@ class NyashLLVMBuilder:
|
||||
phi = b.phi(self.i64, name=f"phi_{dst_vid}")
|
||||
self.vmap[dst_vid] = phi
|
||||
n = getattr(phi, 'name', b'').decode() if hasattr(getattr(phi, 'name', None), 'decode') else str(getattr(phi, 'name', ''))
|
||||
trace_phi(f"[finalize] target phi={n}")
|
||||
try:
|
||||
trace_phi_json({"phi": "finalize_target", "block": int(block_id), "dst": int(dst_vid), "ir": str(n)})
|
||||
except Exception:
|
||||
pass
|
||||
# Wire incoming per CFG predecessor; map src_vid when provided
|
||||
preds_raw = [p for p in self.preds.get(block_id, []) if p != block_id]
|
||||
# Deduplicate while preserving order
|
||||
@ -1214,8 +1327,7 @@ def main():
|
||||
if dummy:
|
||||
# Emit dummy ny_main
|
||||
ir_text = builder._create_dummy_main()
|
||||
if os.environ.get('NYASH_CLI_VERBOSE') == '1':
|
||||
print(f"[Python LLVM] Generated dummy IR:\n{ir_text}")
|
||||
trace_debug(f"[Python LLVM] Generated dummy IR:\n{ir_text}")
|
||||
builder.compile_to_object(output_file)
|
||||
print(f"Compiled to {output_file}")
|
||||
return
|
||||
@ -1229,8 +1341,7 @@ def main():
|
||||
mir_json = json.load(f)
|
||||
|
||||
llvm_ir = builder.build_from_mir(mir_json)
|
||||
if os.environ.get('NYASH_CLI_VERBOSE') == '1':
|
||||
print(f"[Python LLVM] Generated LLVM IR (see NYASH_LLVM_DUMP_IR or tmp/nyash_harness.ll)")
|
||||
trace_debug("[Python LLVM] Generated LLVM IR (see NYASH_LLVM_DUMP_IR or tmp/nyash_harness.ll)")
|
||||
|
||||
builder.compile_to_object(output_file)
|
||||
print(f"Compiled to {output_file}")
|
||||
|
||||
@ -1,270 +0,0 @@
|
||||
"""
|
||||
PHI wiring helpers
|
||||
|
||||
- setup_phi_placeholders: Predeclare PHIs and collect incoming metadata
|
||||
- finalize_phis: Wire PHI incomings using end-of-block snapshots and resolver
|
||||
|
||||
These operate on the NyashLLVMBuilder instance to keep changes minimal.
|
||||
|
||||
Refactor note: core responsibilities are split into smaller helpers so they
|
||||
can be unit-tested in isolation.
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Any, Optional, Tuple
|
||||
import os
|
||||
import json
|
||||
import llvmlite.ir as ir
|
||||
|
||||
# ---- Small helpers (analyzable/testable) ----
|
||||
|
||||
def _collect_produced_stringish(blocks: List[Dict[str, Any]]) -> Dict[int, bool]:
|
||||
produced_str: Dict[int, bool] = {}
|
||||
for block_data in blocks:
|
||||
for inst in block_data.get("instructions", []) or []:
|
||||
try:
|
||||
opx = inst.get("op")
|
||||
dstx = inst.get("dst")
|
||||
if dstx is None:
|
||||
continue
|
||||
is_str = False
|
||||
if opx == "const":
|
||||
v = inst.get("value", {}) or {}
|
||||
t = v.get("type")
|
||||
if t == "string" or (isinstance(t, dict) and t.get("kind") in ("handle", "ptr") and t.get("box_type") == "StringBox"):
|
||||
is_str = True
|
||||
elif opx in ("binop", "boxcall", "externcall"):
|
||||
t = inst.get("dst_type")
|
||||
if isinstance(t, dict) and t.get("kind") == "handle" and t.get("box_type") == "StringBox":
|
||||
is_str = True
|
||||
if is_str:
|
||||
produced_str[int(dstx)] = True
|
||||
except Exception:
|
||||
pass
|
||||
return produced_str
|
||||
|
||||
def _trace(msg: Any):
|
||||
if os.environ.get("NYASH_LLVM_TRACE_PHI", "0") == "1":
|
||||
out = os.environ.get("NYASH_LLVM_TRACE_OUT")
|
||||
# Format as single-line JSON for machine parsing
|
||||
if not isinstance(msg, (str, bytes)):
|
||||
try:
|
||||
msg = json.dumps(msg, ensure_ascii=False, separators=(",", ":"))
|
||||
except Exception:
|
||||
msg = str(msg)
|
||||
if out:
|
||||
try:
|
||||
with open(out, "a", encoding="utf-8") as f:
|
||||
f.write(msg.rstrip() + "\n")
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
print(msg)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def analyze_incomings(blocks: List[Dict[str, Any]]) -> Dict[int, Dict[int, List[Tuple[int, int]]]]:
|
||||
"""Return block_phi_incomings map: block_id -> { dst_vid -> [(decl_b, v_src), ...] }"""
|
||||
result: Dict[int, Dict[int, List[Tuple[int, int]]]] = {}
|
||||
for block_data in blocks:
|
||||
bid0 = block_data.get("id", 0)
|
||||
for inst in block_data.get("instructions", []) or []:
|
||||
if inst.get("op") == "phi":
|
||||
try:
|
||||
dst0 = int(inst.get("dst"))
|
||||
incoming0 = inst.get("incoming", []) or []
|
||||
except Exception:
|
||||
dst0 = None; incoming0 = []
|
||||
if dst0 is None:
|
||||
continue
|
||||
try:
|
||||
pairs = [(int(b), int(v)) for (v, b) in incoming0]
|
||||
result.setdefault(int(bid0), {})[dst0] = pairs
|
||||
_trace({
|
||||
"phi": "analyze",
|
||||
"block": int(bid0),
|
||||
"dst": dst0,
|
||||
"incoming": pairs,
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
return result
|
||||
|
||||
def ensure_phi(builder, block_id: int, dst_vid: int, bb: ir.Block) -> ir.Instruction:
|
||||
"""Ensure a PHI placeholder exists at the block head for dst_vid and return it."""
|
||||
b = ir.IRBuilder(bb)
|
||||
try:
|
||||
b.position_at_start(bb)
|
||||
except Exception:
|
||||
pass
|
||||
# Prefer predeclared PHIs (e.g., from if-merge prepass)
|
||||
predecl = getattr(builder, 'predeclared_ret_phis', {}) if hasattr(builder, 'predeclared_ret_phis') else {}
|
||||
phi = predecl.get((int(block_id), int(dst_vid))) if predecl else None
|
||||
if phi is not None:
|
||||
builder.vmap[dst_vid] = phi
|
||||
_trace({"phi": "ensure_predecl", "block": int(block_id), "dst": int(dst_vid)})
|
||||
return phi
|
||||
# Reuse current if it is a PHI in the correct block
|
||||
cur = builder.vmap.get(dst_vid)
|
||||
try:
|
||||
if cur is not None and hasattr(cur, 'add_incoming') and getattr(getattr(cur, 'basic_block', None), 'name', None) == bb.name:
|
||||
return cur
|
||||
except Exception:
|
||||
pass
|
||||
# Create a new placeholder
|
||||
ph = b.phi(builder.i64, name=f"phi_{dst_vid}")
|
||||
builder.vmap[dst_vid] = ph
|
||||
_trace({"phi": "ensure_create", "block": int(block_id), "dst": int(dst_vid)})
|
||||
return ph
|
||||
|
||||
def _build_succs(preds: Dict[int, List[int]]) -> Dict[int, List[int]]:
|
||||
succs: Dict[int, List[int]] = {}
|
||||
for to_bid, from_list in (preds or {}).items():
|
||||
for fr in from_list:
|
||||
succs.setdefault(fr, []).append(to_bid)
|
||||
return succs
|
||||
|
||||
def _nearest_pred_on_path(succs: Dict[int, List[int]], preds_list: List[int], decl_b: int, target_bid: int) -> Optional[int]:
|
||||
from collections import deque
|
||||
q = deque([decl_b])
|
||||
visited = set([decl_b])
|
||||
parent: Dict[int, Any] = {decl_b: None}
|
||||
while q:
|
||||
cur = q.popleft()
|
||||
if cur == target_bid:
|
||||
par = parent.get(target_bid)
|
||||
return par if par in preds_list else None
|
||||
for nx in succs.get(cur, []):
|
||||
if nx not in visited:
|
||||
visited.add(nx)
|
||||
parent[nx] = cur
|
||||
q.append(nx)
|
||||
return None
|
||||
|
||||
def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[int, int]]):
|
||||
"""Wire PHI incoming edges for (block_id, dst_vid) using declared (decl_b, v_src) pairs."""
|
||||
bb = builder.bb_map.get(block_id)
|
||||
if bb is None:
|
||||
return
|
||||
phi = ensure_phi(builder, block_id, dst_vid, bb)
|
||||
# Normalize predecessor list
|
||||
preds_raw = [p for p in builder.preds.get(block_id, []) if p != block_id]
|
||||
seen = set()
|
||||
preds_list: List[int] = []
|
||||
for p in preds_raw:
|
||||
if p not in seen:
|
||||
preds_list.append(p)
|
||||
seen.add(p)
|
||||
succs = _build_succs(builder.preds)
|
||||
# Precompute a non-self initial source for self-carry
|
||||
init_src_vid = None
|
||||
for (_bd0, vs0) in incoming:
|
||||
try:
|
||||
vi = int(vs0)
|
||||
except Exception:
|
||||
continue
|
||||
if vi != int(dst_vid):
|
||||
init_src_vid = vi
|
||||
break
|
||||
chosen: Dict[int, ir.Value] = {}
|
||||
for (b_decl, v_src) in incoming:
|
||||
try:
|
||||
bd = int(b_decl); vs = int(v_src)
|
||||
except Exception:
|
||||
continue
|
||||
pred_match = _nearest_pred_on_path(succs, preds_list, bd, block_id)
|
||||
if pred_match is None:
|
||||
_trace({
|
||||
"phi": "wire_skip_no_path",
|
||||
"decl_b": bd,
|
||||
"target": int(block_id),
|
||||
"src": vs,
|
||||
})
|
||||
continue
|
||||
if vs == int(dst_vid) and init_src_vid is not None:
|
||||
vs = int(init_src_vid)
|
||||
try:
|
||||
val = builder.resolver._value_at_end_i64(vs, pred_match, builder.preds, builder.block_end_values, builder.vmap, builder.bb_map)
|
||||
except Exception:
|
||||
val = None
|
||||
if val is None:
|
||||
val = ir.Constant(builder.i64, 0)
|
||||
chosen[pred_match] = val
|
||||
_trace({
|
||||
"phi": "wire_choose",
|
||||
"pred": int(pred_match),
|
||||
"dst": int(dst_vid),
|
||||
"src": int(vs),
|
||||
})
|
||||
for pred_bid, val in chosen.items():
|
||||
pred_bb = builder.bb_map.get(pred_bid)
|
||||
if pred_bb is None:
|
||||
continue
|
||||
phi.add_incoming(val, pred_bb)
|
||||
_trace({"phi": "add_incoming", "dst": int(dst_vid), "pred": int(pred_bid)})
|
||||
|
||||
# ---- Public API (used by llvm_builder) ----
|
||||
|
||||
def setup_phi_placeholders(builder, blocks: List[Dict[str, Any]]):
|
||||
"""Predeclare PHIs and collect incoming metadata for finalize_phis.
|
||||
|
||||
This pass is function-local and must be invoked after basic blocks are
|
||||
created and before lowering individual blocks. It also tags string-ish
|
||||
values eagerly to help downstream resolvers choose correct intrinsics.
|
||||
"""
|
||||
try:
|
||||
produced_str = _collect_produced_stringish(blocks)
|
||||
builder.block_phi_incomings = analyze_incomings(blocks)
|
||||
_trace({"phi": "setup", "produced_str_keys": list(produced_str.keys())})
|
||||
# Materialize placeholders and propagate stringish tags
|
||||
for block_data in blocks:
|
||||
bid0 = block_data.get("id", 0)
|
||||
bb0 = builder.bb_map.get(bid0)
|
||||
for inst in block_data.get("instructions", []) or []:
|
||||
if inst.get("op") != "phi":
|
||||
continue
|
||||
try:
|
||||
dst0 = int(inst.get("dst"))
|
||||
incoming0 = inst.get("incoming", []) or []
|
||||
except Exception:
|
||||
dst0 = None; incoming0 = []
|
||||
if dst0 is None or bb0 is None:
|
||||
continue
|
||||
_ = ensure_phi(builder, bid0, dst0, bb0)
|
||||
# Tag propagation
|
||||
try:
|
||||
dst_type0 = inst.get("dst_type")
|
||||
mark_str = isinstance(dst_type0, dict) and dst_type0.get("kind") == "handle" and dst_type0.get("box_type") == "StringBox"
|
||||
if not mark_str:
|
||||
for (_b_decl_i, v_src_i) in incoming0:
|
||||
try:
|
||||
if produced_str.get(int(v_src_i)):
|
||||
mark_str = True; break
|
||||
except Exception:
|
||||
pass
|
||||
if mark_str and hasattr(builder.resolver, 'mark_string'):
|
||||
builder.resolver.mark_string(int(dst0))
|
||||
except Exception:
|
||||
pass
|
||||
# Definition hint: PHI defines dst in this block
|
||||
try:
|
||||
builder.def_blocks.setdefault(int(dst0), set()).add(int(bid0))
|
||||
except Exception:
|
||||
pass
|
||||
# Sync to resolver
|
||||
try:
|
||||
builder.resolver.block_phi_incomings = builder.block_phi_incomings
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def finalize_phis(builder):
|
||||
"""Finalize PHIs declared in JSON by wiring incoming edges at block heads.
|
||||
Uses resolver._value_at_end_i64 to materialize values at predecessor ends.
|
||||
"""
|
||||
for block_id, dst_map in (getattr(builder, 'block_phi_incomings', {}) or {}).items():
|
||||
for dst_vid, incoming in (dst_map or {}).items():
|
||||
wire_incomings(builder, int(block_id), int(dst_vid), incoming)
|
||||
_trace({"phi": "finalize", "block": int(block_id), "dst": int(dst_vid)})
|
||||
33
src/llvm_py/phi_wiring/__init__.py
Normal file
33
src/llvm_py/phi_wiring/__init__.py
Normal file
@ -0,0 +1,33 @@
|
||||
"""
|
||||
PHI wiring package
|
||||
|
||||
Submodules
|
||||
- analysis: analyze_incomings and stringish production scan
|
||||
- wiring: ensure_phi, wire_incomings, finalize_phis
|
||||
- tagging: setup_phi_placeholders (predeclare + tagging + sync)
|
||||
|
||||
This package re-exports the primary helpers for backward compatibility with
|
||||
`from phi_wiring import ...` and `from src.llvm_py import phi_wiring` usage.
|
||||
"""
|
||||
|
||||
from .analysis import analyze_incomings, collect_produced_stringish
|
||||
from .wiring import ensure_phi, wire_incomings, finalize_phis, build_succs, nearest_pred_on_path, phi_at_block_head
|
||||
from .tagging import setup_phi_placeholders
|
||||
|
||||
# Backward-compatible aliases for tests that used private helpers
|
||||
_build_succs = build_succs
|
||||
_nearest_pred_on_path = nearest_pred_on_path
|
||||
|
||||
__all__ = [
|
||||
"analyze_incomings",
|
||||
"collect_produced_stringish",
|
||||
"ensure_phi",
|
||||
"wire_incomings",
|
||||
"finalize_phis",
|
||||
"phi_at_block_head",
|
||||
"build_succs",
|
||||
"nearest_pred_on_path",
|
||||
"setup_phi_placeholders",
|
||||
"_build_succs",
|
||||
"_nearest_pred_on_path",
|
||||
]
|
||||
68
src/llvm_py/phi_wiring/analysis.py
Normal file
68
src/llvm_py/phi_wiring/analysis.py
Normal file
@ -0,0 +1,68 @@
|
||||
from __future__ import annotations
|
||||
from typing import Dict, List, Any, Tuple
|
||||
|
||||
from .common import trace
|
||||
|
||||
|
||||
def collect_produced_stringish(blocks: List[Dict[str, Any]]) -> Dict[int, bool]:
|
||||
produced_str: Dict[int, bool] = {}
|
||||
for block_data in blocks:
|
||||
for inst in block_data.get("instructions", []) or []:
|
||||
try:
|
||||
opx = inst.get("op")
|
||||
dstx = inst.get("dst")
|
||||
if dstx is None:
|
||||
continue
|
||||
is_str = False
|
||||
if opx == "const":
|
||||
v = inst.get("value", {}) or {}
|
||||
t = v.get("type")
|
||||
if t == "string" or (
|
||||
isinstance(t, dict)
|
||||
and t.get("kind") in ("handle", "ptr")
|
||||
and t.get("box_type") == "StringBox"
|
||||
):
|
||||
is_str = True
|
||||
elif opx in ("binop", "boxcall", "externcall"):
|
||||
t = inst.get("dst_type")
|
||||
if (
|
||||
isinstance(t, dict)
|
||||
and t.get("kind") == "handle"
|
||||
and t.get("box_type") == "StringBox"
|
||||
):
|
||||
is_str = True
|
||||
if is_str:
|
||||
produced_str[int(dstx)] = True
|
||||
except Exception:
|
||||
pass
|
||||
return produced_str
|
||||
|
||||
|
||||
def analyze_incomings(blocks: List[Dict[str, Any]]) -> Dict[int, Dict[int, List[Tuple[int, int]]]]:
|
||||
"""Return block_phi_incomings: block_id -> { dst_vid -> [(decl_b, v_src), ...] }"""
|
||||
result: Dict[int, Dict[int, List[Tuple[int, int]]]] = {}
|
||||
for block_data in blocks:
|
||||
bid0 = block_data.get("id", 0)
|
||||
for inst in block_data.get("instructions", []) or []:
|
||||
if inst.get("op") == "phi":
|
||||
try:
|
||||
dst0 = int(inst.get("dst"))
|
||||
incoming0 = inst.get("incoming", []) or []
|
||||
except Exception:
|
||||
dst0 = None
|
||||
incoming0 = []
|
||||
if dst0 is None:
|
||||
continue
|
||||
try:
|
||||
pairs = [(int(b), int(v)) for (v, b) in incoming0]
|
||||
result.setdefault(int(bid0), {})[dst0] = pairs
|
||||
trace({
|
||||
"phi": "analyze",
|
||||
"block": int(bid0),
|
||||
"dst": dst0,
|
||||
"incoming": pairs,
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
return result
|
||||
|
||||
26
src/llvm_py/phi_wiring/common.py
Normal file
26
src/llvm_py/phi_wiring/common.py
Normal file
@ -0,0 +1,26 @@
|
||||
from __future__ import annotations
|
||||
from typing import Any
|
||||
import os
|
||||
import json
|
||||
|
||||
def trace(msg: Any):
|
||||
if os.environ.get("NYASH_LLVM_TRACE_PHI", "0") != "1":
|
||||
return
|
||||
out = os.environ.get("NYASH_LLVM_TRACE_OUT")
|
||||
if not isinstance(msg, (str, bytes)):
|
||||
try:
|
||||
msg = json.dumps(msg, ensure_ascii=False, separators=(",", ":"))
|
||||
except Exception:
|
||||
msg = str(msg)
|
||||
if out:
|
||||
try:
|
||||
with open(out, "a", encoding="utf-8") as f:
|
||||
f.write(msg.rstrip() + "\n")
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
print(msg)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
66
src/llvm_py/phi_wiring/tagging.py
Normal file
66
src/llvm_py/phi_wiring/tagging.py
Normal file
@ -0,0 +1,66 @@
|
||||
from __future__ import annotations
|
||||
from typing import Dict, List, Any
|
||||
|
||||
from .common import trace
|
||||
from .analysis import analyze_incomings, collect_produced_stringish
|
||||
from .wiring import ensure_phi
|
||||
|
||||
|
||||
def setup_phi_placeholders(builder, blocks: List[Dict[str, Any]]):
|
||||
"""Predeclare PHIs and collect incoming metadata for finalize_phis.
|
||||
|
||||
Function-local: must be invoked after basic blocks are created and before
|
||||
lowering individual blocks. Also tags string-ish values to help downstream
|
||||
resolvers.
|
||||
"""
|
||||
try:
|
||||
produced_str = collect_produced_stringish(blocks)
|
||||
builder.block_phi_incomings = analyze_incomings(blocks)
|
||||
trace({"phi": "setup", "produced_str_keys": list(produced_str.keys())})
|
||||
for block_data in blocks:
|
||||
bid0 = block_data.get("id", 0)
|
||||
bb0 = builder.bb_map.get(bid0)
|
||||
for inst in block_data.get("instructions", []) or []:
|
||||
if inst.get("op") != "phi":
|
||||
continue
|
||||
try:
|
||||
dst0 = int(inst.get("dst"))
|
||||
incoming0 = inst.get("incoming", []) or []
|
||||
except Exception:
|
||||
dst0 = None
|
||||
incoming0 = []
|
||||
if dst0 is None or bb0 is None:
|
||||
continue
|
||||
_ = ensure_phi(builder, bid0, dst0, bb0)
|
||||
# Tag propagation
|
||||
try:
|
||||
dst_type0 = inst.get("dst_type")
|
||||
mark_str = (
|
||||
isinstance(dst_type0, dict)
|
||||
and dst_type0.get("kind") == "handle"
|
||||
and dst_type0.get("box_type") == "StringBox"
|
||||
)
|
||||
if not mark_str:
|
||||
# JSON v0 incoming pairs are (value, block)
|
||||
for (v_src_i, _b_decl_i) in incoming0:
|
||||
try:
|
||||
if produced_str.get(int(v_src_i)):
|
||||
mark_str = True
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
if mark_str and hasattr(builder.resolver, "mark_string"):
|
||||
builder.resolver.mark_string(int(dst0))
|
||||
except Exception:
|
||||
pass
|
||||
# Definition hint: PHI defines dst in this block
|
||||
try:
|
||||
builder.def_blocks.setdefault(int(dst0), set()).add(int(bid0))
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
builder.resolver.block_phi_incomings = builder.block_phi_incomings
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
143
src/llvm_py/phi_wiring/wiring.py
Normal file
143
src/llvm_py/phi_wiring/wiring.py
Normal file
@ -0,0 +1,143 @@
|
||||
from __future__ import annotations
|
||||
from typing import Dict, List, Any, Optional, Tuple
|
||||
|
||||
import llvmlite.ir as ir
|
||||
|
||||
from .common import trace
|
||||
|
||||
|
||||
def ensure_phi(builder, block_id: int, dst_vid: int, bb: ir.Block) -> ir.Instruction:
|
||||
"""Ensure a PHI placeholder exists at the block head for dst_vid and return it."""
|
||||
b = ir.IRBuilder(bb)
|
||||
try:
|
||||
b.position_at_start(bb)
|
||||
except Exception:
|
||||
pass
|
||||
predecl = getattr(builder, "predeclared_ret_phis", {}) if hasattr(builder, "predeclared_ret_phis") else {}
|
||||
phi = predecl.get((int(block_id), int(dst_vid))) if predecl else None
|
||||
if phi is not None:
|
||||
builder.vmap[dst_vid] = phi
|
||||
trace({"phi": "ensure_predecl", "block": int(block_id), "dst": int(dst_vid)})
|
||||
return phi
|
||||
cur = builder.vmap.get(dst_vid)
|
||||
try:
|
||||
if cur is not None and hasattr(cur, "add_incoming") and getattr(getattr(cur, "basic_block", None), "name", None) == bb.name:
|
||||
return cur
|
||||
except Exception:
|
||||
pass
|
||||
ph = b.phi(builder.i64, name=f"phi_{dst_vid}")
|
||||
builder.vmap[dst_vid] = ph
|
||||
trace({"phi": "ensure_create", "block": int(block_id), "dst": int(dst_vid)})
|
||||
return ph
|
||||
|
||||
|
||||
def phi_at_block_head(block: ir.Block, ty: ir.Type, name: str | None = None) -> ir.Instruction:
|
||||
"""Create a PHI at the very start of `block` and return it.
|
||||
Keeps LLVM's requirement that PHI nodes are grouped at the top of a block.
|
||||
"""
|
||||
b = ir.IRBuilder(block)
|
||||
try:
|
||||
b.position_at_start(block)
|
||||
except Exception:
|
||||
pass
|
||||
return b.phi(ty, name=name) if name is not None else b.phi(ty)
|
||||
|
||||
|
||||
def build_succs(preds: Dict[int, List[int]]) -> Dict[int, List[int]]:
|
||||
succs: Dict[int, List[int]] = {}
|
||||
for to_bid, from_list in (preds or {}).items():
|
||||
for fr in from_list:
|
||||
succs.setdefault(fr, []).append(to_bid)
|
||||
return succs
|
||||
|
||||
|
||||
def nearest_pred_on_path(
|
||||
succs: Dict[int, List[int]], preds_list: List[int], decl_b: int, target_bid: int
|
||||
) -> Optional[int]:
|
||||
from collections import deque
|
||||
|
||||
q = deque([decl_b])
|
||||
visited = set([decl_b])
|
||||
parent: Dict[int, Any] = {decl_b: None}
|
||||
while q:
|
||||
cur = q.popleft()
|
||||
if cur == target_bid:
|
||||
par = parent.get(target_bid)
|
||||
return par if par in preds_list else None
|
||||
for nx in succs.get(cur, []):
|
||||
if nx not in visited:
|
||||
visited.add(nx)
|
||||
parent[nx] = cur
|
||||
q.append(nx)
|
||||
return None
|
||||
|
||||
|
||||
def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[int, int]]):
|
||||
"""Wire PHI incoming edges for (block_id, dst_vid) using declared (decl_b, v_src) pairs."""
|
||||
bb = builder.bb_map.get(block_id)
|
||||
if bb is None:
|
||||
return
|
||||
phi = ensure_phi(builder, block_id, dst_vid, bb)
|
||||
preds_raw = [p for p in builder.preds.get(block_id, []) if p != block_id]
|
||||
seen = set()
|
||||
preds_list: List[int] = []
|
||||
for p in preds_raw:
|
||||
if p not in seen:
|
||||
preds_list.append(p)
|
||||
seen.add(p)
|
||||
succs = build_succs(builder.preds)
|
||||
init_src_vid = None
|
||||
for (_bd0, vs0) in incoming:
|
||||
try:
|
||||
vi = int(vs0)
|
||||
except Exception:
|
||||
continue
|
||||
if vi != int(dst_vid):
|
||||
init_src_vid = vi
|
||||
break
|
||||
chosen: Dict[int, ir.Value] = {}
|
||||
for (b_decl, v_src) in incoming:
|
||||
try:
|
||||
bd = int(b_decl)
|
||||
vs = int(v_src)
|
||||
except Exception:
|
||||
continue
|
||||
pred_match = nearest_pred_on_path(succs, preds_list, bd, block_id)
|
||||
if pred_match is None:
|
||||
trace({"phi": "wire_skip_no_path", "decl_b": bd, "target": int(block_id), "src": vs})
|
||||
continue
|
||||
if vs == int(dst_vid) and init_src_vid is not None:
|
||||
vs = int(init_src_vid)
|
||||
try:
|
||||
val = builder.resolver._value_at_end_i64(
|
||||
vs, pred_match, builder.preds, builder.block_end_values, builder.vmap, builder.bb_map
|
||||
)
|
||||
except Exception:
|
||||
val = None
|
||||
if val is None:
|
||||
val = ir.Constant(builder.i64, 0)
|
||||
chosen[pred_match] = val
|
||||
trace({"phi": "wire_choose", "pred": int(pred_match), "dst": int(dst_vid), "src": int(vs)})
|
||||
wired = 0
|
||||
for pred_bid, val in chosen.items():
|
||||
pred_bb = builder.bb_map.get(pred_bid)
|
||||
if pred_bb is None:
|
||||
continue
|
||||
phi.add_incoming(val, pred_bb)
|
||||
trace({"phi": "add_incoming", "dst": int(dst_vid), "pred": int(pred_bid)})
|
||||
wired += 1
|
||||
return wired
|
||||
|
||||
|
||||
def finalize_phis(builder):
|
||||
total_blocks = 0
|
||||
total_dsts = 0
|
||||
total_wired = 0
|
||||
for block_id, dst_map in (getattr(builder, "block_phi_incomings", {}) or {}).items():
|
||||
total_blocks += 1
|
||||
for dst_vid, incoming in (dst_map or {}).items():
|
||||
total_dsts += 1
|
||||
wired = wire_incomings(builder, int(block_id), int(dst_vid), incoming)
|
||||
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_summary", "blocks": int(total_blocks), "dsts": int(total_dsts), "incoming_wired": int(total_wired)})
|
||||
@ -28,8 +28,20 @@ def plan_ret_phi_predeclare(block_by_id: Dict[int, Dict[str, Any]]) -> Optional[
|
||||
val = term.get('value')
|
||||
if not isinstance(val, int):
|
||||
continue
|
||||
# Heuristic: skip when the return value is freshly defined in this block
|
||||
# (e.g., returning a const computed in cleanup). Predeclaring a PHI for such
|
||||
# values is unnecessary and may violate PHI grouping/order.
|
||||
try:
|
||||
defined_here = False
|
||||
for ins in blk.get('instructions') or []:
|
||||
if isinstance(ins, dict) and ins.get('dst') == int(val):
|
||||
defined_here = True
|
||||
break
|
||||
if defined_here:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
pred_list = [p for p in preds.get(int(bid), []) if p != int(bid)]
|
||||
if len(pred_list) > 1:
|
||||
plan[int(bid)] = int(val)
|
||||
return plan or None
|
||||
|
||||
|
||||
@ -222,37 +222,8 @@ class Resolver:
|
||||
placeholder = vmap.get(value_id)
|
||||
result = placeholder if (placeholder is not None and hasattr(placeholder, 'add_incoming')) else ir.Constant(self.i64, 0)
|
||||
else:
|
||||
# Synthesize a PHI to localize the value and dominate its uses.
|
||||
try:
|
||||
bb = bb_map.get(cur_bid) if isinstance(bb_map, dict) else current_block
|
||||
except Exception:
|
||||
bb = current_block
|
||||
b = ir.IRBuilder(bb)
|
||||
try:
|
||||
b.position_at_start(bb)
|
||||
except Exception:
|
||||
pass
|
||||
existing = vmap.get(value_id)
|
||||
if existing is not None and hasattr(existing, 'add_incoming'):
|
||||
phi = existing
|
||||
else:
|
||||
phi = b.phi(self.i64, name=f"res_phi_{value_id}_{cur_bid}")
|
||||
vmap[value_id] = phi
|
||||
# Wire end-of-block values from each predecessor
|
||||
for pred_bid in pred_ids:
|
||||
try:
|
||||
pred_bb = bb_map.get(pred_bid) if isinstance(bb_map, dict) else None
|
||||
except Exception:
|
||||
pred_bb = None
|
||||
val = self._value_at_end_i64(value_id, pred_bid, preds, block_end_values, vmap, bb_map)
|
||||
if pred_bb is None:
|
||||
# If we cannot map to a real basic block (shouldn't happen),
|
||||
# fallback to a zero to keep IR consistent.
|
||||
val = val if hasattr(val, 'type') else ir.Constant(self.i64, 0)
|
||||
# llvmlite requires a real BasicBlock; skip if missing.
|
||||
continue
|
||||
phi.add_incoming(val, pred_bb)
|
||||
result = phi
|
||||
# No declared PHI and multi-pred: do not synthesize; fallback to zero
|
||||
result = ir.Constant(self.i64, 0)
|
||||
|
||||
# Cache and return
|
||||
self.i64_cache[cache_key] = result
|
||||
|
||||
80
src/llvm_py/tests/test_phi_tagging.py
Normal file
80
src/llvm_py/tests/test_phi_tagging.py
Normal file
@ -0,0 +1,80 @@
|
||||
import unittest
|
||||
import llvmlite.ir as ir
|
||||
|
||||
from src.llvm_py.phi_wiring import setup_phi_placeholders
|
||||
|
||||
|
||||
class DummyResolver:
|
||||
def __init__(self):
|
||||
self.marked = set()
|
||||
|
||||
def mark_string(self, vid: int):
|
||||
self.marked.add(int(vid))
|
||||
|
||||
|
||||
class DummyBuilder:
|
||||
def __init__(self, bb_map):
|
||||
self.i64 = ir.IntType(64)
|
||||
self.vmap = {}
|
||||
self.def_blocks = {}
|
||||
self.resolver = DummyResolver()
|
||||
self.bb_map = bb_map
|
||||
|
||||
|
||||
class TestPhiTagging(unittest.TestCase):
|
||||
def _mk_blocks_and_bbs(self):
|
||||
mod = ir.Module(name="m")
|
||||
fnty = ir.FunctionType(ir.VoidType(), [])
|
||||
fn = ir.Function(mod, fnty, name="f")
|
||||
b0 = fn.append_basic_block(name="b0")
|
||||
b1 = fn.append_basic_block(name="b1")
|
||||
return mod, {0: b0, 1: b1}
|
||||
|
||||
def test_mark_by_dst_type(self):
|
||||
_mod, bb_map = self._mk_blocks_and_bbs()
|
||||
builder = DummyBuilder(bb_map)
|
||||
blocks = [
|
||||
{
|
||||
"id": 1,
|
||||
"instructions": [
|
||||
{
|
||||
"op": "phi",
|
||||
"dst": 42,
|
||||
"dst_type": {"kind": "handle", "box_type": "StringBox"},
|
||||
"incoming": [[7, 0]],
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
setup_phi_placeholders(builder, blocks)
|
||||
self.assertIn(42, builder.resolver.marked)
|
||||
|
||||
def test_mark_by_incoming_stringish(self):
|
||||
_mod, bb_map = self._mk_blocks_and_bbs()
|
||||
builder = DummyBuilder(bb_map)
|
||||
blocks = [
|
||||
{
|
||||
"id": 0,
|
||||
"instructions": [
|
||||
{"op": "const", "dst": 7, "value": {"type": "string", "value": "hi"}}
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"instructions": [
|
||||
{
|
||||
"op": "phi",
|
||||
"dst": 43,
|
||||
# no dst_type string; inference should happen via incoming
|
||||
"incoming": [[7, 0]],
|
||||
}
|
||||
],
|
||||
},
|
||||
]
|
||||
setup_phi_placeholders(builder, blocks)
|
||||
self.assertIn(43, builder.resolver.marked)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@ -14,28 +14,40 @@ Import and use:
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
_TRACE_OUT = os.environ.get('NYASH_LLVM_TRACE_OUT')
|
||||
|
||||
def _write(msg: str) -> None:
|
||||
if _TRACE_OUT:
|
||||
try:
|
||||
with open(_TRACE_OUT, 'a', encoding='utf-8') as f:
|
||||
f.write(msg.rstrip() + "\n")
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
print(msg, flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _enabled(env_key: str) -> bool:
|
||||
return os.environ.get(env_key) == '1'
|
||||
|
||||
def debug(msg: str) -> None:
|
||||
if _enabled('NYASH_CLI_VERBOSE'):
|
||||
try:
|
||||
print(msg, flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
_write(msg)
|
||||
|
||||
def phi(msg: str) -> None:
|
||||
def phi(msg) -> None:
|
||||
if _enabled('NYASH_LLVM_TRACE_PHI'):
|
||||
try:
|
||||
print(msg, flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
# Accept raw strings or arbitrary objects; non-strings are JSON-encoded
|
||||
if not isinstance(msg, (str, bytes)):
|
||||
try:
|
||||
msg = json.dumps(msg, ensure_ascii=False, separators=(",", ":"))
|
||||
except Exception:
|
||||
msg = str(msg)
|
||||
_write(msg)
|
||||
|
||||
def values(msg: str) -> None:
|
||||
if _enabled('NYASH_LLVM_TRACE_VALUES'):
|
||||
try:
|
||||
print(msg, flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
_write(msg)
|
||||
|
||||
Reference in New Issue
Block a user