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:
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)})
|
||||
Reference in New Issue
Block a user