llvm(py): introduce BuildCtx + trace hub; refactor if-merge prepass + PHI wiring into module; unify logs; ctx-enable compare/ret/call/boxcall/externcall/typeop/newbox/safepoint; curated smoke option for if-merge; README updates; keep behavior stable

This commit is contained in:
Selfhosting Dev
2025-09-17 16:11:01 +09:00
parent 2720884a20
commit a5054a271b
58 changed files with 2002 additions and 311 deletions

View File

@ -5,6 +5,8 @@ Based on src/backend/llvm/compiler/codegen/instructions/resolver.rs
from typing import Dict, Optional, Any, Tuple
import os
from trace import phi as trace_phi
from trace import values as trace_values
import llvmlite.ir as ir
class Resolver:
@ -26,6 +28,13 @@ class Resolver:
# Legacy constructor (vmap, bb_map) — builder/module will be set later when available
self.builder = None
self.module = None
try:
# Keep references to global maps when provided
self.global_vmap = a if isinstance(a, dict) else None
self.global_bb_map = b if isinstance(b, dict) else None
except Exception:
self.global_vmap = None
self.global_bb_map = None
# Caches: (block_name, value_id) -> llvm value
self.i64_cache: Dict[Tuple[str, int], ir.Value] = {}
@ -95,9 +104,35 @@ class Resolver:
bmap = self.block_phi_incomings.get(block_id)
if isinstance(bmap, dict) and value_id in bmap:
existing_cur = vmap.get(value_id)
if existing_cur is not None and hasattr(existing_cur, 'add_incoming'):
# Use placeholder only if it belongs to the current block; otherwise
# create/ensure a local PHI at the current block head to dominate uses.
is_phi_here = False
try:
is_phi_here = (
existing_cur is not None
and hasattr(existing_cur, 'add_incoming')
and getattr(getattr(existing_cur, 'basic_block', None), 'name', None) == current_block.name
)
except Exception:
is_phi_here = False
if is_phi_here:
self.i64_cache[cache_key] = existing_cur
return existing_cur
# Materialize a local PHI placeholder at block start and bind to vmap
b = ir.IRBuilder(current_block)
try:
b.position_at_start(current_block)
except Exception:
pass
phi_local = b.phi(self.i64, name=f"phi_{value_id}")
vmap[value_id] = phi_local
try:
if isinstance(getattr(self, 'global_vmap', None), dict):
self.global_vmap[value_id] = phi_local
except Exception:
pass
self.i64_cache[cache_key] = phi_local
return phi_local
except Exception:
pass
@ -116,8 +151,7 @@ class Resolver:
if defined_here:
existing = vmap.get(value_id)
if existing is not None and hasattr(existing, 'type') and isinstance(existing.type, ir.IntType) and existing.type.width == 64:
if os.environ.get('NYASH_LLVM_TRACE_VALUES') == '1':
print(f"[resolve] local reuse: bb{bid} v{value_id}", flush=True)
trace_values(f"[resolve] local reuse: bb{bid} v{value_id}")
self.i64_cache[cache_key] = existing
return existing
else:
@ -131,8 +165,7 @@ class Resolver:
base_val = vmap.get(value_id)
if base_val is None:
result = ir.Constant(self.i64, 0)
if os.environ.get('NYASH_LLVM_TRACE_PHI') == '1':
print(f"[resolve] bb{bid} v{value_id} entry/no-preds → 0", flush=True)
trace_phi(f"[resolve] bb{bid} v{value_id} entry/no-preds → 0")
else:
# If pointer string, box to handle in current block (use local builder)
if hasattr(base_val, 'type') and isinstance(base_val.type, ir.PointerType) and self.module is not None:
@ -185,8 +218,7 @@ class Resolver:
declared = False
if declared:
# Return existing placeholder if present; do not create a new PHI here.
if os.environ.get('NYASH_LLVM_TRACE_PHI') == '1':
print(f"[resolve] use placeholder PHI: bb{cur_bid} v{value_id}", flush=True)
trace_phi(f"[resolve] use placeholder PHI: bb{cur_bid} v{value_id}")
placeholder = vmap.get(value_id)
result = placeholder if (placeholder is not None and hasattr(placeholder, 'add_incoming')) else ir.Constant(self.i64, 0)
else:
@ -258,19 +290,14 @@ class Resolver:
bb_map: Optional[Dict[int, ir.Block]] = None,
_vis: Optional[set] = None) -> ir.Value:
"""Resolve value as i64 at the end of a given block by traversing predecessors if needed."""
if os.environ.get('NYASH_LLVM_TRACE_PHI') == '1':
try:
print(f"[resolve] end_i64 enter: bb{block_id} v{value_id}", flush=True)
except Exception:
pass
trace_phi(f"[resolve] end_i64 enter: bb{block_id} v{value_id}")
key = (block_id, value_id)
if key in self._end_i64_cache:
return self._end_i64_cache[key]
if _vis is None:
_vis = set()
if key in _vis:
if os.environ.get('NYASH_LLVM_TRACE_PHI') == '1':
print(f"[resolve] cycle detected at end_i64(bb{block_id}, v{value_id}) → 0", flush=True)
trace_phi(f"[resolve] cycle detected at end_i64(bb{block_id}, v{value_id}) → 0")
return ir.Constant(self.i64, 0)
_vis.add(key)
@ -285,16 +312,21 @@ class Resolver:
is_phi_val = hasattr(val, 'add_incoming')
except Exception:
is_phi_val = False
if os.environ.get('NYASH_LLVM_TRACE_PHI') == '1':
try:
ty = 'phi' if is_phi_val else ('ptr' if hasattr(val, 'type') and isinstance(val.type, ir.PointerType) else ('i'+str(getattr(val.type,'width','?')) if hasattr(val,'type') and isinstance(val.type, ir.IntType) else 'other'))
print(f"[resolve] snap hit: bb{block_id} v{value_id} type={ty}", flush=True)
except Exception:
pass
try:
ty = 'phi' if is_phi_val else ('ptr' if hasattr(val, 'type') and isinstance(val.type, ir.PointerType) else ('i'+str(getattr(val.type,'width','?')) if hasattr(val,'type') and isinstance(val.type, ir.IntType) else 'other'))
trace_phi(f"[resolve] snap hit: bb{block_id} v{value_id} type={ty}")
except Exception:
pass
if is_phi_val:
# Using a dominating PHI placeholder as incoming is valid for finalize_phis
self._end_i64_cache[key] = val
return val
# Accept PHI only when it belongs to the same block (dominates end-of-block).
try:
belongs_here = (getattr(getattr(val, 'basic_block', None), 'name', b'').decode() if hasattr(getattr(val, 'basic_block', None), 'name') else str(getattr(getattr(val, 'basic_block', None), 'name', ''))) == f"bb{block_id}"
except Exception:
belongs_here = False
if belongs_here:
self._end_i64_cache[key] = val
return val
# Otherwise ignore and try predecessors to avoid self-carry from foreign PHI
coerced = self._coerce_in_block_to_i64(val, block_id, bb_map)
self._end_i64_cache[key] = coerced
return coerced
@ -310,9 +342,8 @@ class Resolver:
# Do not use global vmap here; if not materialized by end of this block
# (or its preds), bail out with zero to preserve dominance.
if os.environ.get('NYASH_LLVM_TRACE_PHI') == '1':
preds_s = ','.join(str(x) for x in pred_ids)
print(f"[resolve] end_i64 miss: bb{block_id} v{value_id} preds=[{preds_s}] → 0", flush=True)
preds_s = ','.join(str(x) for x in pred_ids)
trace_phi(f"[resolve] end_i64 miss: bb{block_id} v{value_id} preds=[{preds_s}] → 0")
z = ir.Constant(self.i64, 0)
self._end_i64_cache[key] = z
return z