diff --git a/src/llvm_py/builders/block_lower.py b/src/llvm_py/builders/block_lower.py index 8a88afc7..a52417c9 100644 --- a/src/llvm_py/builders/block_lower.py +++ b/src/llvm_py/builders/block_lower.py @@ -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: diff --git a/src/llvm_py/instructions/controlflow/branch.py b/src/llvm_py/instructions/controlflow/branch.py index 481a6abb..88ab42b5 100644 --- a/src/llvm_py/instructions/controlflow/branch.py +++ b/src/llvm_py/instructions/controlflow/branch.py @@ -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) diff --git a/src/llvm_py/instructions/unop.py b/src/llvm_py/instructions/unop.py index 25bd9332..877553a8 100644 --- a/src/llvm_py/instructions/unop.py +++ b/src/llvm_py/instructions/unop.py @@ -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): diff --git a/src/llvm_py/llvm_builder.py b/src/llvm_py/llvm_builder.py index 3819617a..fac83712 100644 --- a/src/llvm_py/llvm_builder.py +++ b/src/llvm_py/llvm_builder.py @@ -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) - except Exception: + 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': diff --git a/src/llvm_py/phi_wiring/wiring.py b/src/llvm_py/phi_wiring/wiring.py index 957c417b..2c4b7e75 100644 --- a/src/llvm_py/phi_wiring/wiring.py +++ b/src/llvm_py/phi_wiring/wiring.py @@ -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: diff --git a/src/runner/modes/common_util/exec.rs b/src/runner/modes/common_util/exec.rs index 78fc753f..b13cba40 100644 --- a/src/runner/modes/common_util/exec.rs +++ b/src/runner/modes/common_util/exec.rs @@ -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={:?})",