feat(llvm): Phase 132 - Fix PHI instruction ordering bug
Structural fix for LLVM backend PHI placement issue discovered in Phase 131. Changes: - Modified ensure_phi() to position PHI before existing instructions - Enhanced setup_phi_placeholders() with debug mode - Created phi_placement.py utility for validation - Added test script for representative cases Technical approach: - PHI creation during setup (before block lowering) - Explicit positioning with position_before(instrs[0]) - Debug infrastructure via NYASH_PHI_ORDERING_DEBUG=1 Design principles: - PHI must be created when blocks are empty - finalize_phis only wires, never creates - llvmlite API constraints respected Phase 132 complete. Ready for Phase 133 (ConsoleBox integration). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -9,6 +9,9 @@ from .wiring import ensure_phi
|
||||
def setup_phi_placeholders(builder, blocks: List[Dict[str, Any]]):
|
||||
"""Predeclare PHIs and collect incoming metadata for finalize_phis.
|
||||
|
||||
Phase 132 Enhancement: Ensure ALL PHI instructions are created at block heads
|
||||
BEFORE any other instructions are added. This is critical for LLVM IR validity.
|
||||
|
||||
Function-local: must be invoked after basic blocks are created and before
|
||||
lowering individual blocks. Also tags string-ish values to help downstream
|
||||
resolvers.
|
||||
@ -17,9 +20,30 @@ def setup_phi_placeholders(builder, blocks: List[Dict[str, Any]]):
|
||||
produced_str = collect_produced_stringish(blocks)
|
||||
builder.block_phi_incomings = analyze_incomings(blocks)
|
||||
trace({"phi": "setup", "produced_str_keys": list(produced_str.keys())})
|
||||
|
||||
# Phase 132: Create all PHI placeholders FIRST, before any other operations
|
||||
# This ensures PHIs are at block heads when blocks are still empty
|
||||
import os
|
||||
debug_mode = os.environ.get('NYASH_PHI_ORDERING_DEBUG') == '1'
|
||||
|
||||
if debug_mode:
|
||||
print(f"[phi_wiring/setup] Processing {len(blocks)} blocks for PHI placeholders")
|
||||
|
||||
for block_data in blocks:
|
||||
bid0 = block_data.get("id", 0)
|
||||
bb0 = builder.bb_map.get(bid0)
|
||||
if bb0 is None:
|
||||
continue
|
||||
|
||||
# Count PHIs in this block for debugging
|
||||
phi_count = 0
|
||||
for inst in block_data.get("instructions", []) or []:
|
||||
if inst.get("op") == "phi":
|
||||
phi_count += 1
|
||||
|
||||
if debug_mode and phi_count > 0:
|
||||
print(f"[phi_wiring/setup] Block {bid0}: {phi_count} PHI instructions to create")
|
||||
|
||||
for inst in block_data.get("instructions", []) or []:
|
||||
if inst.get("op") != "phi":
|
||||
continue
|
||||
@ -29,8 +53,21 @@ def setup_phi_placeholders(builder, blocks: List[Dict[str, Any]]):
|
||||
except Exception:
|
||||
dst0 = None
|
||||
incoming0 = []
|
||||
if dst0 is None or bb0 is None:
|
||||
if dst0 is None:
|
||||
continue
|
||||
|
||||
# Phase 132: Verify block is still empty (no terminator)
|
||||
has_terminator = False
|
||||
try:
|
||||
if bb0.terminator is not None:
|
||||
has_terminator = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if has_terminator and debug_mode:
|
||||
print(f"[phi_wiring/setup] WARNING: Block {bid0} already has terminator "
|
||||
f"when creating PHI for v{dst0}!")
|
||||
|
||||
# Predeclare a placeholder PHI at the block head so that
|
||||
# mid-block users (e.g., compare/branch) dominate correctly
|
||||
# and refer to the same SSA node that finalize_phis() will wire.
|
||||
@ -45,10 +82,14 @@ def setup_phi_placeholders(builder, blocks: List[Dict[str, Any]]):
|
||||
builder.predeclared_ret_phis = {}
|
||||
try:
|
||||
builder.predeclared_ret_phis[(int(bid0), int(dst0))] = ph
|
||||
if debug_mode:
|
||||
print(f"[phi_wiring/setup] Created PHI placeholder for v{dst0} in bb{bid0}")
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
if debug_mode:
|
||||
print(f"[phi_wiring/setup] ERROR creating PHI for v{dst0} in bb{bid0}: {e}")
|
||||
|
||||
# Tag propagation
|
||||
try:
|
||||
dst_type0 = inst.get("dst_type")
|
||||
@ -79,5 +120,9 @@ def setup_phi_placeholders(builder, blocks: List[Dict[str, Any]]):
|
||||
builder.resolver.block_phi_incomings = builder.block_phi_incomings
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
import os
|
||||
if os.environ.get('NYASH_PHI_ORDERING_DEBUG') == '1':
|
||||
print(f"[phi_wiring/setup] FATAL ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
@ -14,19 +14,22 @@ def _const_i64(builder, n: int) -> ir.Constant:
|
||||
|
||||
|
||||
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."""
|
||||
"""Ensure a PHI placeholder exists at the block head for dst_vid and return it.
|
||||
|
||||
Phase 132 Fix: Ensure PHI instructions are ALWAYS at block head, before any
|
||||
other instructions. This is critical for LLVM IR validity.
|
||||
"""
|
||||
# Always place PHI at block start to keep LLVM invariant "PHI nodes at top"
|
||||
b = ir.IRBuilder(bb)
|
||||
try:
|
||||
b.position_at_start(bb)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Check if we already have a predeclared PHI (from setup_phi_placeholders)
|
||||
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
|
||||
|
||||
# Check if PHI already exists in vmap for this block
|
||||
cur = builder.vmap.get(dst_vid)
|
||||
try:
|
||||
if cur is not None and hasattr(cur, "add_incoming"):
|
||||
@ -46,9 +49,43 @@ def ensure_phi(builder, block_id: int, dst_vid: int, bb: ir.Block) -> ir.Instruc
|
||||
return cur
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Phase 132 Critical Fix: Check if block already has a terminator
|
||||
# If so, we're trying to add PHI too late - this is a bug
|
||||
block_has_terminator = False
|
||||
try:
|
||||
if bb.terminator is not None:
|
||||
block_has_terminator = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if block_has_terminator:
|
||||
# This should not happen - PHIs must be created before terminators
|
||||
import os
|
||||
if os.environ.get('NYASH_PHI_ORDERING_DEBUG') == '1':
|
||||
print(f"[phi_wiring] WARNING: Attempting to create PHI in bb{block_id} "
|
||||
f"after terminator already exists! This will cause LLVM IR errors.")
|
||||
# Try to create PHI anyway at the start, but log the issue
|
||||
|
||||
# Create PHI at block start
|
||||
b = ir.IRBuilder(bb)
|
||||
try:
|
||||
# Phase 132: Explicitly position BEFORE the first instruction
|
||||
# This ensures PHI is at the very start
|
||||
instrs = list(bb.instructions)
|
||||
if instrs:
|
||||
b.position_before(instrs[0])
|
||||
else:
|
||||
b.position_at_start(bb)
|
||||
except Exception:
|
||||
try:
|
||||
b.position_at_start(bb)
|
||||
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)})
|
||||
trace({"phi": "ensure_create", "block": int(block_id), "dst": int(dst_vid), "after_term": block_has_terminator})
|
||||
return ph
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user