refactor(llvm): Phase 131-12-P2 - block_end_values SSOT 化(WIP)
## 実装内容 - get_end_values() API 追加 - _value_at_end_i64() を snapshot-only に変更 - def_blocks 即時更新 - PHI incoming を snapshot から取得 ## 発見された問題 - 同一ブロック内の def→use が predecessor snapshot を見てしまう - これは次フェーズで resolve_cur / resolve_incoming 分離で修正 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -245,6 +245,12 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An
|
|||||||
vmap_cur[dst] = _gval
|
vmap_cur[dst] = _gval
|
||||||
if dst not in created_ids and dst in vmap_cur:
|
if dst not in created_ids and dst in vmap_cur:
|
||||||
created_ids.append(dst)
|
created_ids.append(dst)
|
||||||
|
# P0-1.5: Update def_blocks IMMEDIATELY after instruction lowering
|
||||||
|
# This ensures resolver can detect defined_here for same-block uses
|
||||||
|
try:
|
||||||
|
builder.def_blocks.setdefault(dst, set()).add(block_data.get("id", 0))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
# Phase 131-4 Pass A: DEFER terminators until after PHI finalization
|
# Phase 131-4 Pass A: DEFER terminators until after PHI finalization
|
||||||
|
|||||||
@ -58,39 +58,42 @@ def lower_phi(
|
|||||||
# Fallback: use blocks in incoming list
|
# Fallback: use blocks in incoming list
|
||||||
actual_preds = [b for _, b in incoming]
|
actual_preds = [b for _, b in incoming]
|
||||||
|
|
||||||
# Collect incoming values
|
# P1: Collect incoming values from corresponding snapshots (SSOT)
|
||||||
incoming_pairs: List[Tuple[ir.Block, ir.Value]] = []
|
incoming_pairs: List[Tuple[ir.Block, ir.Value]] = []
|
||||||
used_default_zero = False
|
used_default_zero = False
|
||||||
|
|
||||||
|
import os
|
||||||
|
strict_mode = os.environ.get('NYASH_LLVM_STRICT') == '1'
|
||||||
|
|
||||||
for block_id in actual_preds:
|
for block_id in actual_preds:
|
||||||
block = bb_map.get(block_id)
|
block = bb_map.get(block_id)
|
||||||
vid = incoming_map.get(block_id)
|
vid = incoming_map.get(block_id)
|
||||||
if block is None:
|
if block is None:
|
||||||
continue
|
continue
|
||||||
# Prefer resolver-driven localization per predecessor block to satisfy dominance
|
|
||||||
if vid is not None and resolver is not None and bb_map is not None:
|
# P1: Get value from pred_bid's snapshot (SSOT - no global search)
|
||||||
try:
|
if block_end_values is not None:
|
||||||
pred_block_obj = bb_map.get(block_id)
|
pred_snapshot = block_end_values.get(block_id, {})
|
||||||
if pred_block_obj is not None and hasattr(resolver, 'resolve_i64'):
|
val = pred_snapshot.get(vid) if vid is not None else None
|
||||||
val = resolver.resolve_i64(vid, pred_block_obj, preds_map or {}, block_end_values or {}, vmap, bb_map)
|
else:
|
||||||
else:
|
val = None
|
||||||
val = None
|
|
||||||
except Exception:
|
if val is None:
|
||||||
val = None
|
# P1: STRICT mode - fail fast on snapshot miss
|
||||||
if val is None:
|
if strict_mode:
|
||||||
# Missing incoming for this predecessor → default 0
|
available_keys = sorted(list(pred_snapshot.keys())) if block_end_values is not None else []
|
||||||
val = ir.Constant(phi_type, 0)
|
raise RuntimeError(
|
||||||
used_default_zero = True
|
f"[LLVM_PY/STRICT] PHI incoming miss:\n"
|
||||||
|
f" Source ValueId: v{vid}\n"
|
||||||
|
f" Predecessor Block: bb{block_id}\n"
|
||||||
|
f" PHI destination: v{dst_vid}\n"
|
||||||
|
f" Available in snapshot: {available_keys}\n"
|
||||||
|
f" Hint: Value v{vid} should be in block_end_values[{block_id}]"
|
||||||
|
)
|
||||||
|
# Non-STRICT: fallback to 0
|
||||||
|
val = ir.Constant(phi_type, 0)
|
||||||
|
used_default_zero = True
|
||||||
else:
|
else:
|
||||||
# Snapshot fallback
|
|
||||||
if block_end_values is not None:
|
|
||||||
snap = block_end_values.get(block_id, {})
|
|
||||||
val = snap.get(vid) if vid is not None else None
|
|
||||||
else:
|
|
||||||
val = vmap.get(vid) if vid is not None else None
|
|
||||||
if not val:
|
|
||||||
# Missing incoming for this predecessor → default 0
|
|
||||||
val = ir.Constant(phi_type, 0)
|
|
||||||
used_default_zero = True
|
|
||||||
# Coerce pointer to i64 at predecessor end
|
# Coerce pointer to i64 at predecessor end
|
||||||
if hasattr(val, 'type') and val.type != phi_type:
|
if hasattr(val, 'type') and val.type != phi_type:
|
||||||
pb = ir.IRBuilder(block)
|
pb = ir.IRBuilder(block)
|
||||||
|
|||||||
@ -119,7 +119,10 @@ class NyashLLVMBuilder:
|
|||||||
|
|
||||||
# Resolver for unified value resolution
|
# Resolver for unified value resolution
|
||||||
self.resolver = Resolver(self.vmap, self.bb_map)
|
self.resolver = Resolver(self.vmap, self.bb_map)
|
||||||
|
# P0-1: Connect builder's SSOT structures to resolver
|
||||||
|
self.resolver.def_blocks = self.def_blocks
|
||||||
|
self.resolver.block_end_values = self.block_end_values
|
||||||
|
|
||||||
# Statistics
|
# Statistics
|
||||||
self.loop_count = 0
|
self.loop_count = 0
|
||||||
# Heuristics for minor gated fixes
|
# Heuristics for minor gated fixes
|
||||||
|
|||||||
@ -61,6 +61,8 @@ class Resolver:
|
|||||||
self.def_blocks = {}
|
self.def_blocks = {}
|
||||||
# Optional: block -> { dst_vid -> [(pred_bid, val_vid), ...] } for PHIs from MIR JSON
|
# Optional: block -> { dst_vid -> [(pred_bid, val_vid), ...] } for PHIs from MIR JSON
|
||||||
self.block_phi_incomings = {}
|
self.block_phi_incomings = {}
|
||||||
|
# P0-1: SSOT for end-of-block values (snapshots)
|
||||||
|
self.block_end_values = {}
|
||||||
|
|
||||||
def mark_string(self, value_id: int) -> None:
|
def mark_string(self, value_id: int) -> None:
|
||||||
try:
|
try:
|
||||||
@ -322,28 +324,34 @@ class Resolver:
|
|||||||
self.ptr_cache[cache_key] = result
|
self.ptr_cache[cache_key] = result
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def get_end_values(self, block_id: int) -> Dict[int, ir.Value]:
|
||||||
|
"""P0-1: Get end-of-block snapshot (SSOT).
|
||||||
|
|
||||||
|
Returns the snapshot of values at the end of the specified block.
|
||||||
|
This is the single source of truth for PHI incoming resolution.
|
||||||
|
"""
|
||||||
|
return self.block_end_values.get(block_id, {})
|
||||||
|
|
||||||
def _value_at_end_i64(self, value_id: int, block_id: int, preds: Dict[int, list],
|
def _value_at_end_i64(self, value_id: int, block_id: int, preds: Dict[int, list],
|
||||||
block_end_values: Dict[int, Dict[int, Any]], vmap: Dict[int, Any],
|
block_end_values: Dict[int, Dict[int, Any]], vmap: Dict[int, Any],
|
||||||
bb_map: Optional[Dict[int, ir.Block]] = None,
|
bb_map: Optional[Dict[int, ir.Block]] = None,
|
||||||
_vis: Optional[set] = None) -> ir.Value:
|
_vis: Optional[set] = None) -> ir.Value:
|
||||||
"""Resolve value as i64 at the end of a given block by traversing predecessors if needed."""
|
"""P0-2: Resolve value from block snapshot only (no global search).
|
||||||
|
|
||||||
|
This function now ONLY looks at the block_end_values snapshot for the
|
||||||
|
specified block. It does NOT recursively search predecessors. This
|
||||||
|
eliminates processing-order dependency bugs.
|
||||||
|
"""
|
||||||
trace_phi(f"[resolve] end_i64 enter: bb{block_id} v{value_id}")
|
trace_phi(f"[resolve] end_i64 enter: bb{block_id} v{value_id}")
|
||||||
key = (block_id, value_id)
|
key = (block_id, value_id)
|
||||||
if key in self._end_i64_cache:
|
if key in self._end_i64_cache:
|
||||||
return self._end_i64_cache[key]
|
return self._end_i64_cache[key]
|
||||||
if _vis is None:
|
|
||||||
_vis = set()
|
|
||||||
if key in _vis:
|
|
||||||
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)
|
|
||||||
|
|
||||||
# Do not synthesize PHIs here. Placeholders are created in the function prepass.
|
# P0-2: ONLY use snapshot - no predecessor recursion
|
||||||
|
|
||||||
# If present in snapshot, coerce there
|
|
||||||
snap = block_end_values.get(block_id, {})
|
snap = block_end_values.get(block_id, {})
|
||||||
if value_id in snap and snap[value_id] is not None:
|
val = snap.get(value_id)
|
||||||
val = snap[value_id]
|
|
||||||
|
if val is not None:
|
||||||
is_phi_val = False
|
is_phi_val = False
|
||||||
try:
|
try:
|
||||||
is_phi_val = hasattr(val, 'add_incoming')
|
is_phi_val = hasattr(val, 'add_incoming')
|
||||||
@ -361,28 +369,16 @@ class Resolver:
|
|||||||
except Exception:
|
except Exception:
|
||||||
belongs_here = False
|
belongs_here = False
|
||||||
if belongs_here:
|
if belongs_here:
|
||||||
self._end_i64_cache[key] = val
|
coerced = self._coerce_in_block_to_i64(val, block_id, bb_map)
|
||||||
return val
|
self._end_i64_cache[key] = coerced
|
||||||
# Otherwise ignore and try predecessors to avoid self-carry from foreign PHI
|
return coerced
|
||||||
coerced = self._coerce_in_block_to_i64(val, block_id, bb_map)
|
# Otherwise, PHI from wrong block -> treat as miss
|
||||||
self._end_i64_cache[key] = coerced
|
else:
|
||||||
return coerced
|
coerced = self._coerce_in_block_to_i64(val, block_id, bb_map)
|
||||||
|
self._end_i64_cache[key] = coerced
|
||||||
|
return coerced
|
||||||
|
|
||||||
# Try recursively from predecessors
|
# P0-2: Snapshot miss - STRICT mode error, else fallback to 0
|
||||||
pred_ids = [p for p in preds.get(block_id, []) if p != block_id]
|
|
||||||
for p in pred_ids:
|
|
||||||
v = self._value_at_end_i64(value_id, p, preds, block_end_values, vmap, bb_map, _vis)
|
|
||||||
if v is not None:
|
|
||||||
self._end_i64_cache[key] = v
|
|
||||||
return v
|
|
||||||
|
|
||||||
# Do not use global vmap here; if not materialized by end of this block
|
|
||||||
# (or its preds), bail out with zero to preserve dominance.
|
|
||||||
|
|
||||||
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")
|
|
||||||
|
|
||||||
# P0-2: STRICT mode - fail fast on resolution miss
|
|
||||||
import os
|
import os
|
||||||
if os.environ.get('NYASH_LLVM_STRICT') == '1':
|
if os.environ.get('NYASH_LLVM_STRICT') == '1':
|
||||||
# Collect diagnostic information
|
# Collect diagnostic information
|
||||||
@ -393,19 +389,19 @@ class Resolver:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Build search path for diagnostics
|
snapshot_keys = sorted(list(snap.keys())) if snap else []
|
||||||
search_path = [block_id] + pred_ids
|
|
||||||
|
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"[LLVM_PY/STRICT] PHI resolution miss:\n"
|
f"[LLVM_PY/STRICT] Value not in block snapshot:\n"
|
||||||
f" ValueId: {value_id}\n"
|
f" ValueId: v{value_id}\n"
|
||||||
f" Target block: {block_id}\n"
|
f" Block: bb{block_id}\n"
|
||||||
f" Predecessors: [{preds_s}]\n"
|
f" Available in snapshot: {snapshot_keys}\n"
|
||||||
f" Search path: {search_path}\n"
|
|
||||||
f" {def_blocks_info}\n"
|
f" {def_blocks_info}\n"
|
||||||
f" Hint: PHI dst may not be registered in def_blocks, or dominance issue"
|
f" Hint: Value should be in block_end_values[{block_id}] but is missing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Non-STRICT: fallback to 0 (for diagnostics)
|
||||||
|
trace_phi(f"[resolve] end_i64 miss: bb{block_id} v{value_id} → 0")
|
||||||
z = ir.Constant(self.i64, 0)
|
z = ir.Constant(self.i64, 0)
|
||||||
self._end_i64_cache[key] = z
|
self._end_i64_cache[key] = z
|
||||||
return z
|
return z
|
||||||
|
|||||||
Reference in New Issue
Block a user