diff --git a/src/llvm_py/builders/block_lower.py b/src/llvm_py/builders/block_lower.py index 49779a2e..9778575a 100644 --- a/src/llvm_py/builders/block_lower.py +++ b/src/llvm_py/builders/block_lower.py @@ -245,6 +245,12 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An vmap_cur[dst] = _gval if dst not in created_ids and dst in vmap_cur: 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: pass # Phase 131-4 Pass A: DEFER terminators until after PHI finalization diff --git a/src/llvm_py/instructions/phi.py b/src/llvm_py/instructions/phi.py index 23a490d8..a4479861 100644 --- a/src/llvm_py/instructions/phi.py +++ b/src/llvm_py/instructions/phi.py @@ -58,39 +58,42 @@ def lower_phi( # Fallback: use blocks in incoming list 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]] = [] used_default_zero = False + + import os + strict_mode = os.environ.get('NYASH_LLVM_STRICT') == '1' + for block_id in actual_preds: block = bb_map.get(block_id) vid = incoming_map.get(block_id) if block is None: 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: - try: - pred_block_obj = bb_map.get(block_id) - if pred_block_obj is not None and hasattr(resolver, 'resolve_i64'): - val = resolver.resolve_i64(vid, pred_block_obj, preds_map or {}, block_end_values or {}, vmap, bb_map) - else: - val = None - except Exception: - val = None - if val is None: - # Missing incoming for this predecessor → default 0 - val = ir.Constant(phi_type, 0) - used_default_zero = True + + # P1: Get value from pred_bid's snapshot (SSOT - no global search) + if block_end_values is not None: + pred_snapshot = block_end_values.get(block_id, {}) + val = pred_snapshot.get(vid) if vid is not None else None + else: + val = None + + if val is None: + # P1: STRICT mode - fail fast on snapshot miss + if strict_mode: + available_keys = sorted(list(pred_snapshot.keys())) if block_end_values is not None else [] + raise RuntimeError( + 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: - # 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 if hasattr(val, 'type') and val.type != phi_type: pb = ir.IRBuilder(block) diff --git a/src/llvm_py/llvm_builder.py b/src/llvm_py/llvm_builder.py index fac83712..8001d9fe 100644 --- a/src/llvm_py/llvm_builder.py +++ b/src/llvm_py/llvm_builder.py @@ -119,7 +119,10 @@ class NyashLLVMBuilder: # Resolver for unified value resolution 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 self.loop_count = 0 # Heuristics for minor gated fixes diff --git a/src/llvm_py/resolver.py b/src/llvm_py/resolver.py index 84ab48d7..36eacf1e 100644 --- a/src/llvm_py/resolver.py +++ b/src/llvm_py/resolver.py @@ -61,6 +61,8 @@ class Resolver: self.def_blocks = {} # Optional: block -> { dst_vid -> [(pred_bid, val_vid), ...] } for PHIs from MIR JSON 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: try: @@ -322,28 +324,34 @@ class Resolver: self.ptr_cache[cache_key] = 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], block_end_values: Dict[int, Dict[int, Any]], vmap: Dict[int, Any], 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.""" + """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}") 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: - 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. - - # If present in snapshot, coerce there + # P0-2: ONLY use snapshot - no predecessor recursion snap = block_end_values.get(block_id, {}) - if value_id in snap and snap[value_id] is not None: - val = snap[value_id] + val = snap.get(value_id) + + if val is not None: is_phi_val = False try: is_phi_val = hasattr(val, 'add_incoming') @@ -361,28 +369,16 @@ class Resolver: 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 + coerced = self._coerce_in_block_to_i64(val, block_id, bb_map) + self._end_i64_cache[key] = coerced + return coerced + # Otherwise, PHI from wrong block -> treat as miss + else: + coerced = self._coerce_in_block_to_i64(val, block_id, bb_map) + self._end_i64_cache[key] = coerced + return coerced - # Try recursively from predecessors - 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 + # P0-2: Snapshot miss - STRICT mode error, else fallback to 0 import os if os.environ.get('NYASH_LLVM_STRICT') == '1': # Collect diagnostic information @@ -393,19 +389,19 @@ class Resolver: except Exception: pass - # Build search path for diagnostics - search_path = [block_id] + pred_ids + snapshot_keys = sorted(list(snap.keys())) if snap else [] raise RuntimeError( - f"[LLVM_PY/STRICT] PHI resolution miss:\n" - f" ValueId: {value_id}\n" - f" Target block: {block_id}\n" - f" Predecessors: [{preds_s}]\n" - f" Search path: {search_path}\n" + f"[LLVM_PY/STRICT] Value not in block snapshot:\n" + f" ValueId: v{value_id}\n" + f" Block: bb{block_id}\n" + f" Available in snapshot: {snapshot_keys}\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) self._end_i64_cache[key] = z return z