fix(llvm): Phase 131-7 - Multi-pass vmap sync 修正(Branch bug 解消)
Phase 131-7: Multi-pass block lowering の vmap sync バグ修正 問題: - Pass A (body instructions) が vmap_cur に値を保存 - Pass C (deferred terminators) が builder.vmap を参照 - → Pass A の値が Pass C に届かない - → Branch condition が 0 に fallback → 無限ループ 修正: - src/llvm_py/builders/block_lower.py (lines 237-248): - PHI-only フィルタを削除 - 全ての値を global vmap に sync 変更ファイル: - src/llvm_py/builders/block_lower.py: vmap sync 修正 - src/llvm_py/instructions/controlflow/branch.py: trace logging 追加 - src/llvm_py/instructions/unop.py: trace logging 追加 - src/llvm_py/llvm_builder.py: debug helpers 追加 - src/llvm_py/phi_wiring/wiring.py: trace logging 追加 - src/runner/modes/common_util/exec.rs: Python stdout 転送 結果: - ✅ Branch bug 修正: ループが正しく終了 - ✅ 出力: 無限ループ → 3回で停止 - ❌ 新 bug 発見: ExternCall が null を受け取る(PHI 値の代わりに) Next: Phase 131-8 (ExternCall argument handling 修正) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -234,10 +234,12 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An
|
||||
builder._deferred_terminators = {}
|
||||
if term_ops:
|
||||
builder._deferred_terminators[bid] = (bb, term_ops)
|
||||
# Phase 131-7: Sync ALL created values to global vmap (not just PHIs)
|
||||
# This ensures Pass C (deferred terminators) can access values from Pass A
|
||||
try:
|
||||
for vid in created_ids:
|
||||
val = vmap_cur.get(vid)
|
||||
if val is not None and hasattr(val, 'add_incoming'):
|
||||
if val is not None:
|
||||
try:
|
||||
builder.vmap[vid] = val
|
||||
except Exception:
|
||||
|
||||
@ -30,7 +30,22 @@ def lower_branch(
|
||||
bb_map: Block map
|
||||
"""
|
||||
# Get condition value with preference to same-block SSA
|
||||
# Phase 131-7 debug
|
||||
try:
|
||||
import os, sys
|
||||
if os.environ.get('NYASH_CLI_VERBOSE') == '1':
|
||||
print(f"[branch] cond_vid={cond_vid} in vmap={cond_vid in vmap} (vmap id={id(vmap)})", file=sys.stderr)
|
||||
if cond_vid in vmap:
|
||||
print(f"[branch] vmap[{cond_vid}] = {vmap[cond_vid]}", file=sys.stderr)
|
||||
except Exception:
|
||||
pass
|
||||
cond = resolve_i64_strict(resolver, cond_vid, builder.block, preds, block_end_values, vmap, bb_map)
|
||||
try:
|
||||
import os, sys
|
||||
if os.environ.get('NYASH_CLI_VERBOSE') == '1':
|
||||
print(f"[branch] resolved cond={cond}", file=sys.stderr)
|
||||
except Exception:
|
||||
pass
|
||||
if cond is None:
|
||||
# Default to false if missing
|
||||
cond = ir.Constant(ir.IntType(1), 0)
|
||||
|
||||
@ -32,10 +32,26 @@ def lower_unop(
|
||||
val = resolve_i64_strict(resolver, src, current_block, preds, block_end_values, vmap, bb_map)
|
||||
# Logical NOT: prefer i1 when available; otherwise compare == 0
|
||||
if kind in ('not', 'logical_not', '!'):
|
||||
# Phase 131-7 debug: check what type val is
|
||||
try:
|
||||
import os, sys
|
||||
if os.environ.get('NYASH_CLI_VERBOSE') == '1':
|
||||
val_type = str(val.type) if hasattr(val, 'type') else 'no-type'
|
||||
print(f"[unop/not] dst={dst} val={val} type={val_type}", file=sys.stderr)
|
||||
except Exception:
|
||||
pass
|
||||
# If already i1, xor with 1
|
||||
if hasattr(val, 'type') and isinstance(val.type, ir.IntType) and val.type.width == 1:
|
||||
one = ir.Constant(ir.IntType(1), 1)
|
||||
vmap[dst] = builder.xor(val, one, name=f"not_{dst}")
|
||||
result = builder.xor(val, one, name=f"not_{dst}")
|
||||
vmap[dst] = result
|
||||
# Phase 131-7 debug
|
||||
try:
|
||||
import os
|
||||
if os.environ.get('NYASH_CLI_VERBOSE') == '1':
|
||||
print(f"[unop/not] Stored dst={dst} -> {result} in vmap (id={id(vmap)})", file=sys.stderr)
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
# If pointer: null check (== null) yields i1
|
||||
if hasattr(val, 'type') and isinstance(val.type, ir.PointerType):
|
||||
|
||||
@ -460,12 +460,20 @@ class NyashLLVMBuilder:
|
||||
self.lower_instruction(builder, inst, func)
|
||||
else:
|
||||
self.lower_instruction(builder, inst, func)
|
||||
# Sync per-block vmap snapshot with any new definitions that were
|
||||
# written into the global vmap by lowering routines (e.g., copy)
|
||||
# Phase 131-7: Bidirectional sync between per-block vmap and global vmap
|
||||
# This ensures values are available for subsequent instructions (e.g., branch using unop result)
|
||||
try:
|
||||
dst = inst.get("dst")
|
||||
if isinstance(dst, int):
|
||||
if dst in self.vmap:
|
||||
# First, check if the instruction wrote to vmap_cur (local lowering result)
|
||||
if dst in vmap_cur:
|
||||
# Sync vmap_cur[dst] -> self.vmap[dst] -> vmap_cur[dst]
|
||||
# (bidirectional to ensure both maps have the value)
|
||||
self.vmap[dst] = vmap_cur[dst]
|
||||
# Explicitly update vmap_cur reference in case it's a different object
|
||||
# (though it should be the same, this ensures consistency)
|
||||
# Then check if global vmap has a value (e.g., from resolver)
|
||||
elif dst in self.vmap:
|
||||
_gval = self.vmap[dst]
|
||||
# Avoid syncing PHIs that belong to other blocks (placeholders)
|
||||
try:
|
||||
@ -488,6 +496,15 @@ class NyashLLVMBuilder:
|
||||
try:
|
||||
import os
|
||||
trace_debug(f"[llvm-py] term op: {inst.get('op')} dst={inst.get('dst')} cond={inst.get('cond')}")
|
||||
# Phase 131-7: Debug branch condition resolution
|
||||
if inst.get('op') == 'branch':
|
||||
cond_vid = inst.get('cond')
|
||||
if cond_vid is not None:
|
||||
trace_debug(f"[llvm-py] branch cond_vid={cond_vid} vmap_cur={cond_vid in vmap_cur} self.vmap={cond_vid in self.vmap}")
|
||||
if cond_vid in vmap_cur:
|
||||
trace_debug(f"[llvm-py] branch cond in vmap_cur: {vmap_cur[cond_vid]}")
|
||||
if cond_vid in self.vmap:
|
||||
trace_debug(f"[llvm-py] branch cond in self.vmap: {self.vmap[cond_vid]}")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
@ -673,22 +690,59 @@ class NyashLLVMBuilder:
|
||||
bd = int(b_decl); vs = int(v_src)
|
||||
except Exception:
|
||||
continue
|
||||
try:
|
||||
trace_phi(f"[finalize_phis] Processing incoming: dst_vid={dst_vid}, b_decl={bd}, v_src={vs}")
|
||||
except Exception:
|
||||
pass
|
||||
pred_match = nearest_pred_on_path(bd)
|
||||
try:
|
||||
trace_phi(f"[finalize_phis] nearest_pred_on_path({bd}) = {pred_match}")
|
||||
except Exception:
|
||||
pass
|
||||
if pred_match is None:
|
||||
continue
|
||||
# If self-carry is specified (vs == dst_vid), map to init_src_vid when available
|
||||
original_vs = vs
|
||||
if vs == int(dst_vid) and init_src_vid is not None:
|
||||
try:
|
||||
trace_phi(f"[finalize_phis] SELF-CARRY DETECTED: vs={vs} == dst_vid={dst_vid}, replacing with init_src_vid={init_src_vid}")
|
||||
except Exception:
|
||||
pass
|
||||
vs = int(init_src_vid)
|
||||
try:
|
||||
val = self.resolver._value_at_end_i64(vs, pred_match, self.preds, self.block_end_values, self.vmap, self.bb_map)
|
||||
try:
|
||||
trace_phi(f"[finalize_phis] _value_at_end_i64({vs}, {pred_match}) = {val}")
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
val = None
|
||||
try:
|
||||
trace_phi(f"[finalize_phis] _value_at_end_i64({vs}, {pred_match}) FAILED: {e}")
|
||||
except Exception:
|
||||
pass
|
||||
if val is None:
|
||||
try:
|
||||
trace_phi(f"[finalize_phis] Value resolution failed, using fallback 0")
|
||||
except Exception:
|
||||
pass
|
||||
val = ir.Constant(self.i64, 0)
|
||||
chosen[pred_match] = val
|
||||
try:
|
||||
trace_phi(f"[finalize_phis] CHOSEN: pred_bid={pred_match} -> val={val}")
|
||||
except Exception:
|
||||
pass
|
||||
# Fill remaining predecessors with dst carry or (optionally) a synthesized default
|
||||
try:
|
||||
trace_phi(f"[finalize_phis] Filling remaining preds: preds_list={preds_list}, chosen_keys={list(chosen.keys())}")
|
||||
except Exception:
|
||||
pass
|
||||
for pred_bid in preds_list:
|
||||
if pred_bid not in chosen:
|
||||
try:
|
||||
trace_phi(f"[finalize_phis] Filling missing pred_bid={pred_bid}")
|
||||
except Exception:
|
||||
pass
|
||||
val = None
|
||||
# Optional gated fix for esc_json: default branch should append current char
|
||||
try:
|
||||
@ -770,6 +824,15 @@ class NyashLLVMBuilder:
|
||||
|
||||
# Compile
|
||||
ir_text = str(self.module)
|
||||
# Optional IR dump for debugging (Phase 131-7)
|
||||
if os.environ.get('NYASH_LLVM_DUMP_IR') == '1':
|
||||
try:
|
||||
ir_dump_path = output_path.replace('.o', '.ll')
|
||||
with open(ir_dump_path, 'w') as f:
|
||||
f.write(ir_text)
|
||||
print(f"[llvm_builder] IR dumped to: {ir_dump_path}", file=sys.stderr)
|
||||
except Exception as e:
|
||||
print(f"[llvm_builder] IR dump failed: {e}", file=sys.stderr)
|
||||
# Optional sanitize: drop any empty PHI rows (no incoming list) to satisfy IR parser.
|
||||
# Gate with NYASH_LLVM_SANITIZE_EMPTY_PHI=1. Additionally, auto-enable when harness is requested.
|
||||
if os.environ.get('NYASH_LLVM_SANITIZE_EMPTY_PHI') == '1' or os.environ.get('NYASH_LLVM_USE_HARNESS') == '1':
|
||||
|
||||
@ -189,17 +189,25 @@ def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[in
|
||||
vs = int(v_src)
|
||||
except Exception:
|
||||
continue
|
||||
trace({"phi": "wire_process", "dst": int(dst_vid), "decl_b": bd, "v_src": vs, "init_src_vid": init_src_vid})
|
||||
pred_match = nearest_pred_on_path(succs, preds_list, bd, block_id)
|
||||
trace({"phi": "wire_pred_match", "decl_b": bd, "pred_match": pred_match})
|
||||
if pred_match is None:
|
||||
trace({"phi": "wire_skip_no_path", "decl_b": bd, "target": int(block_id), "src": vs})
|
||||
continue
|
||||
original_vs = vs
|
||||
if vs == int(dst_vid) and init_src_vid is not None:
|
||||
trace({"phi": "wire_self_carry", "dst": int(dst_vid), "vs": vs, "init_src_vid": init_src_vid})
|
||||
vs = int(init_src_vid)
|
||||
if original_vs != vs:
|
||||
trace({"phi": "wire_replaced_src", "original": original_vs, "replaced": vs})
|
||||
try:
|
||||
val = builder.resolver._value_at_end_i64(
|
||||
vs, pred_match, builder.preds, builder.block_end_values, builder.vmap, builder.bb_map
|
||||
)
|
||||
except Exception:
|
||||
trace({"phi": "wire_resolved", "vs": vs, "pred": pred_match, "val_type": type(val).__name__})
|
||||
except Exception as e:
|
||||
trace({"phi": "wire_resolve_fail", "vs": vs, "pred": pred_match, "error": str(e)})
|
||||
val = None
|
||||
# Normalize to a well-typed LLVM value (i64)
|
||||
if val is None:
|
||||
|
||||
@ -43,6 +43,17 @@ pub fn llvmlite_emit_object(
|
||||
out_path,
|
||||
]);
|
||||
let out = spawn_with_timeout(cmd, timeout_ms).map_err(|e| format!("spawn harness: {}", e))?;
|
||||
// Print Python stdout/stderr for debugging (Phase 131-7)
|
||||
let should_print_debug = std::env::var("NYASH_LLVM_TRACE_PHI").ok().as_deref() == Some("1")
|
||||
|| std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1");
|
||||
if should_print_debug {
|
||||
if !out.stdout.is_empty() {
|
||||
eprintln!("[Python stdout]:\n{}", String::from_utf8_lossy(&out.stdout));
|
||||
}
|
||||
if !out.stderr.is_empty() {
|
||||
eprintln!("[Python stderr]:\n{}", String::from_utf8_lossy(&out.stderr));
|
||||
}
|
||||
}
|
||||
if out.timed_out || !out.status_ok {
|
||||
return Err(format!(
|
||||
"llvmlite harness failed (timeout={} code={:?})",
|
||||
|
||||
Reference in New Issue
Block a user