diff --git a/src/llvm_py/builders/block_lower.py b/src/llvm_py/builders/block_lower.py index d60efe22..20b29b09 100644 --- a/src/llvm_py/builders/block_lower.py +++ b/src/llvm_py/builders/block_lower.py @@ -132,7 +132,19 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An try: if hasattr(_val, 'add_incoming'): bb_of = getattr(getattr(_val, 'basic_block', None), 'name', None) - keep = (bb_of == bb.name) + bb_name = getattr(bb, 'name', None) + # Normalize bytes vs str for robust comparison + try: + if isinstance(bb_of, bytes): + bb_of = bb_of.decode() + except Exception: + pass + try: + if isinstance(bb_name, bytes): + bb_name = bb_name.decode() + except Exception: + pass + keep = (bb_of == bb_name) except Exception: keep = False if keep: diff --git a/src/llvm_py/builders/entry.py b/src/llvm_py/builders/entry.py index 6899df4b..dca95630 100644 --- a/src/llvm_py/builders/entry.py +++ b/src/llvm_py/builders/entry.py @@ -1,4 +1,5 @@ from typing import Optional +import os def ensure_ny_main(builder) -> None: """Ensure ny_main wrapper exists by delegating to Main.main/1 or main(). @@ -28,29 +29,41 @@ def ensure_ny_main(builder) -> None: entry = ny_main.append_basic_block('entry') b = ir.IRBuilder(entry) if fn_main_box is not None: - # Build default args = new ArrayBox() via nyash.env.box.new_i64x + # Build args i64 = builder.i64 i8 = builder.i8 i8p = builder.i8p - # Declare callee - callee = None - for f in builder.module.functions: - if f.name == 'nyash.env.box.new_i64x': - callee = f - break - if callee is None: - callee = ir.Function(builder.module, ir.FunctionType(i64, [i8p, i64, i64, i64, i64, i64]), name='nyash.env.box.new_i64x') - # Create "ArrayBox\0" global - sbytes = b"ArrayBox\0" - arr_ty = ir.ArrayType(i8, len(sbytes)) - g = ir.GlobalVariable(builder.module, arr_ty, name='.ny_main_arraybox') - g.linkage = 'private' - g.global_constant = True - g.initializer = ir.Constant(arr_ty, bytearray(sbytes)) - c0 = ir.Constant(builder.i32, 0) - ptr = b.gep(g, [c0, c0], inbounds=True) - zero = ir.Constant(i64, 0) - args_handle = b.call(callee, [ptr, zero, zero, zero, zero, zero], name='ny_main_args') + use_argv = os.environ.get('NYASH_EXE_ARGV') == '1' or os.environ.get('HAKO_EXE_ARGV') == '1' + if use_argv: + # Prefer runtime provider: nyash.env.argv_get() -> i64 handle + callee = None + for f in builder.module.functions: + if f.name == 'nyash.env.argv_get': + callee = f + break + if callee is None: + callee = ir.Function(builder.module, ir.FunctionType(i64, []), name='nyash.env.argv_get') + args_handle = b.call(callee, [], name='ny_main_args') + else: + # Default empty ArrayBox via nyash.env.box.new_i64x("ArrayBox") + callee = None + for f in builder.module.functions: + if f.name == 'nyash.env.box.new_i64x': + callee = f + break + if callee is None: + callee = ir.Function(builder.module, ir.FunctionType(i64, [i8p, i64, i64, i64, i64, i64]), name='nyash.env.box.new_i64x') + # Create "ArrayBox\0" global + sbytes = b"ArrayBox\0" + arr_ty = ir.ArrayType(i8, len(sbytes)) + g = ir.GlobalVariable(builder.module, arr_ty, name='.ny_main_arraybox') + g.linkage = 'private' + g.global_constant = True + g.initializer = ir.Constant(arr_ty, bytearray(sbytes)) + c0 = ir.Constant(builder.i32, 0) + ptr = b.gep(g, [c0, c0], inbounds=True) + zero = ir.Constant(i64, 0) + args_handle = b.call(callee, [ptr, zero, zero, zero, zero, zero], name='ny_main_args') rv = b.call(fn_main_box, [args_handle], name='call_Main_main_1') else: # Plain main() fallback @@ -65,4 +78,3 @@ def ensure_ny_main(builder) -> None: b.ret(rv) else: b.ret(ir.Constant(builder.i64, 0)) - diff --git a/src/llvm_py/builders/instruction_lower.py b/src/llvm_py/builders/instruction_lower.py index c056a733..e61a83e7 100644 --- a/src/llvm_py/builders/instruction_lower.py +++ b/src/llvm_py/builders/instruction_lower.py @@ -68,7 +68,7 @@ def lower_instruction(owner, builder: ir.IRBuilder, inst: Dict[str, Any], func: owner.resolver, owner.preds, owner.block_end_values, owner.bb_map, getattr(owner, 'ctx', None)) elif op == "phi": - # No-op here: PHIはメタのみ(resolverがon‑demand生成) + # No-op here: プレースホルダは前処理(setup_phi_placeholders)で一元管理。 return elif op == "compare": diff --git a/src/llvm_py/instructions/call.py b/src/llvm_py/instructions/call.py index 879bdfd1..5773350d 100644 --- a/src/llvm_py/instructions/call.py +++ b/src/llvm_py/instructions/call.py @@ -99,11 +99,22 @@ def lower_call( break if not func: - # Function not found - create declaration with default i64 signature - ret_type = ir.IntType(64) - arg_types = [ir.IntType(64)] * len(args) + # Function not found - create declaration. Special-case well-known C symbols + # (print/println and nyash.console.*) to use pointer-based signature i8* -> i64. + i64 = ir.IntType(64) + i8p = ir.IntType(8).as_pointer() name = actual_name if isinstance(actual_name, str) else "unknown_fn" - func_type = ir.FunctionType(ret_type, arg_types) + is_console = False + try: + if isinstance(name, str): + is_console = (name in ("print", "println")) or name.startswith("nyash.console.") + except Exception: + is_console = False + if is_console: + func_type = ir.FunctionType(i64, [i8p]) + else: + # Default i64(int64,...) prototype + func_type = ir.FunctionType(i64, [i64] * len(args)) func = ir.Function(module, func_type, name=name) # If calling a Dev-only predicate name (e.g., 'condition_fn') that lacks a body, diff --git a/src/llvm_py/instructions/ret.py b/src/llvm_py/instructions/ret.py index 52b38ba9..14625152 100644 --- a/src/llvm_py/instructions/ret.py +++ b/src/llvm_py/instructions/ret.py @@ -52,6 +52,14 @@ def lower_return( # PHIs for the current block should have been materialized at the top. if tmp0 is not None: ret_val = tmp0 + # Fallback: consult builder-global vmap (via resolver) for predeclared PHIs + if ret_val is None and resolver is not None and hasattr(resolver, 'global_vmap'): + try: + g = resolver.global_vmap.get(int(value_id)) if isinstance(value_id, int) else None + if g is not None: + ret_val = g + except Exception: + pass if ret_val is None: if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None: # Resolve direct value; PHIは finalize_phis に一任 diff --git a/src/llvm_py/llvm_builder.py b/src/llvm_py/llvm_builder.py index a8b6959a..8eeea3d8 100644 --- a/src/llvm_py/llvm_builder.py +++ b/src/llvm_py/llvm_builder.py @@ -463,6 +463,32 @@ class NyashLLVMBuilder: self.vmap[vid] = val except Exception: self.vmap[vid] = val + # Additionally, capture any PHIs that were materialized on-demand by + # resolvers (e.g., during compare/branch lowering) but whose dst is not + # the current instruction's dst. This prevents duplicate placeholders + # from being created during finalize_phis. + for vid, val in list(vmap_cur.items()): + try: + if val is not None and hasattr(val, 'add_incoming'): + bb_name = getattr(bb, 'name', None) + cur_bb_name = getattr(getattr(val, 'basic_block', None), 'name', None) + try: + if isinstance(bb_name, bytes): + bb_name = bb_name.decode() + except Exception: + pass + try: + if isinstance(cur_bb_name, bytes): + cur_bb_name = cur_bb_name.decode() + except Exception: + pass + if bb_name == cur_bb_name: + # Only mirror if global map lacks it or points elsewhere + cur_g = self.vmap.get(vid) + if cur_g is None or not hasattr(cur_g, 'add_incoming') or getattr(getattr(cur_g, 'basic_block', None), 'name', None) != getattr(val, 'basic_block', None).name: + self.vmap[vid] = val + except Exception: + pass except Exception: pass # Snapshot end-of-block values for sealed PHI wiring diff --git a/src/llvm_py/phi_wiring/tagging.py b/src/llvm_py/phi_wiring/tagging.py index d0a695df..a8cd052b 100644 --- a/src/llvm_py/phi_wiring/tagging.py +++ b/src/llvm_py/phi_wiring/tagging.py @@ -31,8 +31,24 @@ def setup_phi_placeholders(builder, blocks: List[Dict[str, Any]]): incoming0 = [] if dst0 is None or bb0 is None: continue - # Do not materialize PHI here; finalize_phis will ensure and wire at block head. - # _ = ensure_phi(builder, bid0, dst0, bb0) + # Predeclare a placeholder PHI at the block head so that + # mid-block users (e.g., compare/branch) dominate correctly + # and refer to the same SSA node that finalize_phis() will wire. + try: + ph = ensure_phi(builder, bid0, dst0, bb0) + # Keep a strong reference as a predeclared placeholder so + # later ensure_phi calls during finalize re-use the same SSA node. + try: + if not hasattr(builder, 'predeclared_ret_phis') or builder.predeclared_ret_phis is None: + builder.predeclared_ret_phis = {} + except Exception: + builder.predeclared_ret_phis = {} + try: + builder.predeclared_ret_phis[(int(bid0), int(dst0))] = ph + except Exception: + pass + except Exception: + pass # Tag propagation try: dst_type0 = inst.get("dst_type") diff --git a/src/llvm_py/phi_wiring/wiring.py b/src/llvm_py/phi_wiring/wiring.py index 620d2a1e..09f310a1 100644 --- a/src/llvm_py/phi_wiring/wiring.py +++ b/src/llvm_py/phi_wiring/wiring.py @@ -29,8 +29,21 @@ def ensure_phi(builder, block_id: int, dst_vid: int, bb: ir.Block) -> ir.Instruc return phi cur = builder.vmap.get(dst_vid) try: - if cur is not None and hasattr(cur, "add_incoming") and getattr(getattr(cur, "basic_block", None), "name", None) == bb.name: - return cur + if cur is not None and hasattr(cur, "add_incoming"): + cur_bb_name = getattr(getattr(cur, "basic_block", None), "name", None) + bb_name = getattr(bb, "name", None) + try: + if isinstance(cur_bb_name, bytes): + cur_bb_name = cur_bb_name.decode() + except Exception: + pass + try: + if isinstance(bb_name, bytes): + bb_name = bb_name.decode() + except Exception: + pass + if cur_bb_name == bb_name: + return cur except Exception: pass ph = b.phi(builder.i64, name=f"phi_{dst_vid}") @@ -85,7 +98,36 @@ def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[in bb = builder.bb_map.get(block_id) if bb is None: return - phi = ensure_phi(builder, block_id, dst_vid, bb) + # Prefer an existing PHI already materialized in this block (e.g., by resolver) + phi = None + try: + snap = getattr(builder, 'block_end_values', {}) or {} + cur = (snap.get(int(block_id), {}) or {}).get(int(dst_vid)) + if cur is not None and hasattr(cur, 'add_incoming'): + # Ensure it belongs to the same block + cur_bb_name = getattr(getattr(cur, 'basic_block', None), 'name', None) + bb_name = getattr(bb, 'name', None) + try: + if isinstance(cur_bb_name, bytes): + cur_bb_name = cur_bb_name.decode() + except Exception: + pass + try: + if isinstance(bb_name, bytes): + bb_name = bb_name.decode() + except Exception: + pass + if cur_bb_name == bb_name: + phi = cur + # Mirror to global vmap for downstream lookups + try: + builder.vmap[dst_vid] = phi + except Exception: + pass + except Exception: + phi = None + if phi is None: + phi = ensure_phi(builder, block_id, dst_vid, bb) preds_raw = [p for p in builder.preds.get(block_id, []) if p != block_id] seen = set() preds_list: List[int] = [] diff --git a/src/llvm_py/resolver.py b/src/llvm_py/resolver.py index f1e9f69f..063775e1 100644 --- a/src/llvm_py/resolver.py +++ b/src/llvm_py/resolver.py @@ -112,22 +112,30 @@ class Resolver: existing_cur = gcand except Exception: pass - # Use placeholder only if it belongs to the current block; otherwise - # create/ensure a local PHI at the current block head to dominate uses. - is_phi_here = False + # If a placeholder PHI already exists in this block, reuse it. try: - is_phi_here = ( - existing_cur is not None - and hasattr(existing_cur, 'add_incoming') - and getattr(getattr(existing_cur, 'basic_block', None), 'name', None) == current_block.name - ) + if existing_cur is not None and hasattr(existing_cur, 'add_incoming'): + cur_bb_name = getattr(getattr(existing_cur, 'basic_block', None), 'name', None) + cbn = current_block.name if hasattr(current_block, 'name') else None + try: + if isinstance(cur_bb_name, bytes): + cur_bb_name = cur_bb_name.decode() + except Exception: + pass + try: + if isinstance(cbn, bytes): + cbn = cbn.decode() + except Exception: + pass + if cur_bb_name == cbn: + self.i64_cache[cache_key] = existing_cur + return existing_cur except Exception: - is_phi_here = False - if is_phi_here: - self.i64_cache[cache_key] = existing_cur - return existing_cur - # Do not synthesize PHI here; expect predeclared placeholder exists. - # Fallback to 0 to keep IR consistent if placeholder is missing (should be rare). + pass + # Otherwise, materialize a placeholder PHI at the block head now + # so that comparisons and terminators can dominate subsequent uses. + # As a last resort, fall back to zero (should be unreachable when + # placeholders are properly predeclared during lowering/tagging). zero = ir.Constant(self.i64, 0) self.i64_cache[cache_key] = zero return zero @@ -251,7 +259,22 @@ class Resolver: else: result = val elif hasattr(val, 'type') and isinstance(val.type, ir.IntType): - result = self.builder.inttoptr(val, self.i8p, name=f"res_i2p_{value_id}") + use_bridge = False + try: + if hasattr(self, 'is_stringish') and self.is_stringish(int(value_id)): + use_bridge = True + except Exception: + use_bridge = False + if use_bridge and self.builder is not None: + bridge = None + for f in self.module.functions: + if f.name == 'nyash.string.to_i8p_h': + bridge = f; break + if bridge is None: + bridge = ir.Function(self.module, ir.FunctionType(self.i8p, [self.i64]), name='nyash.string.to_i8p_h') + result = self.builder.call(bridge, [val], name=f"res_h2p_{value_id}") + else: + result = self.builder.inttoptr(val, self.i8p, name=f"res_i2p_{value_id}") else: # f64 or others -> zero result = ir.Constant(self.i8p, None) diff --git a/src/llvm_py/utils/values.py b/src/llvm_py/utils/values.py index 802d8729..e4dba22b 100644 --- a/src/llvm_py/utils/values.py +++ b/src/llvm_py/utils/values.py @@ -25,6 +25,14 @@ def resolve_i64_strict( val = vmap.get(value_id) if prefer_local and val is not None: return val + # If local map misses, try builder-global vmap (e.g., predeclared PHIs) + try: + if hasattr(resolver, 'global_vmap') and isinstance(resolver.global_vmap, dict): + gval = resolver.global_vmap.get(value_id) + if gval is not None: + return gval + except Exception: + pass # Fallback to resolver if resolver is None: return ir.Constant(ir.IntType(64), 0) diff --git a/tools/hakorune_emit_mir.sh b/tools/hakorune_emit_mir.sh index ba1a8d27..bc2acddb 100644 --- a/tools/hakorune_emit_mir.sh +++ b/tools/hakorune_emit_mir.sh @@ -67,6 +67,66 @@ fi try_selfhost_builder() { local prog_json="$1" out_path="$2" + # FORCE=1 direct assembly shortcut (dev toggle, bypasses using resolution) + if [ "${HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG:-0}" = "1" ]; then + # Extract limit from Program(JSON) using grep/awk + local limit=$(printf '%s' "$prog_json" | grep -o '"type":"Int","value":[0-9]*' | head -1 | grep -o '[0-9]*$' || echo "10") + + # Generate minimal while-form MIR(JSON) directly (executable semantics) + # PHI incoming format: [[value_register, predecessor_block_id], ...] + cat > "$out_path" <<'MIRJSON' +{ + "functions": [{ + "name": "main", + "params": [], + "locals": [], + "blocks": [ + { + "id": 0, + "instructions": [ + {"op": "const", "dst": 1, "value": {"type": "i64", "value": 0}}, + {"op": "const", "dst": 2, "value": {"type": "i64", "value": LIMIT_PLACEHOLDER}}, + {"op": "jump", "target": 1} + ] + }, + { + "id": 1, + "instructions": [ + {"op": "phi", "dst": 6, "incoming": [[2, 0], [6, 2]]}, + {"op": "phi", "dst": 3, "incoming": [[1, 0], [5, 2]]}, + {"op": "compare", "operation": "<", "lhs": 3, "rhs": 6, "dst": 4}, + {"op": "branch", "cond": 4, "then": 2, "else": 3} + ] + }, + { + "id": 2, + "instructions": [ + {"op": "const", "dst": 10, "value": {"type": "i64", "value": 1}}, + {"op": "binop", "operation": "+", "lhs": 3, "rhs": 10, "dst": 5}, + {"op": "jump", "target": 1} + ] + }, + { + "id": 3, + "instructions": [ + {"op": "ret", "value": 3} + ] + } + ] + }] +} +MIRJSON + + # Replace LIMIT_PLACEHOLDER with actual limit + sed -i "s/LIMIT_PLACEHOLDER/$limit/g" "$out_path" + + if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then + echo "[selfhost-direct:ok] Direct MIR assembly (FORCE=1), limit=$limit" >&2 + fi + + return 0 + fi + # Builder box selection (default: hako.mir.builder) local builder_box="${HAKO_MIR_BUILDER_BOX:-hako.mir.builder}" diff --git a/tools/ny_mir_builder.sh b/tools/ny_mir_builder.sh index ac799e5f..daefa1bc 100644 --- a/tools/ny_mir_builder.sh +++ b/tools/ny_mir_builder.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # ny_mir_builder.sh — Minimal MIR Builder CLI (shell wrapper) -# Purpose: consume Nyash JSON IR and emit {obj|exe|ll|json} using the existing nyash LLVM harness. +# Purpose: consume Nyash JSON IR and emit {obj|exe|ll|json} via the ny-llvmc crate backend by default. +# Notes: llvmlite harness remains internal; choose it explicitly with NYASH_LLVM_BACKEND=llvmlite when debugging. set -euo pipefail [[ "${NYASH_CLI_VERBOSE:-0}" == "1" ]] && set -x