📚 Phase 15計画を詳細化・更新: Python/llvmlite正式採用とプラグイン全方向ビルド戦略

 主な更新内容:
- Python/llvmlite実装の正式採用を明記(開発速度10倍、~2400行)
- プラグイン全方向ビルド戦略(.so/.o/.a同時生成)で単一EXE生成可能に
- 各実装の予想コード量を具体化(パーサー800行、MIR Builder 2500行、VM 5000行)
- 循環依存問題の解決を明記(nyrtがC ABI経由で提供)
- 現実的なスケジュール調整(2025年9月~2026年3月)

🎉 最新進捗:
- dep_tree_min_string.nyashオブジェクト生成成功(10.4KB)
- LLVM verifier green - dominance違反解決
- Resolver patternでSSA安全性確保

🚀 次のマイルストーン:
- Python/llvmliteでEXE生成パイプライン完成
- nyash-llvm-compiler分離設計
- NyashパーサーMVP実装開始

Everything is Boxの究極形が、ついに実現へ!

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Selfhosting Dev
2025-09-13 15:37:58 +09:00
parent 8e4f6d774d
commit 1d6fab4eda
44 changed files with 1653 additions and 598 deletions

View File

@ -47,7 +47,11 @@ def lower_atomic_op(
val_vid: Optional[int],
dst_vid: Optional[int],
vmap: Dict[int, ir.Value],
ordering: str = "seq_cst"
ordering: str = "seq_cst",
resolver=None,
preds=None,
block_end_values=None,
bb_map=None
) -> None:
"""
Lower atomic operations
@ -62,7 +66,10 @@ def lower_atomic_op(
ordering: Memory ordering
"""
# Get pointer
ptr = vmap.get(ptr_vid)
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
ptr = resolver.resolve_ptr(ptr_vid, builder.block, preds, block_end_values, vmap)
else:
ptr = vmap.get(ptr_vid)
if not ptr:
# Create dummy pointer
i64 = ir.IntType(64)
@ -78,13 +85,19 @@ def lower_atomic_op(
elif op == "store":
# Atomic store
if val_vid is not None:
val = vmap.get(val_vid, ir.Constant(ir.IntType(64), 0))
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
val = resolver.resolve_i64(val_vid, builder.block, preds, block_end_values, vmap, bb_map)
else:
val = vmap.get(val_vid, ir.Constant(ir.IntType(64), 0))
builder.store_atomic(val, ptr, ordering=ordering, align=8)
elif op == "add":
# Atomic add (fetch_add)
if val_vid is not None:
val = vmap.get(val_vid, ir.Constant(ir.IntType(64), 1))
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
val = resolver.resolve_i64(val_vid, builder.block, preds, block_end_values, vmap, bb_map)
else:
val = ir.Constant(ir.IntType(64), 1)
result = builder.atomic_rmw("add", ptr, val, ordering=ordering)
if dst_vid is not None:
vmap[dst_vid] = result
@ -114,4 +127,4 @@ def insert_thread_fence(
elif fence_type == "write":
builder.fence("release")
else:
builder.fence("seq_cst")
builder.fence("seq_cst")

View File

@ -5,6 +5,8 @@ Handles +, -, *, /, %, &, |, ^, <<, >>
import llvmlite.ir as ir
from typing import Dict
from .compare import lower_compare
import llvmlite.ir as ir
def lower_binop(
builder: ir.IRBuilder,
@ -14,7 +16,10 @@ def lower_binop(
rhs: int,
dst: int,
vmap: Dict[int, ir.Value],
current_block: ir.Block
current_block: ir.Block,
preds=None,
block_end_values=None,
bb_map=None
) -> None:
"""
Lower MIR BinOp instruction
@ -31,18 +36,72 @@ def lower_binop(
"""
# Resolve operands as i64 (using resolver when available)
# For now, simple vmap lookup
lhs_val = vmap.get(lhs, ir.Constant(ir.IntType(64), 0))
rhs_val = vmap.get(rhs, ir.Constant(ir.IntType(64), 0))
if resolver is not None and preds is not None and block_end_values is not None:
lhs_val = resolver.resolve_i64(lhs, current_block, preds, block_end_values, vmap, bb_map)
rhs_val = resolver.resolve_i64(rhs, current_block, preds, block_end_values, vmap, bb_map)
else:
lhs_val = vmap.get(lhs, ir.Constant(ir.IntType(64), 0))
rhs_val = vmap.get(rhs, ir.Constant(ir.IntType(64), 0))
# Relational/equality operators delegate to compare
if op in ('==','!=','<','>','<=','>='):
lower_compare(builder, op, lhs, rhs, dst, vmap)
return
# String-aware concatenation unified to handles (i64) when any side is pointer string
if op == '+':
i64 = ir.IntType(64)
i8p = ir.IntType(8).as_pointer()
lhs_raw = vmap.get(lhs)
rhs_raw = vmap.get(rhs)
is_str = (hasattr(lhs_raw, 'type') and isinstance(lhs_raw.type, ir.PointerType)) or \
(hasattr(rhs_raw, 'type') and isinstance(rhs_raw.type, ir.PointerType))
if is_str:
# Helper: convert raw or resolved value to string handle
def to_handle(raw, val, tag: str):
if raw is not None and hasattr(raw, 'type') and isinstance(raw.type, ir.PointerType):
# pointer-to-array -> GEP
try:
if isinstance(raw.type.pointee, ir.ArrayType):
c0 = ir.Constant(ir.IntType(32), 0)
raw = builder.gep(raw, [c0, c0], name=f"bin_gep_{tag}_{dst}")
except Exception:
pass
cal = None
for f in builder.module.functions:
if f.name == 'nyash.box.from_i8_string':
cal = f; break
if cal is None:
cal = ir.Function(builder.module, ir.FunctionType(i64, [i8p]), name='nyash.box.from_i8_string')
return builder.call(cal, [raw], name=f"str_ptr2h_{tag}_{dst}")
# if already i64
if val is not None and hasattr(val, 'type') and isinstance(val.type, ir.IntType) and val.type.width == 64:
return val
return ir.Constant(i64, 0)
hl = to_handle(lhs_raw, lhs_val, 'l')
hr = to_handle(rhs_raw, rhs_val, 'r')
# concat_hh(handle, handle) -> handle
hh_fnty = ir.FunctionType(i64, [i64, i64])
callee = None
for f in builder.module.functions:
if f.name == 'nyash.string.concat_hh':
callee = f; break
if callee is None:
callee = ir.Function(builder.module, hh_fnty, name='nyash.string.concat_hh')
res = builder.call(callee, [hl, hr], name=f"concat_hh_{dst}")
vmap[dst] = res
return
# Ensure both are i64
i64 = ir.IntType(64)
if hasattr(lhs_val, 'type') and lhs_val.type != i64:
# Type conversion if needed
if lhs_val.type.is_pointer:
lhs_val = builder.ptrtoint(lhs_val, i64)
lhs_val = builder.ptrtoint(lhs_val, i64, name=f"binop_lhs_p2i_{dst}")
if hasattr(rhs_val, 'type') and rhs_val.type != i64:
if rhs_val.type.is_pointer:
rhs_val = builder.ptrtoint(rhs_val, i64)
rhs_val = builder.ptrtoint(rhs_val, i64, name=f"binop_rhs_p2i_{dst}")
# Perform operation
if op == '+':
@ -73,4 +132,4 @@ def lower_binop(
result = ir.Constant(i64, 0)
# Store result
vmap[dst] = result
vmap[dst] = result

View File

@ -6,6 +6,36 @@ Core of Nyash's "Everything is Box" philosophy
import llvmlite.ir as ir
from typing import Dict, List, Optional
def _declare(module: ir.Module, name: str, ret, args):
for f in module.functions:
if f.name == name:
return f
fnty = ir.FunctionType(ret, args)
return ir.Function(module, fnty, name=name)
def _ensure_handle(builder: ir.IRBuilder, module: ir.Module, v: ir.Value) -> ir.Value:
"""Coerce a value to i64 handle. If pointer, box via nyash.box.from_i8_string."""
i64 = ir.IntType(64)
if hasattr(v, 'type'):
if isinstance(v.type, ir.IntType) and v.type.width == 64:
return v
if isinstance(v.type, ir.PointerType):
# call nyash.box.from_i8_string(i8*) -> i64
i8p = ir.IntType(8).as_pointer()
# If pointer-to-array, GEP to first element
try:
if isinstance(v.type.pointee, ir.ArrayType):
c0 = ir.IntType(32)(0)
v = builder.gep(v, [c0, c0], name="bc_str_gep")
except Exception:
pass
callee = _declare(module, "nyash.box.from_i8_string", i64, [i8p])
return builder.call(callee, [v], name="str_ptr2h")
if isinstance(v.type, ir.IntType):
# extend/trunc to i64
return builder.zext(v, i64) if v.type.width < 64 else builder.trunc(v, i64)
return ir.Constant(i64, 0)
def lower_boxcall(
builder: ir.IRBuilder,
module: ir.Module,
@ -14,7 +44,10 @@ def lower_boxcall(
args: List[int],
dst_vid: Optional[int],
vmap: Dict[int, ir.Value],
resolver=None
resolver=None,
preds=None,
block_end_values=None,
bb_map=None
) -> None:
"""
Lower MIR BoxCall instruction
@ -31,74 +64,153 @@ def lower_boxcall(
vmap: Value map
resolver: Optional resolver for type handling
"""
# Get box handle (i64)
box_handle = vmap.get(box_vid, ir.Constant(ir.IntType(64), 0))
# Ensure handle is i64
if hasattr(box_handle, 'type') and box_handle.type.is_pointer:
box_handle = builder.ptrtoint(box_handle, ir.IntType(64))
# Method ID dispatch for plugin boxes
# This matches the current LLVM backend approach
method_id = hash(method_name) & 0xFFFF # Simple hash for demo
# Look up or create ny_boxcall_by_id function
boxcall_func = None
for f in module.functions:
if f.name == "ny_boxcall_by_id":
boxcall_func = f
break
if not boxcall_func:
# Declare ny_boxcall_by_id(handle: i64, method_id: i64, args: i8*) -> i64
i8 = ir.IntType(8)
i64 = ir.IntType(64)
i8_ptr = i8.as_pointer()
func_type = ir.FunctionType(i64, [i64, i64, i8_ptr])
boxcall_func = ir.Function(module, func_type, name="ny_boxcall_by_id")
# Prepare arguments array
i8 = ir.IntType(8)
i64 = ir.IntType(64)
if args:
# Allocate space for arguments (8 bytes per arg)
args_size = len(args) * 8
args_ptr = builder.alloca(i8, size=args_size, name="boxcall_args")
# Cast to i64* for storing arguments
i64_ptr_type = i64.as_pointer()
args_i64_ptr = builder.bitcast(args_ptr, i64_ptr_type)
# Store each argument
for i, arg_id in enumerate(args):
arg_val = vmap.get(arg_id, ir.Constant(i64, 0))
# Ensure i64
if hasattr(arg_val, 'type'):
if arg_val.type.is_pointer:
arg_val = builder.ptrtoint(arg_val, i64)
elif arg_val.type != i64:
# TODO: Handle other conversions
pass
# Calculate offset and store
idx = ir.Constant(ir.IntType(32), i)
ptr = builder.gep(args_i64_ptr, [idx])
builder.store(arg_val, ptr)
# Cast back to i8* for call
call_args_ptr = builder.bitcast(args_i64_ptr, i8.as_pointer())
i8 = ir.IntType(8)
i8p = i8.as_pointer()
# Receiver value
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
recv_val = resolver.resolve_i64(box_vid, builder.block, preds, block_end_values, vmap, bb_map)
else:
# No arguments - pass null
call_args_ptr = ir.Constant(i8.as_pointer(), None)
# Make the boxcall
method_id_val = ir.Constant(i64, method_id)
result = builder.call(boxcall_func, [box_handle, method_id_val, call_args_ptr],
name=f"boxcall_{method_name}")
# Store result if needed
recv_val = vmap.get(box_vid, ir.Constant(i64, 0))
# Minimal method bridging for strings and console
if method_name in ("length", "len"):
# Prefer handle-based len_h
recv_h = _ensure_handle(builder, module, recv_val)
callee = _declare(module, "nyash.string.len_h", i64, [i64])
result = builder.call(callee, [recv_h], name="strlen_h")
if dst_vid is not None:
vmap[dst_vid] = result
return
if method_name == "substring":
# substring(start, end) with pointer-based API
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
s = resolver.resolve_i64(args[0], builder.block, preds, block_end_values, vmap, bb_map) if args else ir.Constant(i64, 0)
e = resolver.resolve_i64(args[1], builder.block, preds, block_end_values, vmap, bb_map) if len(args) > 1 else ir.Constant(i64, 0)
else:
s = vmap.get(args[0], ir.Constant(i64, 0)) if args else ir.Constant(i64, 0)
e = vmap.get(args[1], ir.Constant(i64, 0)) if len(args) > 1 else ir.Constant(i64, 0)
# Coerce recv to i8*
recv_p = recv_val
if hasattr(recv_p, 'type') and isinstance(recv_p.type, ir.IntType):
recv_p = builder.inttoptr(recv_p, i8p, name="bc_i2p_recv")
elif hasattr(recv_p, 'type') and isinstance(recv_p.type, ir.PointerType):
try:
if isinstance(recv_p.type.pointee, ir.ArrayType):
c0 = ir.Constant(ir.IntType(32), 0)
recv_p = builder.gep(recv_p, [c0, c0], name="bc_gep_recv")
except Exception:
pass
else:
recv_p = ir.Constant(i8p, None)
# Coerce indices
if hasattr(s, 'type') and isinstance(s.type, ir.PointerType):
s = builder.ptrtoint(s, i64)
if hasattr(e, 'type') and isinstance(e.type, ir.PointerType):
e = builder.ptrtoint(e, i64)
callee = _declare(module, "nyash.string.substring_sii", i8p, [i8p, i64, i64])
p = builder.call(callee, [recv_p, s, e], name="substring")
# Return as handle across blocks (i8* -> i64 via nyash.box.from_i8_string)
conv_fnty = ir.FunctionType(i64, [i8p])
conv = _declare(module, "nyash.box.from_i8_string", i64, [i8p])
h = builder.call(conv, [p], name="str_ptr2h_sub")
if dst_vid is not None:
vmap[dst_vid] = h
return
if method_name == "lastIndexOf":
# lastIndexOf(needle)
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
needle = resolver.resolve_ptr(args[0], builder.block, preds, block_end_values, vmap) if args else ir.Constant(i8p, None)
else:
needle = vmap.get(args[0], ir.Constant(i8p, None)) if args else ir.Constant(i8p, None)
recv_p = recv_val
if hasattr(recv_p, 'type') and isinstance(recv_p.type, ir.IntType):
recv_p = builder.inttoptr(recv_p, i8p, name="bc_i2p_recv2")
elif hasattr(recv_p, 'type') and isinstance(recv_p.type, ir.PointerType):
try:
if isinstance(recv_p.type.pointee, ir.ArrayType):
c0 = ir.Constant(ir.IntType(32), 0)
recv_p = builder.gep(recv_p, [c0, c0], name="bc_gep_recv2")
except Exception:
pass
if hasattr(needle, 'type') and isinstance(needle.type, ir.IntType):
needle = builder.inttoptr(needle, i8p, name="bc_i2p_needle")
elif hasattr(needle, 'type') and isinstance(needle.type, ir.PointerType):
try:
if isinstance(needle.type.pointee, ir.ArrayType):
c0 = ir.Constant(ir.IntType(32), 0)
needle = builder.gep(needle, [c0, c0], name="bc_gep_needle")
except Exception:
pass
elif not hasattr(needle, 'type'):
needle = ir.Constant(i8p, None)
callee = _declare(module, "nyash.string.lastIndexOf_ss", i64, [i8p, i8p])
res = builder.call(callee, [recv_p, needle], name="lastIndexOf")
if dst_vid is not None:
vmap[dst_vid] = res
return
if method_name in ("print", "println", "log"):
# Console mapping
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
arg0 = resolver.resolve_i64(args[0], builder.block, preds, block_end_values, vmap, bb_map) if args else None
else:
arg0 = vmap.get(args[0]) if args else None
if arg0 is None:
arg0 = ir.Constant(i8p, None)
# Prefer handle API if arg is i64, else pointer API
if hasattr(arg0, 'type') and isinstance(arg0.type, ir.IntType) and arg0.type.width == 64:
callee = _declare(module, "nyash.console.log_handle", i64, [i64])
_ = builder.call(callee, [arg0], name="console_log_h")
else:
if hasattr(arg0, 'type') and isinstance(arg0.type, ir.IntType):
arg0 = builder.inttoptr(arg0, i8p)
callee = _declare(module, "nyash.console.log", i64, [i8p])
_ = builder.call(callee, [arg0], name="console_log")
if dst_vid is not None:
vmap[dst_vid] = ir.Constant(i64, 0)
return
# Default: invoke via NyRT by-name shim (runtime resolves method id)
recv_h = _ensure_handle(builder, module, recv_val)
# Build C string for method name
mbytes = (method_name + "\0").encode('utf-8')
arr_ty = ir.ArrayType(ir.IntType(8), len(mbytes))
try:
fn = builder.block.parent
fn_name = getattr(fn, 'name', 'fn')
except Exception:
fn_name = 'fn'
base = f".meth_{fn_name}_{method_name}"
existing = {g.name for g in module.global_values}
gname = base
k = 1
while gname in existing:
gname = f"{base}.{k}"; k += 1
g = ir.GlobalVariable(module, arr_ty, name=gname)
g.linkage = 'private'
g.global_constant = True
g.initializer = ir.Constant(arr_ty, bytearray(mbytes))
c0 = ir.Constant(ir.IntType(32), 0)
mptr = builder.gep(g, [c0, c0], inbounds=True)
# Up to 2 args for minimal path
argc = ir.Constant(i64, min(len(args), 2))
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
a1 = resolver.resolve_i64(args[0], builder.block, preds, block_end_values, vmap, bb_map) if len(args) >= 1 else ir.Constant(i64, 0)
a2 = resolver.resolve_i64(args[1], builder.block, preds, block_end_values, vmap, bb_map) if len(args) >= 2 else ir.Constant(i64, 0)
else:
a1 = vmap.get(args[0], ir.Constant(i64, 0)) if len(args) >= 1 else ir.Constant(i64, 0)
a2 = vmap.get(args[1], ir.Constant(i64, 0)) if len(args) >= 2 else ir.Constant(i64, 0)
if hasattr(a1, 'type') and isinstance(a1.type, ir.PointerType):
a1 = builder.ptrtoint(a1, i64)
if hasattr(a2, 'type') and isinstance(a2.type, ir.PointerType):
a2 = builder.ptrtoint(a2, i64)
callee = _declare(module, "nyash.plugin.invoke_by_name_i64", i64, [i64, i8p, i64, i64, i64])
result = builder.call(callee, [recv_h, mptr, argc, a1, a2], name="pinvoke_by_name")
if dst_vid is not None:
vmap[dst_vid] = result
vmap[dst_vid] = result

View File

@ -12,7 +12,10 @@ def lower_branch(
then_bid: int,
else_bid: int,
vmap: Dict[int, ir.Value],
bb_map: Dict[int, ir.Block]
bb_map: Dict[int, ir.Block],
resolver=None,
preds=None,
block_end_values=None
) -> None:
"""
Lower MIR Branch instruction
@ -26,7 +29,10 @@ def lower_branch(
bb_map: Block map
"""
# Get condition value
cond = vmap.get(cond_vid)
if resolver is not None and preds is not None and block_end_values is not None:
cond = resolver.resolve_i64(cond_vid, builder.block, preds, block_end_values, vmap, bb_map)
else:
cond = vmap.get(cond_vid)
if not cond:
# Default to false if missing
cond = ir.Constant(ir.IntType(1), 0)
@ -47,4 +53,4 @@ def lower_branch(
else_bb = bb_map.get(else_bid)
if then_bb and else_bb:
builder.cbranch(cond, then_bb, else_bb)
builder.cbranch(cond, then_bb, else_bb)

View File

@ -13,7 +13,10 @@ def lower_call(
args: List[int],
dst_vid: Optional[int],
vmap: Dict[int, ir.Value],
resolver=None
resolver=None,
preds=None,
block_end_values=None,
bb_map=None
) -> None:
"""
Lower MIR Call instruction
@ -27,49 +30,59 @@ def lower_call(
vmap: Value map
resolver: Optional resolver for type handling
"""
# Resolve function: accepts string name or value-id referencing a string literal
actual_name = func_name
if not isinstance(func_name, str):
# Try resolver.string_literals
if resolver is not None and hasattr(resolver, 'string_literals'):
actual_name = resolver.string_literals.get(func_name)
# Look up function in module
func = None
for f in module.functions:
if f.name == func_name:
func = f
break
if isinstance(actual_name, str):
for f in module.functions:
if f.name == actual_name:
func = f
break
if not func:
# Function not found - create declaration
# Default: i64(i64, ...) signature
# Function not found - create declaration with default i64 signature
ret_type = ir.IntType(64)
arg_types = [ir.IntType(64)] * len(args)
name = actual_name if isinstance(actual_name, str) else "unknown_fn"
func_type = ir.FunctionType(ret_type, arg_types)
func = ir.Function(module, func_type, name=func_name)
func = ir.Function(module, func_type, name=name)
# Prepare arguments
call_args = []
for i, arg_id in enumerate(args):
arg_val = vmap.get(arg_id)
if not arg_val:
# Default based on expected type
arg_val = None
if i < len(func.args):
expected_type = func.args[i].type
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
if hasattr(expected_type, 'is_pointer') and expected_type.is_pointer:
arg_val = resolver.resolve_ptr(arg_id, builder.block, preds, block_end_values, vmap)
else:
arg_val = resolver.resolve_i64(arg_id, builder.block, preds, block_end_values, vmap, bb_map)
if arg_val is None:
arg_val = vmap.get(arg_id)
if arg_val is None:
if i < len(func.args):
expected_type = func.args[i].type
else:
expected_type = ir.IntType(64)
if isinstance(expected_type, ir.IntType):
arg_val = ir.Constant(expected_type, 0)
elif isinstance(expected_type, ir.DoubleType):
arg_val = ir.Constant(expected_type, 0.0)
else:
arg_val = ir.Constant(expected_type, None)
# Type conversion if needed
if i < len(func.args):
expected_type = func.args[i].type
if hasattr(arg_val, 'type') and arg_val.type != expected_type:
if expected_type.is_pointer and isinstance(arg_val.type, ir.IntType):
arg_val = builder.inttoptr(arg_val, expected_type)
arg_val = builder.inttoptr(arg_val, expected_type, name=f"call_i2p_{i}")
elif isinstance(expected_type, ir.IntType) and arg_val.type.is_pointer:
arg_val = builder.ptrtoint(arg_val, expected_type)
arg_val = builder.ptrtoint(arg_val, expected_type, name=f"call_p2i_{i}")
call_args.append(arg_val)
# Make the call
@ -77,4 +90,4 @@ def lower_call(
# Store result if needed
if dst_vid is not None:
vmap[dst_vid] = result
vmap[dst_vid] = result

View File

@ -5,6 +5,7 @@ Handles comparison operations (<, >, <=, >=, ==, !=)
import llvmlite.ir as ir
from typing import Dict
from .externcall import lower_externcall
def lower_compare(
builder: ir.IRBuilder,
@ -12,7 +13,12 @@ def lower_compare(
lhs: int,
rhs: int,
dst: int,
vmap: Dict[int, ir.Value]
vmap: Dict[int, ir.Value],
resolver=None,
current_block=None,
preds=None,
block_end_values=None,
bb_map=None
) -> None:
"""
Lower MIR Compare instruction
@ -26,29 +32,62 @@ def lower_compare(
vmap: Value map
"""
# Get operands
lhs_val = vmap.get(lhs, ir.Constant(ir.IntType(64), 0))
rhs_val = vmap.get(rhs, ir.Constant(ir.IntType(64), 0))
# Ensure both are i64
if resolver is not None and preds is not None and block_end_values is not None and current_block is not None:
lhs_val = resolver.resolve_i64(lhs, current_block, preds, block_end_values, vmap, bb_map)
rhs_val = resolver.resolve_i64(rhs, current_block, preds, block_end_values, vmap, bb_map)
else:
lhs_val = vmap.get(lhs)
rhs_val = vmap.get(rhs)
i64 = ir.IntType(64)
if hasattr(lhs_val, 'type') and lhs_val.type.is_pointer:
i8p = ir.IntType(8).as_pointer()
# String-aware equality: if both are pointers, assume i8* strings
if op in ('==','!=') and hasattr(lhs_val, 'type') and hasattr(rhs_val, 'type'):
if isinstance(lhs_val.type, ir.PointerType) and isinstance(rhs_val.type, ir.PointerType):
# Box both to handles and call nyash.string.eq_hh
# nyash.box.from_i8_string(i8*) -> i64
box_from = None
for f in builder.module.functions:
if f.name == 'nyash.box.from_i8_string':
box_from = f
break
if not box_from:
box_from = ir.Function(builder.module, ir.FunctionType(i64, [i8p]), name='nyash.box.from_i8_string')
lh = builder.call(box_from, [lhs_val], name='lhs_ptr2h')
rh = builder.call(box_from, [rhs_val], name='rhs_ptr2h')
eqf = None
for f in builder.module.functions:
if f.name == 'nyash.string.eq_hh':
eqf = f
break
if not eqf:
eqf = ir.Function(builder.module, ir.FunctionType(i64, [i64, i64]), name='nyash.string.eq_hh')
eq = builder.call(eqf, [lh, rh], name='str_eq')
if op == '==':
vmap[dst] = eq
else:
# ne = 1 - eq
one = ir.Constant(i64, 1)
ne = builder.sub(one, eq, name='str_ne')
vmap[dst] = ne
return
# Default integer compare path
if lhs_val is None:
lhs_val = ir.Constant(i64, 0)
if rhs_val is None:
rhs_val = ir.Constant(i64, 0)
# Ensure both are i64
if hasattr(lhs_val, 'type') and isinstance(lhs_val.type, ir.PointerType):
lhs_val = builder.ptrtoint(lhs_val, i64)
if hasattr(rhs_val, 'type') and rhs_val.type.is_pointer:
if hasattr(rhs_val, 'type') and isinstance(rhs_val.type, ir.PointerType):
rhs_val = builder.ptrtoint(rhs_val, i64)
# Map operations to LLVM predicates
op_map = {
'<': 'slt', # signed less than
'>': 'sgt', # signed greater than
'<=': 'sle', # signed less or equal
'>=': 'sge', # signed greater or equal
'==': 'eq', # equal
'!=': 'ne' # not equal
}
pred = op_map.get(op, 'eq')
# Perform comparison (returns i1)
# Perform signed comparison using canonical predicates ('<','>','<=','>=','==','!=')
pred = op if op in ('<','>','<=','>=','==','!=') else '=='
cmp_result = builder.icmp_signed(pred, lhs_val, rhs_val, name=f"cmp_{dst}")
# Convert i1 to i64 (0 or 1)
@ -81,19 +120,8 @@ def lower_fcmp(
lhs_val = vmap.get(lhs, ir.Constant(f64, 0.0))
rhs_val = vmap.get(rhs, ir.Constant(f64, 0.0))
# Map operations to LLVM predicates
op_map = {
'<': 'olt', # ordered less than
'>': 'ogt', # ordered greater than
'<=': 'ole', # ordered less or equal
'>=': 'oge', # ordered greater or equal
'==': 'oeq', # ordered equal
'!=': 'one' # ordered not equal
}
pred = op_map.get(op, 'oeq')
# Perform comparison (returns i1)
# Perform ordered comparison using canonical predicates
pred = op if op in ('<','>','<=','>=','==','!=') else '=='
cmp_result = builder.fcmp_ordered(pred, lhs_val, rhs_val, name=f"fcmp_{dst}")
# Convert i1 to i64
@ -101,4 +129,4 @@ def lower_fcmp(
result = builder.zext(cmp_result, i64, name=f"fcmp_i64_{dst}")
# Store result
vmap[dst] = result
vmap[dst] = result

View File

@ -11,7 +11,8 @@ def lower_const(
module: ir.Module,
dst: int,
value: Dict[str, Any],
vmap: Dict[int, ir.Value]
vmap: Dict[int, ir.Value],
resolver=None
) -> None:
"""
Lower MIR Const instruction
@ -39,25 +40,31 @@ def lower_const(
vmap[dst] = llvm_val
elif const_type == 'string':
# String constant - create global and get pointer
# String constant - create global, store GlobalVariable (not GEP) to avoid dominance issues
i8 = ir.IntType(8)
str_val = str(const_val)
# Create array constant for the string
str_bytes = str_val.encode('utf-8') + b'\0'
str_const = ir.Constant(ir.ArrayType(i8, len(str_bytes)),
bytearray(str_bytes))
# Create global string constant
global_name = f".str.{dst}"
global_str = ir.GlobalVariable(module, str_const.type, name=global_name)
global_str.initializer = str_const
global_str.linkage = 'private'
global_str.global_constant = True
# Get pointer to first element
indices = [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), 0)]
ptr = builder.gep(global_str, indices, name=f"str_ptr_{dst}")
vmap[dst] = ptr
arr_ty = ir.ArrayType(i8, len(str_bytes))
str_const = ir.Constant(arr_ty, bytearray(str_bytes))
try:
fn = builder.block.parent
fn_name = getattr(fn, 'name', 'fn')
except Exception:
fn_name = 'fn'
base = f".str.{fn_name}.{dst}"
existing = {g.name for g in module.global_values}
name = base
n = 1
while name in existing:
name = f"{base}.{n}"; n += 1
g = ir.GlobalVariable(module, arr_ty, name=name)
g.initializer = str_const
g.linkage = 'private'
g.global_constant = True
# Store the GlobalVariable; resolver.resolve_ptr will emit GEP in the current block
vmap[dst] = g
if resolver is not None and hasattr(resolver, 'string_literals'):
resolver.string_literals[dst] = str_val
elif const_type == 'void':
# Void/null constant - use i64 zero
@ -67,4 +74,4 @@ def lower_const(
else:
# Unknown type - default to i64 zero
i64 = ir.IntType(64)
vmap[dst] = ir.Constant(i64, 0)
vmap[dst] = ir.Constant(i64, 0)

View File

@ -1,40 +1,11 @@
"""
ExternCall instruction lowering
Handles the minimal 5 runtime functions: print, error, panic, exit, now
Minimal mapping for NyRT-exported symbols (console/log family等)
"""
import llvmlite.ir as ir
from typing import Dict, List, Optional
# The 5 minimal external functions
EXTERN_FUNCS = {
"print": {
"ret": "void",
"args": ["i8*"], # String pointer
"llvm_name": "ny_print"
},
"error": {
"ret": "void",
"args": ["i8*"], # Error message
"llvm_name": "ny_error"
},
"panic": {
"ret": "void",
"args": ["i8*"], # Panic message
"llvm_name": "ny_panic"
},
"exit": {
"ret": "void",
"args": ["i64"], # Exit code
"llvm_name": "ny_exit"
},
"now": {
"ret": "i64",
"args": [], # No arguments
"llvm_name": "ny_now"
}
}
def lower_externcall(
builder: ir.IRBuilder,
module: ir.Module,
@ -42,7 +13,10 @@ def lower_externcall(
args: List[int],
dst_vid: Optional[int],
vmap: Dict[int, ir.Value],
resolver=None
resolver=None,
preds=None,
block_end_values=None,
bb_map=None
) -> None:
"""
Lower MIR ExternCall instruction
@ -56,94 +30,121 @@ def lower_externcall(
vmap: Value map
resolver: Optional resolver for type handling
"""
if func_name not in EXTERN_FUNCS:
# Unknown extern function - treat as void()
print(f"Warning: Unknown extern function: {func_name}")
return
extern_info = EXTERN_FUNCS[func_name]
llvm_name = extern_info["llvm_name"]
# Look up or declare function
# Accept full symbol names (e.g., "nyash.console.log", "nyash.string.len_h").
llvm_name = func_name
i8 = ir.IntType(8)
i64 = ir.IntType(64)
i8p = i8.as_pointer()
void = ir.VoidType()
# Known NyRT signatures
sig_map = {
# Strings (handle-based)
"nyash.string.len_h": (i64, [i64]),
"nyash.string.charCodeAt_h": (i64, [i64, i64]),
"nyash.string.concat_hh": (i64, [i64, i64]),
"nyash.string.eq_hh": (i64, [i64, i64]),
# Strings (pointer-based plugin functions)
"nyash.string.concat_ss": (i8p, [i8p, i8p]),
"nyash.string.concat_si": (i8p, [i8p, i64]),
"nyash.string.concat_is": (i8p, [i64, i8p]),
"nyash.string.substring_sii": (i8p, [i8p, i64, i64]),
"nyash.string.lastIndexOf_ss": (i64, [i8p, i8p]),
# Boxing helpers
"nyash.box.from_i8_string": (i64, [i8p]),
# Console (string pointer expected)
# Many call sites pass handles or pointers; we coerce below.
}
# Find or declare function with appropriate prototype
func = None
for f in module.functions:
if f.name == llvm_name:
func = f
break
if not func:
# Build function type
i8 = ir.IntType(8)
i64 = ir.IntType(64)
void = ir.VoidType()
# Return type
if extern_info["ret"] == "void":
ret_type = void
elif extern_info["ret"] == "i64":
ret_type = i64
if llvm_name in sig_map:
ret_ty, arg_tys = sig_map[llvm_name]
fnty = ir.FunctionType(ret_ty, arg_tys)
func = ir.Function(module, fnty, name=llvm_name)
elif llvm_name.startswith("nyash.console."):
# console.*: (i8*) -> i64
fnty = ir.FunctionType(i64, [i8p])
func = ir.Function(module, fnty, name=llvm_name)
else:
ret_type = void
# Argument types
arg_types = []
for arg_type_str in extern_info["args"]:
if arg_type_str == "i8*":
arg_types.append(i8.as_pointer())
elif arg_type_str == "i64":
arg_types.append(i64)
func_type = ir.FunctionType(ret_type, arg_types)
func = ir.Function(module, func_type, name=llvm_name)
# Prepare arguments
call_args = []
# Unknown extern: declare as void(...no args...) and call without args
fnty = ir.FunctionType(void, [])
func = ir.Function(module, fnty, name=llvm_name)
# Prepare/coerce arguments
call_args: List[ir.Value] = []
for i, arg_id in enumerate(args):
if i >= len(extern_info["args"]):
break # Too many arguments
expected_type_str = extern_info["args"][i]
arg_val = vmap.get(arg_id)
if not arg_val:
# Default value
if expected_type_str == "i8*":
# Null string
i8 = ir.IntType(8)
arg_val = ir.Constant(i8.as_pointer(), None)
elif expected_type_str == "i64":
arg_val = ir.Constant(ir.IntType(64), 0)
# Type conversion
if expected_type_str == "i8*":
# Need string pointer
if hasattr(arg_val, 'type'):
if isinstance(arg_val.type, ir.IntType):
# int to ptr
i8 = ir.IntType(8)
arg_val = builder.inttoptr(arg_val, i8.as_pointer())
elif not arg_val.type.is_pointer:
# Need pointer type
i8 = ir.IntType(8)
arg_val = ir.Constant(i8.as_pointer(), None)
elif expected_type_str == "i64":
# Need i64
if hasattr(arg_val, 'type'):
if arg_val.type.is_pointer:
arg_val = builder.ptrtoint(arg_val, ir.IntType(64))
elif arg_val.type != ir.IntType(64):
# Convert to i64
pass # TODO: Handle other conversions
call_args.append(arg_val)
# Make the call
if extern_info["ret"] == "void":
builder.call(func, call_args)
if dst_vid is not None:
# Void return - store 0
vmap[dst_vid] = ir.Constant(ir.IntType(64), 0)
else:
# Prefer resolver
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
if len(func.args) > i and isinstance(func.args[i].type, ir.PointerType):
aval = resolver.resolve_ptr(arg_id, builder.block, preds, block_end_values, vmap)
else:
aval = resolver.resolve_i64(arg_id, builder.block, preds, block_end_values, vmap, bb_map)
else:
aval = vmap.get(arg_id)
if aval is None:
# Default guess
aval = ir.Constant(i64, 0)
# If function prototype is known, coerce to expected type
if len(func.args) > i:
expected_ty = func.args[i].type
if isinstance(expected_ty, ir.PointerType):
# Need pointer
if hasattr(aval, 'type'):
if isinstance(aval.type, ir.IntType):
aval = builder.inttoptr(aval, expected_ty, name=f"ext_i2p_arg{i}")
elif not aval.type.is_pointer:
aval = ir.Constant(expected_ty, None)
else:
# Pointer but wrong element type: if pointer-to-array -> GEP to i8*
try:
if isinstance(aval.type.pointee, ir.ArrayType) and isinstance(expected_ty.pointee, ir.IntType) and expected_ty.pointee.width == 8:
c0 = ir.Constant(ir.IntType(32), 0)
aval = builder.gep(aval, [c0, c0], name=f"ext_gep_arg{i}")
except Exception:
pass
else:
aval = ir.Constant(expected_ty, None)
elif isinstance(expected_ty, ir.IntType) and expected_ty.width == 64:
# Need i64
if hasattr(aval, 'type'):
if isinstance(aval.type, ir.PointerType):
aval = builder.ptrtoint(aval, i64, name=f"ext_p2i_arg{i}")
elif isinstance(aval.type, ir.IntType) and aval.type.width != 64:
# extend/trunc
if aval.type.width < 64:
aval = builder.zext(aval, i64, name=f"ext_zext_{i}")
else:
aval = builder.trunc(aval, i64, name=f"ext_trunc_{i}")
else:
aval = ir.Constant(i64, 0)
else:
# Prototype shorter than args: best-effort pointer->i64 for string-ish APIs
if hasattr(aval, 'type') and isinstance(aval.type, ir.PointerType):
aval = builder.ptrtoint(aval, i64, name=f"ext_p2i_arg{i}")
call_args.append(aval)
# Truncate extra args if prototype shorter
if len(call_args) > len(func.args):
call_args = call_args[:len(func.args)]
# Issue the call
if len(call_args) == len(func.args):
result = builder.call(func, call_args, name=f"extern_{func_name}")
if dst_vid is not None:
vmap[dst_vid] = result
else:
result = builder.call(func, call_args[:len(func.args)])
# Materialize result into vmap
if dst_vid is not None:
rty = func.function_type.return_type
if isinstance(rty, ir.VoidType):
vmap[dst_vid] = ir.Constant(i64, 0)
else:
vmap[dst_vid] = result

View File

@ -50,7 +50,10 @@ def lower_while_loopform(
body_instructions: List[Any],
loop_id: int,
vmap: Dict[int, ir.Value],
bb_map: Dict[int, ir.Block]
bb_map: Dict[int, ir.Block],
resolver=None,
preds=None,
block_end_values=None
) -> bool:
"""
Lower a while loop using LoopForm structure
@ -71,7 +74,12 @@ def lower_while_loopform(
# Header: Evaluate condition
builder.position_at_end(lf.header)
cond = vmap.get(condition_vid, ir.Constant(ir.IntType(1), 0))
if resolver is not None and preds is not None and block_end_values is not None:
cond64 = resolver.resolve_i64(condition_vid, builder.block, preds, block_end_values, vmap, bb_map)
zero64 = ir.IntType(64)(0)
cond = builder.icmp_unsigned('!=', cond64, zero64)
else:
cond = vmap.get(condition_vid, ir.Constant(ir.IntType(1), 0))
# Convert to i1 if needed
if hasattr(cond, 'type') and cond.type == ir.IntType(64):
cond = builder.icmp_unsigned('!=', cond, ir.Constant(ir.IntType(64), 0))
@ -118,4 +126,4 @@ def lower_while_loopform(
if os.environ.get('NYASH_CLI_VERBOSE') == '1':
print(f"[LoopForm] Created loop structure (id={loop_id})")
return True
return True

View File

@ -29,60 +29,40 @@ def lower_newbox(
vmap: Value map
resolver: Optional resolver for type handling
"""
# Look up or declare the box creation function
create_func_name = f"ny_create_{box_type}"
create_func = None
# Use NyRT shim: nyash.env.box.new(type_name: i8*) -> i64
i64 = ir.IntType(64)
i8p = ir.IntType(8).as_pointer()
# Prefer variadic shim: nyash.env.box.new_i64x(type_name, argc, a1, a2, a3, a4)
new_i64x = None
for f in module.functions:
if f.name == create_func_name:
create_func = f
if f.name == "nyash.env.box.new_i64x":
new_i64x = f
break
if not create_func:
# Declare box creation function
# Signature depends on box type
i64 = ir.IntType(64)
i8 = ir.IntType(8)
if box_type in ["StringBox", "IntegerBox", "BoolBox"]:
# Built-in boxes - default constructors (no args)
# Real implementation may have optional args
func_type = ir.FunctionType(i64, [])
else:
# Generic box - variable arguments
# For now, assume no args
func_type = ir.FunctionType(i64, [])
create_func = ir.Function(module, func_type, name=create_func_name)
# Prepare arguments
call_args = []
for i, arg_id in enumerate(args):
arg_val = vmap.get(arg_id)
if not arg_val:
# Default based on box type
if box_type == "StringBox":
# Empty string
i8 = ir.IntType(8)
arg_val = ir.Constant(i8.as_pointer(), None)
else:
# Zero
arg_val = ir.Constant(ir.IntType(64), 0)
# Type conversion if needed
if box_type == "StringBox" and hasattr(arg_val, 'type'):
if isinstance(arg_val.type, ir.IntType):
# int to string ptr
i8 = ir.IntType(8)
arg_val = builder.inttoptr(arg_val, i8.as_pointer())
call_args.append(arg_val)
# Create the box
handle = builder.call(create_func, call_args, name=f"new_{box_type}")
# Store handle
if not new_i64x:
new_i64x = ir.Function(module, ir.FunctionType(i64, [i8p, i64, i64, i64, i64, i64]), name="nyash.env.box.new_i64x")
# Build C-string for type name (unique global per function)
sbytes = (box_type + "\0").encode('utf-8')
arr_ty = ir.ArrayType(ir.IntType(8), len(sbytes))
try:
fn = builder.block.parent
fn_name = getattr(fn, 'name', 'fn')
except Exception:
fn_name = 'fn'
base = f".box_ty_{fn_name}_{dst_vid}"
existing = {g.name for g in module.global_values}
name = base
n = 1
while name in existing:
name = f"{base}.{n}"; n += 1
g = ir.GlobalVariable(module, arr_ty, name=name)
g.linkage = 'private'
g.global_constant = True
g.initializer = ir.Constant(arr_ty, bytearray(sbytes))
c0 = ir.Constant(ir.IntType(32), 0)
ptr = builder.gep(g, [c0, c0], inbounds=True)
zero = ir.Constant(i64, 0)
handle = builder.call(new_i64x, [ptr, zero, zero, zero, zero, zero], name=f"new_{box_type}")
vmap[dst_vid] = handle
def lower_newbox_generic(
@ -113,4 +93,4 @@ def lower_newbox_generic(
size = ir.Constant(ir.IntType(64), 64)
handle = builder.call(alloc_func, [size], name="new_box")
vmap[dst_vid] = handle
vmap[dst_vid] = handle

View File

@ -13,7 +13,9 @@ def lower_phi(
vmap: Dict[int, ir.Value],
bb_map: Dict[int, ir.Block],
current_block: ir.Block,
resolver=None # Resolver instance (optional)
resolver=None, # Resolver instance (optional)
block_end_values: Optional[Dict[int, Dict[int, ir.Value]]] = None,
preds_map: Optional[Dict[int, List[int]]] = None
) -> None:
"""
Lower MIR PHI instruction
@ -32,23 +34,50 @@ def lower_phi(
vmap[dst_vid] = ir.Constant(ir.IntType(64), 0)
return
# Determine PHI type from first incoming value
first_val_id = incoming[0][0]
first_val = vmap.get(first_val_id)
if first_val and hasattr(first_val, 'type'):
phi_type = first_val.type
else:
# Default to i64
phi_type = ir.IntType(64)
# Determine PHI type from snapshots or fallback i64
phi_type = ir.IntType(64)
if block_end_values is not None:
for val_id, pred_bid in incoming:
snap = block_end_values.get(pred_bid, {})
val = snap.get(val_id)
if val is not None and hasattr(val, 'type'):
phi_type = val.type
# Prefer pointer type
if hasattr(phi_type, 'is_pointer') and phi_type.is_pointer:
break
# Create PHI instruction
phi = builder.phi(phi_type, name=f"phi_{dst_vid}")
# Add incoming values
# Build map from provided incoming
incoming_map: Dict[int, int] = {}
for val_id, block_id in incoming:
val = vmap.get(val_id)
incoming_map[block_id] = val_id
# Resolve actual predecessor set
cur_bid = None
try:
cur_bid = int(str(current_block.name).replace('bb',''))
except Exception:
pass
actual_preds = []
if preds_map is not None and cur_bid is not None:
actual_preds = [p for p in preds_map.get(cur_bid, []) if p != cur_bid]
else:
# Fallback: use blocks in incoming list
actual_preds = [b for _, b in incoming]
# Add incoming for each actual predecessor
for block_id in actual_preds:
block = bb_map.get(block_id)
# Prefer pred snapshot
if block_end_values is not None:
snap = block_end_values.get(block_id, {})
vid = incoming_map.get(block_id)
val = snap.get(vid) if vid is not None else None
else:
vid = incoming_map.get(block_id)
val = vmap.get(vid) if vid is not None else None
if not val:
# Create default value based on type
@ -66,34 +95,32 @@ def lower_phi(
# Type conversion if needed
if hasattr(val, 'type') and val.type != phi_type:
# Save current position
saved_block = builder.block
saved_pos = None
if hasattr(builder, '_anchor'):
saved_pos = builder._anchor
# Position at end of predecessor block
builder.position_at_end(block)
# Position at end (before terminator) of predecessor block
pb = ir.IRBuilder(block)
try:
term = block.terminator
if term is not None:
pb.position_before(term)
else:
pb.position_at_end(block)
except Exception:
pb.position_at_end(block)
# Convert types
if isinstance(phi_type, ir.IntType) and val.type.is_pointer:
val = builder.ptrtoint(val, phi_type, name=f"cast_p2i_{val_id}")
val = pb.ptrtoint(val, phi_type, name=f"cast_p2i_{val_id}")
elif phi_type.is_pointer and isinstance(val.type, ir.IntType):
val = builder.inttoptr(val, phi_type, name=f"cast_i2p_{val_id}")
val = pb.inttoptr(val, phi_type, name=f"cast_i2p_{val_id}")
elif isinstance(phi_type, ir.IntType) and isinstance(val.type, ir.IntType):
# Int to int
if phi_type.width > val.type.width:
val = builder.zext(val, phi_type, name=f"zext_{val_id}")
val = pb.zext(val, phi_type, name=f"zext_{val_id}")
else:
val = builder.trunc(val, phi_type, name=f"trunc_{val_id}")
# Restore position
builder.position_at_end(saved_block)
if saved_pos and hasattr(builder, '_anchor'):
builder._anchor = saved_pos
val = pb.trunc(val, phi_type, name=f"trunc_{val_id}")
# Add to PHI
phi.add_incoming(val, block)
# Add to PHI (skip if no block)
if block is not None:
phi.add_incoming(val, block)
# Store PHI result
vmap[dst_vid] = phi
@ -111,4 +138,4 @@ def defer_phi_wiring(
incoming: Incoming edges
phi_deferrals: List to store deferred PHIs
"""
phi_deferrals.append((dst_vid, incoming))
phi_deferrals.append((dst_vid, incoming))

View File

@ -10,7 +10,11 @@ def lower_return(
builder: ir.IRBuilder,
value_id: Optional[int],
vmap: Dict[int, ir.Value],
return_type: ir.Type
return_type: ir.Type,
resolver=None,
preds=None,
block_end_values=None,
bb_map=None
) -> None:
"""
Lower MIR Return instruction
@ -25,8 +29,14 @@ def lower_return(
# Void return
builder.ret_void()
else:
# Get return value
ret_val = vmap.get(value_id)
# Get return value (prefer resolver)
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
if isinstance(return_type, ir.PointerType):
ret_val = resolver.resolve_ptr(value_id, builder.block, preds, block_end_values, vmap)
else:
ret_val = resolver.resolve_i64(value_id, builder.block, preds, block_end_values, vmap, bb_map)
else:
ret_val = vmap.get(value_id)
if not ret_val:
# Default based on return type
if isinstance(return_type, ir.IntType):
@ -41,10 +51,10 @@ def lower_return(
if hasattr(ret_val, 'type') and ret_val.type != return_type:
if isinstance(return_type, ir.IntType) and ret_val.type.is_pointer:
# ptr to int
ret_val = builder.ptrtoint(ret_val, return_type)
ret_val = builder.ptrtoint(ret_val, return_type, name="ret_p2i")
elif isinstance(return_type, ir.PointerType) and isinstance(ret_val.type, ir.IntType):
# int to ptr
ret_val = builder.inttoptr(ret_val, return_type)
ret_val = builder.inttoptr(ret_val, return_type, name="ret_i2p")
elif isinstance(return_type, ir.IntType) and isinstance(ret_val.type, ir.IntType):
# int to int conversion
if return_type.width < ret_val.type.width:
@ -54,4 +64,4 @@ def lower_return(
# Zero extend
ret_val = builder.zext(ret_val, return_type)
builder.ret(ret_val)
builder.ret(ret_val)

View File

@ -11,7 +11,11 @@ def lower_safepoint(
module: ir.Module,
live_values: List[int],
vmap: Dict[int, ir.Value],
safepoint_id: Optional[int] = None
safepoint_id: Optional[int] = None,
resolver=None,
preds=None,
block_end_values=None,
bb_map=None
) -> None:
"""
Lower MIR Safepoint instruction
@ -49,11 +53,14 @@ def lower_safepoint(
# Store each live value
for i, vid in enumerate(live_values):
val = vmap.get(vid, ir.Constant(i64, 0))
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
val = resolver.resolve_i64(vid, builder.block, preds, block_end_values, vmap, bb_map)
else:
val = vmap.get(vid, ir.Constant(i64, 0))
# Ensure i64 (handles are i64)
if hasattr(val, 'type') and val.type.is_pointer:
val = builder.ptrtoint(val, i64)
val = builder.ptrtoint(val, i64, name=f"sp_p2i_{vid}")
idx = ir.Constant(ir.IntType(32), i)
ptr = builder.gep(live_array, [idx])
@ -104,4 +111,4 @@ def insert_automatic_safepoint(
check_func = ir.Function(module, func_type, name="ny_check_safepoint")
# Insert safepoint check
builder.call(check_func, [], name=f"safepoint_{location}")
builder.call(check_func, [], name=f"safepoint_{location}")

View File

@ -13,7 +13,10 @@ def lower_typeop(
dst_vid: int,
target_type: Optional[str],
vmap: Dict[int, ir.Value],
resolver=None
resolver=None,
preds=None,
block_end_values=None,
bb_map=None
) -> None:
"""
Lower MIR TypeOp instruction
@ -32,7 +35,10 @@ def lower_typeop(
vmap: Value map
resolver: Optional resolver for type handling
"""
src_val = vmap.get(src_vid, ir.Constant(ir.IntType(64), 0))
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
src_val = resolver.resolve_i64(src_vid, builder.block, preds, block_end_values, vmap, bb_map)
else:
src_val = vmap.get(src_vid, ir.Constant(ir.IntType(64), 0))
if op == "cast":
# Type casting - for now just pass through
@ -73,7 +79,11 @@ def lower_convert(
dst_vid: int,
from_type: str,
to_type: str,
vmap: Dict[int, ir.Value]
vmap: Dict[int, ir.Value],
resolver=None,
preds=None,
block_end_values=None,
bb_map=None
) -> None:
"""
Lower type conversion between primitive types
@ -86,7 +96,14 @@ def lower_convert(
to_type: Target type
vmap: Value map
"""
src_val = vmap.get(src_vid)
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
# Choose resolution based on from_type
if from_type == "ptr":
src_val = resolver.resolve_ptr(src_vid, builder.block, preds, block_end_values, vmap)
else:
src_val = resolver.resolve_i64(src_vid, builder.block, preds, block_end_values, vmap, bb_map)
else:
src_val = vmap.get(src_vid)
if not src_val:
# Default based on target type
if to_type == "f64":
@ -108,10 +125,10 @@ def lower_convert(
elif from_type == "i64" and to_type == "ptr":
# int to pointer
i8 = ir.IntType(8)
result = builder.inttoptr(src_val, i8.as_pointer())
result = builder.inttoptr(src_val, i8.as_pointer(), name=f"conv_i2p_{dst_vid}")
elif from_type == "ptr" and to_type == "i64":
# pointer to int
result = builder.ptrtoint(src_val, ir.IntType(64))
result = builder.ptrtoint(src_val, ir.IntType(64), name=f"conv_p2i_{dst_vid}")
elif from_type == "i32" and to_type == "i64":
# sign extend
result = builder.sext(src_val, ir.IntType(64))
@ -122,4 +139,4 @@ def lower_convert(
# Unknown conversion - pass through
result = src_val
vmap[dst_vid] = result
vmap[dst_vid] = result

View File

@ -14,6 +14,7 @@ import llvmlite.binding as llvm
# Import instruction handlers
from instructions.const import lower_const
from instructions.binop import lower_binop
from instructions.compare import lower_compare
from instructions.jump import lower_jump
from instructions.branch import lower_branch
from instructions.ret import lower_return
@ -53,8 +54,11 @@ class NyashLLVMBuilder:
self.vmap: Dict[int, ir.Value] = {} # value_id -> LLVM value
self.bb_map: Dict[int, ir.Block] = {} # block_id -> LLVM block
# PHI deferrals for sealed block approach
self.phi_deferrals: List[Tuple[int, List[Tuple[int, int]]]] = []
# PHI deferrals for sealed block approach: (block_id, dst_vid, incoming)
self.phi_deferrals: List[Tuple[int, int, List[Tuple[int, int]]]] = []
# Predecessor map and per-block end snapshots
self.preds: Dict[int, List[int]] = {}
self.block_end_values: Dict[int, Dict[int, ir.Value]] = {}
# Resolver for unified value resolution
self.resolver = Resolver(self.vmap, self.bb_map)
@ -72,12 +76,66 @@ class NyashLLVMBuilder:
# No functions - create dummy ny_main
return self._create_dummy_main()
# Pre-declare all functions with default i64 signature to allow cross-calls
import re
for func_data in functions:
name = func_data.get("name", "unknown")
# Derive arity from name suffix '/N' if params list is empty
m = re.search(r"/(\d+)$", name)
if m:
arity = int(m.group(1))
else:
arity = len(func_data.get("params", []))
if name == "ny_main":
fty = ir.FunctionType(self.i32, [])
else:
fty = ir.FunctionType(self.i64, [self.i64] * arity)
exists = False
for f in self.module.functions:
if f.name == name:
exists = True
break
if not exists:
ir.Function(self.module, fty, name=name)
# Process each function
for func_data in functions:
self.lower_function(func_data)
# Wire deferred PHIs
self._wire_deferred_phis()
# Create ny_main wrapper if necessary
has_ny_main = any(f.name == 'ny_main' for f in self.module.functions)
main_fn = None
for f in self.module.functions:
if f.name == 'main':
main_fn = f
break
if main_fn is not None:
# Hide the user main to avoid conflict with NyRT's main symbol
try:
main_fn.linkage = 'private'
except Exception:
pass
if not has_ny_main:
# i32 ny_main() { return (i32) main(); }
ny_main_ty = ir.FunctionType(self.i32, [])
ny_main = ir.Function(self.module, ny_main_ty, name='ny_main')
entry = ny_main.append_basic_block('entry')
b = ir.IRBuilder(entry)
if len(main_fn.args) == 0:
rv = b.call(main_fn, [], name='call_user_main')
else:
# If signature mismatches, return 0
rv = ir.Constant(self.i64, 0)
if hasattr(rv, 'type') and isinstance(rv.type, ir.IntType) and rv.type.width != 32:
rv32 = b.trunc(rv, self.i32) if rv.type.width > 32 else b.zext(rv, self.i32)
b.ret(rv32)
elif hasattr(rv, 'type') and isinstance(rv.type, ir.IntType) and rv.type.width == 32:
b.ret(rv)
else:
b.ret(ir.Constant(self.i32, 0))
return str(self.module)
@ -93,6 +151,7 @@ class NyashLLVMBuilder:
def lower_function(self, func_data: Dict[str, Any]):
"""Lower a single MIR function to LLVM IR"""
name = func_data.get("name", "unknown")
import re
params = func_data.get("params", [])
blocks = func_data.get("blocks", [])
@ -101,13 +160,50 @@ class NyashLLVMBuilder:
# Special case: ny_main returns i32
func_ty = ir.FunctionType(self.i32, [])
else:
# Default: i64(i64, ...) signature
param_types = [self.i64] * len(params)
# Default: i64(i64, ...) signature; derive arity from '/N' suffix when params missing
m = re.search(r"/(\d+)$", name)
arity = int(m.group(1)) if m else len(params)
param_types = [self.i64] * arity
func_ty = ir.FunctionType(self.i64, param_types)
# Create function
func = ir.Function(self.module, func_ty, name=name)
# Create or reuse function
func = None
for f in self.module.functions:
if f.name == name:
func = f
break
if func is None:
func = ir.Function(self.module, func_ty, name=name)
# Map parameters to vmap (value_id: 0..arity-1)
try:
arity = len(func.args)
for i in range(arity):
self.vmap[i] = func.args[i]
except Exception:
pass
# Build predecessor map from control-flow edges
self.preds = {}
for block_data in blocks:
bid = block_data.get("id", 0)
self.preds.setdefault(bid, [])
for block_data in blocks:
src = block_data.get("id", 0)
for inst in block_data.get("instructions", []):
op = inst.get("op")
if op == "jump":
t = inst.get("target")
if t is not None:
self.preds.setdefault(t, []).append(src)
elif op == "branch":
th = inst.get("then")
el = inst.get("else")
if th is not None:
self.preds.setdefault(th, []).append(src)
if el is not None:
self.preds.setdefault(el, []).append(src)
# Create all blocks first
for block_data in blocks:
bid = block_data.get("id", 0)
@ -124,11 +220,40 @@ class NyashLLVMBuilder:
def lower_block(self, bb: ir.Block, block_data: Dict[str, Any], func: ir.Function):
"""Lower a single basic block"""
builder = ir.IRBuilder(bb)
# Provide builder/module to resolver for PHI/casts insertion
try:
self.resolver.builder = builder
self.resolver.module = self.module
except Exception:
pass
instructions = block_data.get("instructions", [])
created_ids: List[int] = []
# Process each instruction
for inst in instructions:
self.lower_instruction(builder, inst, func)
try:
dst = inst.get("dst")
if isinstance(dst, int) and dst not in created_ids and dst in self.vmap:
created_ids.append(dst)
except Exception:
pass
# Snapshot end-of-block values for sealed PHI wiring
bid = block_data.get("id", 0)
snap: Dict[int, ir.Value] = {}
# include function args (avoid 0 constant confusion later via special-case)
try:
arity = len(func.args)
except Exception:
arity = 0
for i in range(arity):
if i in self.vmap:
snap[i] = self.vmap[i]
for vid in created_ids:
val = self.vmap.get(vid)
if val is not None:
snap[vid] = val
self.block_end_values[bid] = snap
def lower_instruction(self, builder: ir.IRBuilder, inst: Dict[str, Any], func: ir.Function):
"""Dispatch instruction to appropriate handler"""
@ -137,15 +262,15 @@ class NyashLLVMBuilder:
if op == "const":
dst = inst.get("dst")
value = inst.get("value")
lower_const(builder, self.module, dst, value, self.vmap)
lower_const(builder, self.module, dst, value, self.vmap, self.resolver)
elif op == "binop":
operation = inst.get("operation")
lhs = inst.get("lhs")
rhs = inst.get("rhs")
dst = inst.get("dst")
lower_binop(builder, self.resolver, operation, lhs, rhs, dst,
self.vmap, builder.block)
lower_binop(builder, self.resolver, operation, lhs, rhs, dst,
self.vmap, builder.block, self.preds, self.block_end_values, self.bb_map)
elif op == "jump":
target = inst.get("target")
@ -155,38 +280,48 @@ class NyashLLVMBuilder:
cond = inst.get("cond")
then_bid = inst.get("then")
else_bid = inst.get("else")
lower_branch(builder, cond, then_bid, else_bid, self.vmap, self.bb_map)
lower_branch(builder, cond, then_bid, else_bid, self.vmap, self.bb_map, self.resolver, self.preds, self.block_end_values)
elif op == "ret":
value = inst.get("value")
lower_return(builder, value, self.vmap, func.return_value.type)
lower_return(builder, value, self.vmap, func.function_type.return_type,
self.resolver, self.preds, self.block_end_values, self.bb_map)
elif op == "phi":
dst = inst.get("dst")
incoming = inst.get("incoming", [])
# Defer PHI wiring for now
defer_phi_wiring(dst, incoming, self.phi_deferrals)
# Wire PHI immediately at the start of the current block using snapshots
lower_phi(builder, dst, incoming, self.vmap, self.bb_map, builder.block, self.resolver, self.block_end_values, self.preds)
elif op == "compare":
# Dedicated compare op
operation = inst.get("operation") or inst.get("op")
lhs = inst.get("lhs")
rhs = inst.get("rhs")
dst = inst.get("dst")
lower_compare(builder, operation, lhs, rhs, dst, self.vmap,
self.resolver, builder.block, self.preds, self.block_end_values, self.bb_map)
elif op == "call":
func_name = inst.get("func")
args = inst.get("args", [])
dst = inst.get("dst")
lower_call(builder, self.module, func_name, args, dst, self.vmap, self.resolver)
lower_call(builder, self.module, func_name, args, dst, self.vmap, self.resolver, self.preds, self.block_end_values, self.bb_map)
elif op == "boxcall":
box_vid = inst.get("box")
method = inst.get("method")
args = inst.get("args", [])
dst = inst.get("dst")
lower_boxcall(builder, self.module, box_vid, method, args, dst,
self.vmap, self.resolver)
lower_boxcall(builder, self.module, box_vid, method, args, dst,
self.vmap, self.resolver, self.preds, self.block_end_values, self.bb_map)
elif op == "externcall":
func_name = inst.get("func")
args = inst.get("args", [])
dst = inst.get("dst")
lower_externcall(builder, self.module, func_name, args, dst,
self.vmap, self.resolver)
lower_externcall(builder, self.module, func_name, args, dst,
self.vmap, self.resolver, self.preds, self.block_end_values, self.bb_map)
elif op == "newbox":
box_type = inst.get("type")
@ -200,12 +335,14 @@ class NyashLLVMBuilder:
src = inst.get("src")
dst = inst.get("dst")
target_type = inst.get("target_type")
lower_typeop(builder, operation, src, dst, target_type,
self.vmap, self.resolver)
lower_typeop(builder, operation, src, dst, target_type,
self.vmap, self.resolver, self.preds, self.block_end_values, self.bb_map)
elif op == "safepoint":
live = inst.get("live", [])
lower_safepoint(builder, self.module, live, self.vmap)
lower_safepoint(builder, self.module, live, self.vmap,
resolver=self.resolver, preds=self.preds,
block_end_values=self.block_end_values, bb_map=self.bb_map)
elif op == "barrier":
barrier_type = inst.get("type", "memory")
@ -216,8 +353,9 @@ class NyashLLVMBuilder:
cond = inst.get("cond")
body = inst.get("body", [])
self.loop_count += 1
if not lower_while_loopform(builder, func, cond, body,
self.loop_count, self.vmap, self.bb_map):
if not lower_while_loopform(builder, func, cond, body,
self.loop_count, self.vmap, self.bb_map,
self.resolver, self.preds, self.block_end_values):
# Fallback to regular while
self._lower_while_regular(builder, inst, func)
else:
@ -226,16 +364,130 @@ class NyashLLVMBuilder:
def _lower_while_regular(self, builder: ir.IRBuilder, inst: Dict[str, Any], func: ir.Function):
"""Fallback regular while lowering"""
# TODO: Implement regular while lowering
pass
# Create basic blocks: cond -> body -> cond, and exit
cond_vid = inst.get("cond")
body_insts = inst.get("body", [])
cur_bb = builder.block
cond_bb = func.append_basic_block(name=f"while{self.loop_count}_cond")
body_bb = func.append_basic_block(name=f"while{self.loop_count}_body")
exit_bb = func.append_basic_block(name=f"while{self.loop_count}_exit")
# Jump from current to cond
builder.branch(cond_bb)
# Cond block
cbuild = ir.IRBuilder(cond_bb)
try:
cond_val = self.resolver.resolve_i64(cond_vid, builder.block, self.preds, self.block_end_values, self.vmap, self.bb_map)
except Exception:
cond_val = self.vmap.get(cond_vid)
if cond_val is None:
cond_val = ir.Constant(self.i1, 0)
# Normalize to i1
if hasattr(cond_val, 'type'):
if isinstance(cond_val.type, ir.IntType) and cond_val.type.width == 64:
zero64 = ir.Constant(self.i64, 0)
cond_val = cbuild.icmp_unsigned('!=', cond_val, zero64, name="while_cond_i1")
elif isinstance(cond_val.type, ir.PointerType):
nullp = ir.Constant(cond_val.type, None)
cond_val = cbuild.icmp_unsigned('!=', cond_val, nullp, name="while_cond_p1")
elif isinstance(cond_val.type, ir.IntType) and cond_val.type.width == 1:
# already i1
pass
else:
# Fallback: treat as false
cond_val = ir.Constant(self.i1, 0)
else:
cond_val = ir.Constant(self.i1, 0)
cbuild.cbranch(cond_val, body_bb, exit_bb)
# Body block
bbuild = ir.IRBuilder(body_bb)
# Allow nested lowering of body instructions within this block
self._lower_instruction_list(bbuild, body_insts, func)
# Ensure terminator: if not terminated, branch back to cond
if bbuild.block.terminator is None:
bbuild.branch(cond_bb)
# Continue at exit
builder.position_at_end(exit_bb)
def _lower_instruction_list(self, builder: ir.IRBuilder, insts: List[Dict[str, Any]], func: ir.Function):
"""Lower a flat list of instructions using current builder and function."""
for sub in insts:
# If current block already has a terminator, create a continuation block
if builder.block.terminator is not None:
cont = func.append_basic_block(name=f"cont_bb_{builder.block.name}")
builder.position_at_end(cont)
self.lower_instruction(builder, sub, func)
def _wire_deferred_phis(self):
"""Wire all deferred PHI nodes"""
# TODO: Implement PHI wiring after all blocks are created
for dst_vid, incoming in self.phi_deferrals:
# Find the block containing this PHI
# Wire the incoming edges
pass
for cur_bid, dst_vid, incoming in self.phi_deferrals:
bb = self.bb_map.get(cur_bid)
if bb is None:
continue
b = ir.IRBuilder(bb)
b.position_at_start(bb)
# Determine phi type: prefer pointer if any incoming is pointer; else f64; else i64
phi_type = self.i64
for (val_id, pred_bid) in incoming:
snap = self.block_end_values.get(pred_bid, {})
val = snap.get(val_id)
if val is not None and hasattr(val, 'type'):
if hasattr(val.type, 'is_pointer') and val.type.is_pointer:
phi_type = val.type
break
elif str(val.type) == str(self.f64):
phi_type = self.f64
phi = b.phi(phi_type, name=f"phi_{dst_vid}")
for (val_id, pred_bid) in incoming:
pred_bb = self.bb_map.get(pred_bid)
if pred_bb is None:
continue
# Self-reference takes precedence regardless of snapshot
if val_id == dst_vid:
val = phi
else:
snap = self.block_end_values.get(pred_bid, {})
# Special-case: incoming 0 means typed zero/null, not value-id 0
if isinstance(val_id, int) and val_id == 0:
val = None
else:
val = snap.get(val_id)
if val is None:
# Default based on phi type
if isinstance(phi_type, ir.IntType):
val = ir.Constant(phi_type, 0)
elif isinstance(phi_type, ir.DoubleType):
val = ir.Constant(phi_type, 0.0)
else:
val = ir.Constant(phi_type, None)
# Type adjust if needed
if hasattr(val, 'type') and val.type != phi_type:
# Insert cast in predecessor block before its terminator
pb = ir.IRBuilder(pred_bb)
try:
term = pred_bb.terminator
if term is not None:
pb.position_before(term)
else:
pb.position_at_end(pred_bb)
except Exception:
pb.position_at_end(pred_bb)
if isinstance(phi_type, ir.IntType) and isinstance(val.type, ir.PointerType):
val = pb.ptrtoint(val, phi_type, name=f"phi_p2i_{dst_vid}_{pred_bid}")
elif isinstance(phi_type, ir.PointerType) and isinstance(val.type, ir.IntType):
val = pb.inttoptr(val, phi_type, name=f"phi_i2p_{dst_vid}_{pred_bid}")
elif isinstance(phi_type, ir.IntType) and isinstance(val.type, ir.IntType):
if phi_type.width > val.type.width:
val = pb.zext(val, phi_type, name=f"phi_zext_{dst_vid}_{pred_bid}")
elif phi_type.width < val.type.width:
val = pb.trunc(val, phi_type, name=f"phi_trunc_{dst_vid}_{pred_bid}")
phi.add_incoming(val, pred_bb)
self.vmap[dst_vid] = phi
def compile_to_object(self, output_path: str):
"""Compile module to object file"""
@ -255,31 +507,52 @@ class NyashLLVMBuilder:
f.write(obj)
def main():
if len(sys.argv) < 2:
print("Usage: llvm_builder.py <input.mir.json> [-o output.o]")
sys.exit(1)
input_file = sys.argv[1]
# CLI:
# llvm_builder.py <input.mir.json> [-o output.o]
# llvm_builder.py --dummy [-o output.o]
output_file = "nyash_llvm_py.o"
if "-o" in sys.argv:
idx = sys.argv.index("-o")
if idx + 1 < len(sys.argv):
output_file = sys.argv[idx + 1]
# Read MIR JSON
args = sys.argv[1:]
dummy = False
if not args:
print("Usage: llvm_builder.py <input.mir.json> [-o output.o] | --dummy [-o output.o]")
sys.exit(1)
if "-o" in args:
idx = args.index("-o")
if idx + 1 < len(args):
output_file = args[idx + 1]
del args[idx:idx+2]
if args and args[0] == "--dummy":
dummy = True
del args[0]
builder = NyashLLVMBuilder()
if dummy:
# Emit dummy ny_main
ir_text = builder._create_dummy_main()
if os.environ.get('NYASH_CLI_VERBOSE') == '1':
print(f"[Python LLVM] Generated dummy IR:\n{ir_text}")
builder.compile_to_object(output_file)
print(f"Compiled to {output_file}")
return
if not args:
print("error: missing input MIR JSON (or use --dummy)", file=sys.stderr)
sys.exit(2)
input_file = args[0]
with open(input_file, 'r') as f:
mir_json = json.load(f)
# Build LLVM IR
builder = NyashLLVMBuilder()
llvm_ir = builder.build_from_mir(mir_json)
print(f"Generated LLVM IR:\n{llvm_ir}")
# Compile to object
if os.environ.get('NYASH_CLI_VERBOSE') == '1':
print(f"[Python LLVM] Generated LLVM IR:\n{llvm_ir}")
builder.compile_to_object(output_file)
print(f"Compiled to {output_file}")
if __name__ == "__main__":
main()
main()

View File

@ -15,14 +15,23 @@ class Resolver:
- Cache per (block, value) to avoid redundant PHIs
"""
def __init__(self, builder: ir.IRBuilder, module: ir.Module):
self.builder = builder
self.module = module
def __init__(self, a, b=None):
"""Flexible init: either (builder, module) or (vmap, bb_map) for legacy wiring."""
if hasattr(a, 'position_at_end'):
# a is IRBuilder
self.builder = a
self.module = b
else:
# Legacy constructor (vmap, bb_map) — builder/module will be set later when available
self.builder = None
self.module = None
# Caches: (block_name, value_id) -> llvm value
self.i64_cache: Dict[Tuple[str, int], ir.Value] = {}
self.ptr_cache: Dict[Tuple[str, int], ir.Value] = {}
self.f64_cache: Dict[Tuple[str, int], ir.Value] = {}
# String literal map: value_id -> Python string (for by-name calls)
self.string_literals: Dict[int, str] = {}
# Type shortcuts
self.i64 = ir.IntType(64)
@ -33,9 +42,10 @@ class Resolver:
self,
value_id: int,
current_block: ir.Block,
preds: Dict[str, list],
block_end_values: Dict[str, Dict[int, Any]],
vmap: Dict[int, Any]
preds: Dict[int, list],
block_end_values: Dict[int, Dict[int, Any]],
vmap: Dict[int, Any],
bb_map: Optional[Dict[int, ir.Block]] = None
) -> ir.Value:
"""
Resolve a MIR value as i64 dominating the current block.
@ -46,31 +56,81 @@ class Resolver:
# Check cache
if cache_key in self.i64_cache:
return self.i64_cache[cache_key]
# Do not trust global vmap across blocks: always localize via preds when available
# Get predecessor blocks
pred_names = preds.get(current_block.name, [])
try:
bid = int(str(current_block.name).replace('bb',''))
except Exception:
bid = -1
pred_ids = [p for p in preds.get(bid, []) if p != bid]
if not pred_names:
if not pred_ids:
# Entry block or no predecessors
base_val = vmap.get(value_id, ir.Constant(self.i64, 0))
result = self._coerce_to_i64(base_val)
# Do not emit casts here; if pointer, fall back to zero
if hasattr(base_val, 'type') and isinstance(base_val.type, ir.IntType):
result = base_val if base_val.type.width == 64 else ir.Constant(self.i64, 0)
elif hasattr(base_val, 'type') and isinstance(base_val.type, ir.PointerType):
result = ir.Constant(self.i64, 0)
else:
result = ir.Constant(self.i64, 0)
else:
# Create PHI at block start
saved_pos = self.builder.block
self.builder.position_at_start(current_block)
saved_pos = None
if self.builder is not None:
saved_pos = self.builder.block
self.builder.position_at_start(current_block)
phi = self.builder.phi(self.i64, name=f"loc_i64_{value_id}")
# Add incoming values from predecessors
for pred_name in pred_names:
pred_vals = block_end_values.get(pred_name, {})
val = pred_vals.get(value_id, ir.Constant(self.i64, 0))
coerced = self._coerce_to_i64(val)
# Note: In real implementation, need pred block reference
phi.add_incoming(coerced, pred_name) # Simplified
for pred_id in pred_ids:
pred_vals = block_end_values.get(pred_id, {})
val = pred_vals.get(value_id)
# Coerce in predecessor block if needed
if val is None:
coerced = ir.Constant(self.i64, 0)
else:
if hasattr(val, 'type') and isinstance(val.type, ir.IntType):
coerced = val if val.type.width == 64 else ir.Constant(self.i64, 0)
elif hasattr(val, 'type') and isinstance(val.type, ir.PointerType):
# insert ptrtoint in predecessor
pred_bb = bb_map.get(pred_id) if bb_map is not None else None
if pred_bb is not None:
pb = ir.IRBuilder(pred_bb)
try:
term = pred_bb.terminator
if term is not None:
pb.position_before(term)
else:
pb.position_at_end(pred_bb)
except Exception:
pb.position_at_end(pred_bb)
coerced = pb.ptrtoint(val, self.i64, name=f"res_p2i_{value_id}_{pred_id}")
else:
coerced = ir.Constant(self.i64, 0)
else:
coerced = ir.Constant(self.i64, 0)
# Use predecessor block if available
pred_bb = None
if bb_map is not None:
pred_bb = bb_map.get(pred_id)
if pred_bb is not None:
phi.add_incoming(coerced, pred_bb)
# If no valid incoming were added, fold to zero to avoid invalid PHI
if len(getattr(phi, 'incoming', [])) == 0:
# Replace with zero constant and discard phi
result = ir.Constant(self.i64, 0)
# Restore position and cache
if saved_pos and self.builder is not None:
self.builder.position_at_end(saved_pos)
self.i64_cache[cache_key] = result
return result
# Restore position
if saved_pos:
if saved_pos and self.builder is not None:
self.builder.position_at_end(saved_pos)
result = phi
@ -82,16 +142,51 @@ class Resolver:
def resolve_ptr(self, value_id: int, current_block: ir.Block,
preds: Dict, block_end_values: Dict, vmap: Dict) -> ir.Value:
"""Resolve as i8* pointer"""
# Similar to resolve_i64 but with pointer type
# TODO: Implement
pass
cache_key = (current_block.name, value_id)
if cache_key in self.ptr_cache:
return self.ptr_cache[cache_key]
# Coerce current vmap value or GlobalVariable to i8*
val = vmap.get(value_id)
if val is None:
result = ir.Constant(self.i8p, None)
else:
if hasattr(val, 'type') and isinstance(val, ir.PointerType):
# If pointer to array (GlobalVariable), GEP to first element
ty = val.type.pointee if hasattr(val.type, 'pointee') else None
if ty is not None and hasattr(ty, 'element'):
c0 = ir.Constant(ir.IntType(32), 0)
result = self.builder.gep(val, [c0, c0], name=f"res_str_gep_{value_id}")
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}")
else:
# f64 or others -> zero
result = ir.Constant(self.i8p, None)
self.ptr_cache[cache_key] = result
return result
def resolve_f64(self, value_id: int, current_block: ir.Block,
preds: Dict, block_end_values: Dict, vmap: Dict) -> ir.Value:
"""Resolve as f64"""
# Similar pattern
# TODO: Implement
pass
cache_key = (current_block.name, value_id)
if cache_key in self.f64_cache:
return self.f64_cache[cache_key]
val = vmap.get(value_id)
if val is None:
result = ir.Constant(self.f64_type, 0.0)
else:
if hasattr(val, 'type') and val.type == self.f64_type:
result = val
elif hasattr(val, 'type') and isinstance(val.type, ir.IntType):
result = self.builder.sitofp(val, self.f64_type)
elif hasattr(val, 'type') and isinstance(val.type, ir.PointerType):
tmp = self.builder.ptrtoint(val, self.i64, name=f"res_p2i_{value_id}")
result = self.builder.sitofp(tmp, self.f64_type, name=f"res_i2f_{value_id}")
else:
result = ir.Constant(self.f64_type, 0.0)
self.f64_cache[cache_key] = result
return result
def _coerce_to_i64(self, val: Any) -> ir.Value:
"""Coerce various types to i64"""
@ -99,14 +194,14 @@ class Resolver:
return val
elif hasattr(val, 'type') and val.type.is_pointer:
# ptr to int
return self.builder.ptrtoint(val, self.i64)
return self.builder.ptrtoint(val, self.i64, name=f"res_p2i_{getattr(val,'name','x')}") if self.builder is not None else ir.Constant(self.i64, 0)
elif hasattr(val, 'type') and isinstance(val.type, ir.IntType):
# int to int (extend/trunc)
if val.type.width < 64:
return self.builder.zext(val, self.i64)
return self.builder.zext(val, self.i64) if self.builder is not None else ir.Constant(self.i64, 0)
elif val.type.width > 64:
return self.builder.trunc(val, self.i64)
return self.builder.trunc(val, self.i64) if self.builder is not None else ir.Constant(self.i64, 0)
return val
else:
# Default zero
return ir.Constant(self.i64, 0)
return ir.Constant(self.i64, 0)