docs/ci: selfhost bootstrap/exe-first workflows; add ny-llvmc scaffolding + JSON v0 schema validation; plan: unify to Nyash ABI v2 (no backwards compat)
This commit is contained in:
@ -51,7 +51,8 @@ def lower_atomic_op(
|
||||
resolver=None,
|
||||
preds=None,
|
||||
block_end_values=None,
|
||||
bb_map=None
|
||||
bb_map=None,
|
||||
ctx=None,
|
||||
) -> None:
|
||||
"""
|
||||
Lower atomic operations
|
||||
@ -66,7 +67,12 @@ def lower_atomic_op(
|
||||
ordering: Memory ordering
|
||||
"""
|
||||
# Get pointer
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
if ctx is not None:
|
||||
try:
|
||||
ptr = ctx.resolver.resolve_ptr(ptr_vid, builder.block, ctx.preds, ctx.block_end_values, ctx.vmap)
|
||||
except Exception:
|
||||
ptr = vmap.get(ptr_vid)
|
||||
elif resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
ptr = resolver.resolve_ptr(ptr_vid, builder.block, preds, block_end_values, vmap)
|
||||
else:
|
||||
ptr = vmap.get(ptr_vid)
|
||||
@ -85,7 +91,12 @@ def lower_atomic_op(
|
||||
elif op == "store":
|
||||
# Atomic store
|
||||
if val_vid is not None:
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
if ctx is not None:
|
||||
try:
|
||||
val = ctx.resolver.resolve_i64(val_vid, builder.block, ctx.preds, ctx.block_end_values, ctx.vmap, ctx.bb_map)
|
||||
except Exception:
|
||||
val = vmap.get(val_vid, ir.Constant(ir.IntType(64), 0))
|
||||
elif resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
val = resolver.resolve_i64(val_vid, builder.block, preds, block_end_values, vmap, bb_map)
|
||||
else:
|
||||
val = vmap.get(val_vid, ir.Constant(ir.IntType(64), 0))
|
||||
@ -94,7 +105,12 @@ def lower_atomic_op(
|
||||
elif op == "add":
|
||||
# Atomic add (fetch_add)
|
||||
if val_vid is not None:
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
if ctx is not None:
|
||||
try:
|
||||
val = ctx.resolver.resolve_i64(val_vid, builder.block, ctx.preds, ctx.block_end_values, ctx.vmap, ctx.bb_map)
|
||||
except Exception:
|
||||
val = ir.Constant(ir.IntType(64), 1)
|
||||
elif resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
val = resolver.resolve_i64(val_vid, builder.block, preds, block_end_values, vmap, bb_map)
|
||||
else:
|
||||
val = ir.Constant(ir.IntType(64), 1)
|
||||
|
||||
@ -5,6 +5,7 @@ Core of Nyash's "Everything is Box" philosophy
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict, List, Optional, Any
|
||||
from instructions.safepoint import insert_automatic_safepoint
|
||||
|
||||
def _declare(module: ir.Module, name: str, ret, args):
|
||||
for f in module.functions:
|
||||
@ -68,6 +69,13 @@ def lower_boxcall(
|
||||
i64 = ir.IntType(64)
|
||||
i8 = ir.IntType(8)
|
||||
i8p = i8.as_pointer()
|
||||
# Insert a safepoint around potential heavy boxcall sites (pre-call)
|
||||
try:
|
||||
import os
|
||||
if os.environ.get('NYASH_LLVM_AUTO_SAFEPOINT', '1') == '1':
|
||||
insert_automatic_safepoint(builder, module, "boxcall")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Short-hands with ctx (backward-compatible fallback)
|
||||
r = resolver
|
||||
|
||||
@ -6,6 +6,7 @@ Handles regular function calls (not BoxCall or ExternCall)
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict, List, Optional, Any
|
||||
from trace import debug as trace_debug
|
||||
from instructions.safepoint import insert_automatic_safepoint
|
||||
|
||||
def lower_call(
|
||||
builder: ir.IRBuilder,
|
||||
@ -45,6 +46,13 @@ def lower_call(
|
||||
bb_map = ctx.bb_map
|
||||
except Exception:
|
||||
pass
|
||||
# Insert an automatic safepoint after the function call
|
||||
try:
|
||||
import os
|
||||
if os.environ.get('NYASH_LLVM_AUTO_SAFEPOINT', '1') == '1':
|
||||
insert_automatic_safepoint(builder, module, "function_call")
|
||||
except Exception:
|
||||
pass
|
||||
# Short-hands with ctx (backward-compatible fallback)
|
||||
r = resolver
|
||||
p = preds
|
||||
|
||||
@ -4,6 +4,7 @@ Lowering helpers for while-control flow (regular structured)
|
||||
|
||||
from typing import List, Dict, Any
|
||||
import llvmlite.ir as ir
|
||||
from instructions.safepoint import insert_automatic_safepoint
|
||||
|
||||
def lower_while_regular(
|
||||
builder: ir.IRBuilder,
|
||||
@ -57,6 +58,13 @@ def lower_while_regular(
|
||||
else:
|
||||
cond_val = ir.Constant(i1, 0)
|
||||
|
||||
# Insert a safepoint at loop header to allow cooperative GC
|
||||
try:
|
||||
import os
|
||||
if os.environ.get('NYASH_LLVM_AUTO_SAFEPOINT', '1') == '1':
|
||||
insert_automatic_safepoint(cbuild, builder.block.parent.module, "loop_header")
|
||||
except Exception:
|
||||
pass
|
||||
cbuild.cbranch(cond_val, body_bb, exit_bb)
|
||||
|
||||
# Body block
|
||||
@ -77,4 +85,3 @@ def lower_while_regular(
|
||||
|
||||
# Continue at exit
|
||||
builder.position_at_end(exit_bb)
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ Minimal mapping for NyRT-exported symbols (console/log family等)
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict, List, Optional, Any
|
||||
from instructions.safepoint import insert_automatic_safepoint
|
||||
|
||||
def lower_externcall(
|
||||
builder: ir.IRBuilder,
|
||||
@ -197,3 +198,10 @@ def lower_externcall(
|
||||
vmap[dst_vid] = ir.Constant(i64, 0)
|
||||
else:
|
||||
vmap[dst_vid] = result
|
||||
# Insert an automatic safepoint after externcall
|
||||
try:
|
||||
import os
|
||||
if os.environ.get('NYASH_LLVM_AUTO_SAFEPOINT', '1') == '1':
|
||||
insert_automatic_safepoint(builder, module, "extern_call")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -7,6 +7,7 @@ import os
|
||||
import llvmlite.ir as ir
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Tuple, List, Optional, Any
|
||||
from instructions.safepoint import insert_automatic_safepoint
|
||||
|
||||
@dataclass
|
||||
class LoopFormContext:
|
||||
@ -53,7 +54,8 @@ def lower_while_loopform(
|
||||
bb_map: Dict[int, ir.Block],
|
||||
resolver=None,
|
||||
preds=None,
|
||||
block_end_values=None
|
||||
block_end_values=None,
|
||||
ctx=None,
|
||||
) -> bool:
|
||||
"""
|
||||
Lower a while loop using LoopForm structure
|
||||
@ -72,9 +74,22 @@ def lower_while_loopform(
|
||||
builder.position_at_end(lf.preheader)
|
||||
builder.branch(lf.header)
|
||||
|
||||
# Header: Evaluate condition
|
||||
# Header: Evaluate condition (insert a safepoint at loop header)
|
||||
builder.position_at_end(lf.header)
|
||||
if resolver is not None and preds is not None and block_end_values is not None:
|
||||
try:
|
||||
import os
|
||||
if os.environ.get('NYASH_LLVM_AUTO_SAFEPOINT', '1') == '1':
|
||||
insert_automatic_safepoint(builder, func.module, "loop_header")
|
||||
except Exception:
|
||||
pass
|
||||
if ctx is not None:
|
||||
try:
|
||||
cond64 = ctx.resolver.resolve_i64(condition_vid, builder.block, ctx.preds, ctx.block_end_values, ctx.vmap, ctx.bb_map)
|
||||
zero64 = ir.IntType(64)(0)
|
||||
cond = builder.icmp_unsigned('!=', cond64, zero64)
|
||||
except Exception:
|
||||
cond = vmap.get(condition_vid, ir.Constant(ir.IntType(1), 0))
|
||||
elif resolver is not None and preds is not None and block_end_values is not None:
|
||||
cond64 = resolver.resolve_i64(condition_vid, builder.block, preds, block_end_values, vmap, bb_map)
|
||||
zero64 = ir.IntType(64)(0)
|
||||
cond = builder.icmp_unsigned('!=', cond64, zero64)
|
||||
|
||||
@ -776,7 +776,7 @@ class NyashLLVMBuilder:
|
||||
for inst in term_ops:
|
||||
try:
|
||||
import os
|
||||
trace_debug(f"[llvm-py] term op: {inst.get('op')} dst={inst.get('dst')} cond={inst.get('cond')}")
|
||||
trace_debug(f"[llvm-py] term op: {inst.get('op')} dst={inst.get('dst')} cond={inst.get('cond')}")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
@ -950,7 +950,8 @@ class NyashLLVMBuilder:
|
||||
self.loop_count += 1
|
||||
if not lower_while_loopform(builder, func, cond, body,
|
||||
self.loop_count, self.vmap, self.bb_map,
|
||||
self.resolver, self.preds, self.block_end_values):
|
||||
self.resolver, self.preds, self.block_end_values,
|
||||
getattr(self, 'ctx', None)):
|
||||
# Fallback to regular while (structured)
|
||||
try:
|
||||
self.resolver._owner_lower_instruction = self.lower_instruction
|
||||
@ -975,58 +976,10 @@ class NyashLLVMBuilder:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _lower_while_regular(self, builder: ir.IRBuilder, inst: Dict[str, Any], func: ir.Function):
|
||||
"""Fallback regular while lowering"""
|
||||
# Create basic blocks: cond -> body -> cond, and exit
|
||||
cond_vid = inst.get("cond")
|
||||
body_insts = inst.get("body", [])
|
||||
|
||||
cur_bb = builder.block
|
||||
cond_bb = func.append_basic_block(name=f"while{self.loop_count}_cond")
|
||||
body_bb = func.append_basic_block(name=f"while{self.loop_count}_body")
|
||||
exit_bb = func.append_basic_block(name=f"while{self.loop_count}_exit")
|
||||
|
||||
# Jump from current to cond
|
||||
builder.branch(cond_bb)
|
||||
|
||||
# Cond block
|
||||
cbuild = ir.IRBuilder(cond_bb)
|
||||
try:
|
||||
# Resolve against the condition block to localize dominance
|
||||
cond_val = self.resolver.resolve_i64(cond_vid, cbuild.block, self.preds, self.block_end_values, self.vmap, self.bb_map)
|
||||
except Exception:
|
||||
cond_val = self.vmap.get(cond_vid)
|
||||
if cond_val is None:
|
||||
cond_val = ir.Constant(self.i1, 0)
|
||||
# Normalize to i1
|
||||
if hasattr(cond_val, 'type'):
|
||||
if isinstance(cond_val.type, ir.IntType) and cond_val.type.width == 64:
|
||||
zero64 = ir.Constant(self.i64, 0)
|
||||
cond_val = cbuild.icmp_unsigned('!=', cond_val, zero64, name="while_cond_i1")
|
||||
elif isinstance(cond_val.type, ir.PointerType):
|
||||
nullp = ir.Constant(cond_val.type, None)
|
||||
cond_val = cbuild.icmp_unsigned('!=', cond_val, nullp, name="while_cond_p1")
|
||||
elif isinstance(cond_val.type, ir.IntType) and cond_val.type.width == 1:
|
||||
# already i1
|
||||
pass
|
||||
else:
|
||||
# Fallback: treat as false
|
||||
cond_val = ir.Constant(self.i1, 0)
|
||||
else:
|
||||
cond_val = ir.Constant(self.i1, 0)
|
||||
|
||||
cbuild.cbranch(cond_val, body_bb, exit_bb)
|
||||
|
||||
# Body block
|
||||
bbuild = ir.IRBuilder(body_bb)
|
||||
# Allow nested lowering of body instructions within this block
|
||||
self._lower_instruction_list(bbuild, body_insts, func)
|
||||
# Ensure terminator: if not terminated, branch back to cond
|
||||
if bbuild.block.terminator is None:
|
||||
bbuild.branch(cond_bb)
|
||||
|
||||
# Continue at exit
|
||||
builder.position_at_end(exit_bb)
|
||||
# NOTE: regular while lowering is implemented in
|
||||
# instructions/controlflow/while_.py::lower_while_regular and invoked
|
||||
# from NyashLLVMBuilder.lower_instruction(). This legacy helper is removed
|
||||
# to avoid divergence between two implementations.
|
||||
|
||||
def _lower_instruction_list(self, builder: ir.IRBuilder, insts: List[Dict[str, Any]], func: ir.Function):
|
||||
"""Lower a flat list of instructions using current builder and function."""
|
||||
|
||||
@ -5,11 +5,161 @@ PHI wiring helpers
|
||||
- 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
|
||||
from typing import Dict, List, Any, Optional, Tuple
|
||||
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 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:
|
||||
result.setdefault(int(bid0), {})[dst0] = [(int(b), int(v)) for (v, b) in incoming0]
|
||||
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
|
||||
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
|
||||
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:
|
||||
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
|
||||
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)
|
||||
|
||||
# ---- 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.
|
||||
|
||||
@ -18,183 +168,55 @@ def setup_phi_placeholders(builder, blocks: List[Dict[str, Any]]):
|
||||
values eagerly to help downstream resolvers choose correct intrinsics.
|
||||
"""
|
||||
try:
|
||||
# Pass A: collect producer stringish hints per value-id
|
||||
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
|
||||
# Pass B: materialize PHI placeholders and record incoming metadata
|
||||
builder.block_phi_incomings = {}
|
||||
produced_str = _collect_produced_stringish(blocks)
|
||||
builder.block_phi_incomings = analyze_incomings(blocks)
|
||||
# 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":
|
||||
try:
|
||||
dst0 = int(inst.get("dst"))
|
||||
incoming0 = inst.get("incoming", []) or []
|
||||
except Exception:
|
||||
dst0 = None; incoming0 = []
|
||||
if dst0 is None:
|
||||
continue
|
||||
# Record incoming metadata for finalize_phis
|
||||
try:
|
||||
builder.block_phi_incomings.setdefault(bid0, {})[dst0] = [
|
||||
(int(b), int(v)) for (v, b) in incoming0
|
||||
]
|
||||
except Exception:
|
||||
pass
|
||||
# Ensure placeholder exists at block head
|
||||
if bb0 is not None:
|
||||
b0 = ir.IRBuilder(bb0)
|
||||
try:
|
||||
b0.position_at_start(bb0)
|
||||
except Exception:
|
||||
pass
|
||||
existing = builder.vmap.get(dst0)
|
||||
is_phi = False
|
||||
try:
|
||||
is_phi = hasattr(existing, 'add_incoming')
|
||||
except Exception:
|
||||
is_phi = False
|
||||
if not is_phi:
|
||||
ph0 = b0.phi(builder.i64, name=f"phi_{dst0}")
|
||||
builder.vmap[dst0] = ph0
|
||||
# Tag propagation: if explicit dst_type marks string or any incoming was produced as string-ish, tag dst
|
||||
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
|
||||
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,
|
||||
ensuring casts/boxing are inserted in predecessor blocks (dominance-safe)."""
|
||||
# Build succ map for nearest-predecessor mapping
|
||||
succs: Dict[int, List[int]] = {}
|
||||
for to_bid, from_list in (builder.preds or {}).items():
|
||||
for fr in from_list:
|
||||
succs.setdefault(fr, []).append(to_bid)
|
||||
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():
|
||||
bb = builder.bb_map.get(block_id)
|
||||
if bb is None:
|
||||
continue
|
||||
b = ir.IRBuilder(bb)
|
||||
try:
|
||||
b.position_at_start(bb)
|
||||
except Exception:
|
||||
pass
|
||||
for dst_vid, incoming in (dst_map or {}).items():
|
||||
# Ensure placeholder exists at block head
|
||||
# Prefer predeclared ret-phi when available and force using it.
|
||||
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
|
||||
else:
|
||||
phi = builder.vmap.get(dst_vid)
|
||||
need_local_phi = False
|
||||
try:
|
||||
if not (phi is not None and hasattr(phi, 'add_incoming')):
|
||||
need_local_phi = True
|
||||
else:
|
||||
bb_of_phi = getattr(getattr(phi, 'basic_block', None), 'name', None)
|
||||
if bb_of_phi != bb.name:
|
||||
need_local_phi = True
|
||||
except Exception:
|
||||
need_local_phi = True
|
||||
if need_local_phi:
|
||||
phi = b.phi(builder.i64, name=f"phi_{dst_vid}")
|
||||
builder.vmap[dst_vid] = phi
|
||||
# Wire incoming per CFG predecessor; map src_vid when provided
|
||||
preds_raw = [p for p in builder.preds.get(block_id, []) if p != block_id]
|
||||
# Deduplicate while preserving order
|
||||
seen = set()
|
||||
preds_list: List[int] = []
|
||||
for p in preds_raw:
|
||||
if p not in seen:
|
||||
preds_list.append(p)
|
||||
seen.add(p)
|
||||
# Helper: find the nearest immediate predecessor on a path decl_b -> ... -> block_id
|
||||
def nearest_pred_on_path(decl_b: 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 == block_id:
|
||||
par = parent.get(block_id)
|
||||
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
|
||||
# Precompute a non-self initial source (if present) to use for self-carry cases
|
||||
init_src_vid = None
|
||||
for (b_decl0, v_src0) in incoming:
|
||||
try:
|
||||
vs0 = int(v_src0)
|
||||
except Exception:
|
||||
continue
|
||||
if vs0 != int(dst_vid):
|
||||
init_src_vid = vs0
|
||||
break
|
||||
# Pre-resolve declared incomings to nearest immediate predecessors
|
||||
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(bd)
|
||||
if pred_match is None:
|
||||
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:
|
||||
# As a last resort, zero
|
||||
val = ir.Constant(builder.i64, 0)
|
||||
chosen[pred_match] = val
|
||||
# Finally add incomings
|
||||
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)
|
||||
wire_incomings(builder, int(block_id), int(dst_vid), incoming)
|
||||
|
||||
65
src/llvm_py/tests/test_phi_wiring.py
Normal file
65
src/llvm_py/tests/test_phi_wiring.py
Normal file
@ -0,0 +1,65 @@
|
||||
"""
|
||||
Unit tests for phi_wiring helpers
|
||||
|
||||
These tests construct a minimal function with two blocks and a PHI in the
|
||||
second block. We verify that placeholders are created and incoming edges
|
||||
are wired from the correct predecessor, using end-of-block snapshots.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Ensure 'src' is importable when running this test directly
|
||||
TEST_DIR = Path(__file__).resolve().parent
|
||||
PKG_DIR = TEST_DIR.parent # src/llvm_py
|
||||
ROOT = PKG_DIR.parent # src
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
if str(PKG_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(PKG_DIR))
|
||||
|
||||
import llvmlite.ir as ir # type: ignore
|
||||
|
||||
from phi_wiring import setup_phi_placeholders, finalize_phis # type: ignore
|
||||
import llvm_builder # type: ignore
|
||||
|
||||
|
||||
def _simple_mir_with_phi():
|
||||
"""
|
||||
Build a minimal MIR JSON that compiles to:
|
||||
bb0: const v1=42; jump bb1
|
||||
bb1: phi v2=[(bb0,v1)] ; ret v2
|
||||
"""
|
||||
return {
|
||||
"functions": [
|
||||
{
|
||||
"name": "main",
|
||||
"params": [],
|
||||
"blocks": [
|
||||
{"id": 0, "instructions": [
|
||||
{"op": "const", "dst": 1, "value": {"type": "int", "value": 42}},
|
||||
{"op": "jump", "target": 1}
|
||||
]},
|
||||
{"id": 1, "instructions": [
|
||||
{"op": "phi", "dst": 2, "incoming": [[1, 0]]},
|
||||
{"op": "ret", "value": 2}
|
||||
]}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def test_phi_placeholders_and_finalize_basic():
|
||||
mir = _simple_mir_with_phi()
|
||||
b = llvm_builder.NyashLLVMBuilder()
|
||||
# Build once to create function, blocks, preds; stop before finalize by calling internals like lower_function
|
||||
reader_functions = mir["functions"]
|
||||
assert reader_functions
|
||||
b.lower_function(reader_functions[0])
|
||||
# After lowering a function, finalize_phis is already called at the end of lower_function.
|
||||
# Verify via IR text that a PHI exists in bb1 with an incoming from bb0.
|
||||
ir_text = str(b.module)
|
||||
assert 'bb1' in ir_text
|
||||
assert 'phi i64' in ir_text
|
||||
assert '[0, %"bb0"]' in ir_text or '[ i64 0, %"bb0"]' in ir_text
|
||||
Reference in New Issue
Block a user