From 0ea2b7d2cb74287232054f1b480cd03097f56810 Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Wed, 17 Dec 2025 16:12:16 +0900 Subject: [PATCH] fix(llvm_py): stabilize PHI incoming selection (no overwrite by failed candidate) --- .../current/main/phases/phase-97/README.md | 1 + src/llvm_py/phi_wiring/wiring.py | 38 +++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/docs/development/current/main/phases/phase-97/README.md b/docs/development/current/main/phases/phase-97/README.md index 953eeb6c..5a011c57 100644 --- a/docs/development/current/main/phases/phase-97/README.md +++ b/docs/development/current/main/phases/phase-97/README.md @@ -4,3 +4,4 @@ - 新規 smoke: `phase97_next_non_ws_llvm_exe.sh`(apps/tests/phase96_json_loader_next_non_ws_min.hako) / `phase97_json_loader_escape_llvm_exe.sh`(apps/tests/phase95_json_loader_escape_min.hako) - LLVM/llvmlite が無い環境では SKIP(integration プロファイルのみで運用) +- PHI wiring: duplicate incoming per pred の上書きバグを修正(0フォールバック回避) diff --git a/src/llvm_py/phi_wiring/wiring.py b/src/llvm_py/phi_wiring/wiring.py index a24ce11a..13cbe880 100644 --- a/src/llvm_py/phi_wiring/wiring.py +++ b/src/llvm_py/phi_wiring/wiring.py @@ -139,10 +139,24 @@ def nearest_pred_on_path( def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[int, int]], context=None): """Wire PHI incoming edges for (block_id, dst_vid) using declared (decl_b, v_src) pairs. + Phase 132-P1: Use context Box for function-local state isolation. - Args: - context: FunctionLowerContext Box containing function-local state + SSOT: duplicate incoming per predecessor + ---------------------------------------- + LLVM PHI wiring may observe *multiple* incoming candidates that map to the same actual + predecessor (`pred_match`). This can happen legitimately because: + - jump-only / trampoline blocks: multiple declared blocks collapse to the same CFG predecessor, + - alias/self-carry handling: placeholder `dst_vid` appears in the incoming list and is rewritten, + - prepass placeholders: additional metadata can create redundant candidates. + + Selection contract: + - Once a predecessor’s incoming value is successfully chosen, do not overwrite it later + (avoid last-wins instability). + - A synthesized `0` is treated as an "unresolved sentinel" (fallback path). If we later obtain + a successfully-resolved non-zero value for the same predecessor, we may replace `0`. + - In STRICT mode, `0` should be rare; a `0` incoming indicates "missing snapshot / unresolved" + and should be caught by stricter checks upstream. """ bb = builder.bb_map.get(block_id) if bb is None: @@ -201,6 +215,15 @@ def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[in init_src_vid = vi break chosen: Dict[int, ir.Value] = {} + + def _is_zero_const(v: ir.Value) -> bool: + try: + if isinstance(v, ir.Constant) and isinstance(v.type, ir.IntType) and v.type.width == 64: + return int(getattr(v, "constant", 1)) == 0 + except Exception: + pass + return False + for (b_decl, v_src) in incoming: try: bd = int(b_decl) @@ -227,6 +250,7 @@ def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[in except Exception as e: trace({"phi": "wire_resolve_fail", "vs": vs, "pred": pred_match, "error": str(e)}) val = None + resolved_ok = val is not None # Normalize to a well-typed LLVM value (i64) if val is None: val = _const_i64(builder, 0) @@ -237,7 +261,15 @@ def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[in val = _const_i64(builder, int(val)) except Exception: val = _const_i64(builder, 0) - chosen[pred_match] = val + # SSOT for ambiguous PHI incoming (same pred_match multiple times): + # - prefer a non-zero / successfully-resolved value over a synthesized zero, + # - otherwise keep the first choice to avoid last-wins "overwrite to 0". + prev = chosen.get(pred_match) + if prev is None: + chosen[pred_match] = val + else: + if _is_zero_const(prev) and not _is_zero_const(val) and resolved_ok: + 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():