Files
hakorune/src/llvm_py/instructions/mir_call_legacy.py
nyash-codex e46034c6ea feat(llvm): Phase 134-A - mir_call.py unified 設計完成
681行の giant ファイルを機能別に分割し、箱化モジュール化を達成。

Changes:
- NEW: src/llvm_py/mir_call_compat.py (120 lines)
  - JSON v0/v1 互換層を一元管理
  - normalize_callee(), detect_format_version()

- NEW: src/llvm_py/instructions/mir_call/ (7 files)
  - __init__.py: Canonical Dispatcher (lower_mir_call)
  - global_call.py: Global関数呼び出し (90 lines)
  - method_call.py: Boxメソッド呼び出し (175 lines)
  - constructor_call.py: Boxコンストラクタ (122 lines)
  - closure_call.py: Closure生成 (87 lines)
  - value_call.py: 動的関数値呼び出し (112 lines)
  - extern_call.py: 外部C ABI呼び出し (135 lines)

- ARCHIVE: mir_call.py → mir_call_legacy.py

Technical Achievements:
 mir_call.py: 681行 → 分割(各 80-175行、責務 明確)
 Phase 133 ConsoleLlvmBridge パターンを継承
 NYASH_MIR_UNIFIED_CALL フラグ完全廃止
 legacy dispatcher 削除(NotImplementedError 根治)
 JSON v0/v1 互換層を mir_call_compat.py に一元化
 Fail-Fast 原則確立
 テスト: 全 mir_call 関連テスト PASS

Design Principles Inherited:
- Phase 133 ConsoleLlvmBridge 箱化パターン継承
- Each module has clear responsibility
- mir_call_compat.py で Phase 124+ v0削除が容易
- テスト分割で保守性大幅向上

Next Phase:
Phase 134-B - StringBox bridge 分離(boxcall.py:130-282)
Phase 134-C - CollectionBox bridge 分離(boxcall.py:325-375)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 12:06:34 +09:00

682 lines
27 KiB
Python

"""
Unified MIR Call instruction handler - ChatGPT5 Pro A++ Design
Replaces call.py, boxcall.py, externcall.py, newbox.py, plugin_invoke.py, newclosure.py
"""
from typing import Dict, Any, Optional
from llvmlite import ir
import os
import json
def lower_mir_call(owner, builder: ir.IRBuilder, mir_call: Dict[str, Any], dst_vid: Optional[int], vmap: Dict, resolver):
"""
Lower unified MirCall instruction.
Parameters:
- owner: NyashLLVMBuilder instance
- builder: LLVM IR builder
- mir_call: MirCall dict containing 'callee', 'args', 'flags', 'effects'
- dst_vid: Optional destination register
- vmap: Value mapping dict
- resolver: Value resolver instance
"""
# Guard: avoid emitting after a terminator; if current block is closed, create continuation.
try:
if builder.block is not None and getattr(builder.block, 'terminator', None) is not None:
func = builder.block.parent
cont = func.append_basic_block(name=f"cont_bb_{builder.block.name}")
builder.position_at_end(cont)
except Exception:
pass
# Check if unified call is enabled
use_unified = os.getenv("NYASH_MIR_UNIFIED_CALL", "1").lower() not in ("0", "false", "off")
if not use_unified:
# Fall back to legacy dispatching
return lower_legacy_call(owner, builder, mir_call, dst_vid, vmap, resolver)
callee = mir_call.get("callee", {})
args = mir_call.get("args", [])
flags = mir_call.get("flags", {})
effects = mir_call.get("effects", {})
# Parse callee type
callee_type = callee.get("type")
# Optional trace: dump callee info (including certainty for Method)
if os.getenv('NYASH_LLVM_TRACE_CALLS') == '1':
try:
evt = { 'type': callee_type }
if callee_type == 'Global':
evt.update({'name': callee.get('name')})
elif callee_type == 'Method':
evt.update({
'box_name': callee.get('box_name'),
'method': callee.get('method'),
'receiver': callee.get('receiver'),
'certainty': callee.get('certainty'),
})
elif callee_type == 'Extern':
evt.update({'name': callee.get('name')})
print(json.dumps({'phase':'llvm','cat':'mir_call','event':evt}))
except Exception:
pass
if callee_type == "Global":
# Global function call (e.g., print, panic)
func_name = callee.get("name")
lower_global_call(builder, owner.module, func_name, args, dst_vid, vmap, resolver, owner)
elif callee_type == "Method":
# Box method call
# v1 JSON uses "name", v0 uses "method" - support both
method = callee.get("name") or callee.get("method")
box_name = callee.get("box_name")
receiver = callee.get("receiver")
# v1 JSON: receiver is implicit as first arg, box_name may be missing
if receiver is None and args:
receiver = args[0]
args = args[1:] # Remove receiver from args
lower_method_call(builder, owner.module, box_name, method, receiver, args, dst_vid, vmap, resolver, owner)
elif callee_type == "Constructor":
# Box constructor (NewBox)
# v1 JSON uses "name", v0 uses "box_type" - support both
box_type = callee.get("name") or callee.get("box_type")
lower_constructor_call(builder, owner.module, box_type, args, dst_vid, vmap, resolver, owner)
elif callee_type == "Closure":
# Closure creation (NewClosure)
params = callee.get("params", [])
captures = callee.get("captures", [])
me_capture = callee.get("me_capture")
lower_closure_creation(builder, owner.module, params, captures, me_capture, dst_vid, vmap, resolver, owner)
elif callee_type == "Value":
# Dynamic function value call
func_vid = callee.get("value")
lower_value_call(builder, owner.module, func_vid, args, dst_vid, vmap, resolver, owner)
elif callee_type == "Extern":
# External C ABI function call
extern_name = callee.get("name")
lower_extern_call(builder, owner.module, extern_name, args, dst_vid, vmap, resolver, owner)
else:
raise ValueError(f"Unknown callee type: {callee_type}")
def lower_legacy_call(owner, builder, mir_call, dst_vid, vmap, resolver):
"""Legacy dispatcher for backward compatibility"""
# This would dispatch to the old instruction handlers
# For now, just raise an error if unified is disabled
raise NotImplementedError("Legacy call dispatch not implemented in mir_call.py")
def lower_global_call(builder, module, func_name, args, dst_vid, vmap, resolver, owner):
"""Lower global function call - TRUE UNIFIED IMPLEMENTATION"""
from llvmlite import ir
from instructions.safepoint import insert_automatic_safepoint
import os
# Insert automatic safepoint
if os.environ.get('NYASH_LLVM_AUTO_SAFEPOINT', '1') == '1':
insert_automatic_safepoint(builder, module, "function_call")
# Resolver helpers
def _resolve_arg(vid: int):
if resolver and hasattr(resolver, 'resolve_i64'):
try:
return resolver.resolve_i64(vid, builder.block, owner.preds,
owner.block_end_values, vmap, owner.bb_map)
except:
pass
return vmap.get(vid)
# Look up function in module
func = None
for f in module.functions:
if f.name == func_name:
func = f
break
if not func:
# Create function declaration with i64 signature
ret_type = ir.IntType(64)
arg_types = [ir.IntType(64)] * len(args)
func_type = ir.FunctionType(ret_type, arg_types)
func = ir.Function(module, func_type, name=func_name)
# Prepare arguments with type conversion
call_args = []
for i, arg_id in enumerate(args):
arg_val = _resolve_arg(arg_id)
if arg_val is None:
arg_val = ir.Constant(ir.IntType(64), 0)
# Type conversion for function signature matching
if i < len(func.args):
expected_type = func.args[i].type
if expected_type.is_pointer and isinstance(arg_val.type, ir.IntType):
arg_val = builder.inttoptr(arg_val, expected_type, name=f"global_i2p_{i}")
elif isinstance(expected_type, ir.IntType) and arg_val.type.is_pointer:
arg_val = builder.ptrtoint(arg_val, expected_type, name=f"global_p2i_{i}")
call_args.append(arg_val)
# Make the call - TRUE UNIFIED
result = builder.call(func, call_args, name=f"unified_global_{func_name}")
# Store result
if dst_vid is not None:
vmap[dst_vid] = result
# Mark string-producing functions
if resolver and hasattr(resolver, 'mark_string'):
if any(key in func_name for key in ['esc_json', 'node_json', 'dirname', 'join', 'read_all', 'toJson']):
resolver.mark_string(dst_vid)
def lower_method_call(builder, module, box_name, method, receiver, args, dst_vid, vmap, resolver, owner):
"""Lower box method call - TRUE UNIFIED IMPLEMENTATION"""
from llvmlite import ir
from instructions.safepoint import insert_automatic_safepoint
import os
i64 = ir.IntType(64)
i8 = ir.IntType(8)
i8p = i8.as_pointer()
# Insert automatic safepoint
if os.environ.get('NYASH_LLVM_AUTO_SAFEPOINT', '1') == '1':
insert_automatic_safepoint(builder, module, "boxcall")
# Helper to declare function
def _declare(name: str, ret, args_types):
for f in module.functions:
if f.name == name:
return f
fnty = ir.FunctionType(ret, args_types)
return ir.Function(module, fnty, name=name)
# Helper to ensure i64 handle
def _ensure_handle(v):
if isinstance(v.type, ir.IntType) and v.type.width == 64:
return v
if v.type.is_pointer:
callee = _declare("nyash.box.from_i8_string", i64, [i8p])
return builder.call(callee, [v], name="unified_str_ptr2h")
if isinstance(v.type, ir.IntType):
return builder.zext(v, i64) if v.type.width < 64 else builder.trunc(v, i64)
return v
# Resolve receiver and arguments
def _resolve_arg(vid: int):
if resolver and hasattr(resolver, 'resolve_i64'):
try:
return resolver.resolve_i64(vid, builder.block, owner.preds,
owner.block_end_values, vmap, owner.bb_map)
except:
pass
return vmap.get(vid)
recv_val = _resolve_arg(receiver)
if recv_val is None:
recv_val = ir.Constant(i64, 0)
recv_h = _ensure_handle(recv_val)
# TRUE UNIFIED METHOD DISPATCH - Everything is Box philosophy
if method in ["length", "len"]:
callee = _declare("nyash.any.length_h", i64, [i64])
result = builder.call(callee, [recv_h], name="unified_length")
elif method == "size":
callee = _declare("nyash.any.length_h", i64, [i64])
result = builder.call(callee, [recv_h], name="unified_size")
elif method == "substring":
if len(args) >= 2:
s = _resolve_arg(args[0]) or ir.Constant(i64, 0)
e = _resolve_arg(args[1]) or ir.Constant(i64, 0)
callee = _declare("nyash.string.substring_hii", i64, [i64, i64, i64])
result = builder.call(callee, [recv_h, s, e], name="unified_substring")
else:
result = recv_h
elif method == "lastIndexOf":
if args:
needle = _resolve_arg(args[0]) or ir.Constant(i64, 0)
needle_h = _ensure_handle(needle)
callee = _declare("nyash.string.lastIndexOf_hh", i64, [i64, i64])
result = builder.call(callee, [recv_h, needle_h], name="unified_lastIndexOf")
else:
result = ir.Constant(i64, -1)
elif method == "get":
if args:
k = _resolve_arg(args[0]) or ir.Constant(i64, 0)
callee = _declare("nyash.map.get_hh", i64, [i64, i64])
result = builder.call(callee, [recv_h, k], name="unified_map_get")
else:
result = ir.Constant(i64, 0)
elif method == "push":
if args:
v = _resolve_arg(args[0]) or ir.Constant(i64, 0)
callee = _declare("nyash.array.push_h", i64, [i64, i64])
result = builder.call(callee, [recv_h, v], name="unified_array_push")
else:
result = recv_h
elif method == "set":
if len(args) >= 2:
k = _resolve_arg(args[0]) or ir.Constant(i64, 0)
v = _resolve_arg(args[1]) or ir.Constant(i64, 0)
callee = _declare("nyash.map.set_hh", i64, [i64, i64, i64])
result = builder.call(callee, [recv_h, k, v], name="unified_map_set")
else:
result = recv_h
elif method == "has":
if args:
k = _resolve_arg(args[0]) or ir.Constant(i64, 0)
callee = _declare("nyash.map.has_hh", i64, [i64, i64])
result = builder.call(callee, [recv_h, k], name="unified_map_has")
else:
result = ir.Constant(i64, 0)
elif method == "log":
if args:
arg0 = _resolve_arg(args[0]) or ir.Constant(i64, 0)
if isinstance(arg0.type, ir.IntType) and arg0.type.width == 64:
bridge = _declare("nyash.string.to_i8p_h", i8p, [i64])
p = builder.call(bridge, [arg0], name="unified_str_h2p")
callee = _declare("nyash.console.log", i64, [i8p])
result = builder.call(callee, [p], name="unified_console_log")
else:
callee = _declare("nyash.console.log", i64, [i8p])
result = builder.call(callee, [arg0], name="unified_console_log")
else:
result = ir.Constant(i64, 0)
else:
# Generic plugin method invocation
method_str = method.encode('utf-8') + b'\0'
method_global = ir.GlobalVariable(module, ir.ArrayType(i8, len(method_str)), name=f"unified_method_{method}")
method_global.initializer = ir.Constant(ir.ArrayType(i8, len(method_str)), bytearray(method_str))
method_global.global_constant = True
mptr = builder.gep(method_global, [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), 0)])
argc = ir.Constant(i64, len(args))
a1 = _resolve_arg(args[0]) if args else ir.Constant(i64, 0)
a2 = _resolve_arg(args[1]) if len(args) > 1 else ir.Constant(i64, 0)
callee = _declare("nyash.plugin.invoke_by_name_i64", i64, [i64, i8p, i64, i64, i64])
result = builder.call(callee, [recv_h, mptr, argc, a1, a2], name="unified_plugin_invoke")
# Store result
if dst_vid is not None:
vmap[dst_vid] = result
# Mark string-producing methods
if resolver and hasattr(resolver, 'mark_string'):
if method in ['substring', 'esc_json', 'node_json', 'dirname', 'join', 'read_all', 'toJson']:
resolver.mark_string(dst_vid)
def lower_constructor_call(builder, module, box_type, args, dst_vid, vmap, resolver, owner):
"""Lower box constructor - TRUE UNIFIED IMPLEMENTATION"""
from llvmlite import ir
import os
i64 = ir.IntType(64)
i8 = ir.IntType(8)
i8p = i8.as_pointer()
# Helper to resolve arguments
def _resolve_arg(vid: int):
if resolver and hasattr(resolver, 'resolve_i64'):
try:
return resolver.resolve_i64(vid, builder.block, owner.preds,
owner.block_end_values, vmap, owner.bb_map)
except:
pass
return vmap.get(vid)
# Helper to declare function
def _declare(name: str, ret, args_types):
for f in module.functions:
if f.name == name:
return f
fnty = ir.FunctionType(ret, args_types)
return ir.Function(module, fnty, name=name)
# TRUE UNIFIED CONSTRUCTOR DISPATCH
if box_type == "StringBox":
if args and len(args) > 0:
# String constructor with initial value
arg0 = _resolve_arg(args[0])
if arg0 and isinstance(arg0.type, ir.IntType) and arg0.type.width == 64:
# Already a handle, return as-is
result = arg0
elif arg0 and arg0.type.is_pointer:
# Convert i8* to string handle
callee = _declare("nyash.box.from_i8_string", i64, [i8p])
result = builder.call(callee, [arg0], name="unified_str_new")
else:
# Create empty string
callee = _declare("nyash.string.new", i64, [])
result = builder.call(callee, [], name="unified_str_empty")
else:
# Empty string constructor
callee = _declare("nyash.string.new", i64, [])
result = builder.call(callee, [], name="unified_str_empty")
elif box_type == "ArrayBox":
# Align with kernel export (birth_h)
callee = _declare("nyash.array.birth_h", i64, [])
result = builder.call(callee, [], name="unified_arr_new")
elif box_type == "MapBox":
# Align with kernel export (birth_h)
callee = _declare("nyash.map.birth_h", i64, [])
result = builder.call(callee, [], name="unified_map_new")
elif box_type == "IntegerBox":
if args and len(args) > 0:
arg0 = _resolve_arg(args[0]) or ir.Constant(i64, 0)
callee = _declare("nyash.integer.new", i64, [i64])
result = builder.call(callee, [arg0], name="unified_int_new")
else:
callee = _declare("nyash.integer.new", i64, [i64])
result = builder.call(callee, [ir.Constant(i64, 0)], name="unified_int_zero")
elif box_type == "BoolBox":
if args and len(args) > 0:
arg0 = _resolve_arg(args[0]) or ir.Constant(i64, 0)
callee = _declare("nyash.bool.new", i64, [i64])
result = builder.call(callee, [arg0], name="unified_bool_new")
else:
callee = _declare("nyash.bool.new", i64, [i64])
result = builder.call(callee, [ir.Constant(i64, 0)], name="unified_bool_false")
else:
# Generic box constructor or plugin box
# Defensive: ensure box_type is never None
if box_type is None:
# Fallback to generic box if type is missing
box_type = "Box"
box_type_lower = box_type.lower() if hasattr(box_type, 'lower') else str(box_type).lower()
constructor_name = f"nyash.{box_type_lower}.new"
if args:
arg_vals = [_resolve_arg(arg_id) or ir.Constant(i64, 0) for arg_id in args]
arg_types = [i64] * len(arg_vals)
callee = _declare(constructor_name, i64, arg_types)
result = builder.call(callee, arg_vals, name=f"unified_{box_type_lower}_new")
else:
callee = _declare(constructor_name, i64, [])
result = builder.call(callee, [], name=f"unified_{box_type_lower}_new")
# Store result
if dst_vid is not None:
vmap[dst_vid] = result
def lower_closure_creation(builder, module, params, captures, me_capture, dst_vid, vmap, resolver, owner):
"""Lower closure creation - TRUE UNIFIED IMPLEMENTATION"""
from llvmlite import ir
i64 = ir.IntType(64)
i8 = ir.IntType(8)
i8p = i8.as_pointer()
# Helper to resolve arguments
def _resolve_arg(vid: int):
if resolver and hasattr(resolver, 'resolve_i64'):
try:
return resolver.resolve_i64(vid, builder.block, owner.preds,
owner.block_end_values, vmap, owner.bb_map)
except:
pass
return vmap.get(vid)
# Helper to declare function
def _declare(name: str, ret, args_types):
for f in module.functions:
if f.name == name:
return f
fnty = ir.FunctionType(ret, args_types)
return ir.Function(module, fnty, name=name)
# Create closure metadata structure
num_captures = len(captures) if captures else 0
num_params = len(params) if params else 0
# Resolve captured values
capture_vals = []
if captures:
for capture in captures:
if isinstance(capture, dict) and 'id' in capture:
cap_val = _resolve_arg(capture['id']) or ir.Constant(i64, 0)
elif isinstance(capture, int):
cap_val = _resolve_arg(capture) or ir.Constant(i64, 0)
else:
cap_val = ir.Constant(i64, 0)
capture_vals.append(cap_val)
# Add me_capture if present
if me_capture is not None:
me_val = _resolve_arg(me_capture) if isinstance(me_capture, int) else ir.Constant(i64, 0)
capture_vals.append(me_val)
num_captures += 1
# Call closure creation function
if num_captures > 0:
# Closure with captures
callee = _declare("nyash.closure.new_with_captures", i64, [i64, i64] + [i64] * num_captures)
args = [ir.Constant(i64, num_params), ir.Constant(i64, num_captures)] + capture_vals
result = builder.call(callee, args, name="unified_closure_with_captures")
else:
# Simple closure without captures
callee = _declare("nyash.closure.new", i64, [i64])
result = builder.call(callee, [ir.Constant(i64, num_params)], name="unified_closure_simple")
# Store result
if dst_vid is not None:
vmap[dst_vid] = result
def lower_value_call(builder, module, func_vid, args, dst_vid, vmap, resolver, owner):
"""Lower dynamic function value call - TRUE UNIFIED IMPLEMENTATION"""
from llvmlite import ir
i64 = ir.IntType(64)
i8 = ir.IntType(8)
i8p = i8.as_pointer()
# Helper to resolve arguments
def _resolve_arg(vid: int):
if resolver and hasattr(resolver, 'resolve_i64'):
try:
return resolver.resolve_i64(vid, builder.block, owner.preds,
owner.block_end_values, vmap, owner.bb_map)
except:
pass
return vmap.get(vid)
# Helper to declare function
def _declare(name: str, ret, args_types):
for f in module.functions:
if f.name == name:
return f
fnty = ir.FunctionType(ret, args_types)
return ir.Function(module, fnty, name=name)
# Resolve the function value (handle to function or closure)
func_val = _resolve_arg(func_vid)
if func_val is None:
func_val = ir.Constant(i64, 0)
# Resolve arguments
arg_vals = []
for arg_id in args:
arg_val = _resolve_arg(arg_id) or ir.Constant(i64, 0)
arg_vals.append(arg_val)
# Dynamic dispatch based on function value type
# This could be a function handle, closure handle, or method handle
if len(arg_vals) == 0:
# No arguments - simple function call
callee = _declare("nyash.dynamic.call_0", i64, [i64])
result = builder.call(callee, [func_val], name="unified_dynamic_call_0")
elif len(arg_vals) == 1:
# One argument
callee = _declare("nyash.dynamic.call_1", i64, [i64, i64])
result = builder.call(callee, [func_val, arg_vals[0]], name="unified_dynamic_call_1")
elif len(arg_vals) == 2:
# Two arguments
callee = _declare("nyash.dynamic.call_2", i64, [i64, i64, i64])
result = builder.call(callee, [func_val, arg_vals[0], arg_vals[1]], name="unified_dynamic_call_2")
else:
# Generic variadic call
argc = ir.Constant(i64, len(arg_vals))
# Create argument array for variadic call
if len(arg_vals) <= 4:
# Use direct argument passing for small argument lists
arg_types = [i64] * (2 + len(arg_vals)) # func_val, argc, ...args
callee = _declare("nyash.dynamic.call_n", i64, arg_types)
call_args = [func_val, argc] + arg_vals
result = builder.call(callee, call_args, name="unified_dynamic_call_n")
else:
# For large argument lists, use array-based approach
callee = _declare("nyash.dynamic.call_array", i64, [i64, i64, i8p])
# Create temporary array for arguments
array_type = ir.ArrayType(i64, len(arg_vals))
array_alloca = builder.alloca(array_type, name="unified_arg_array")
# Store arguments in array
for i, arg_val in enumerate(arg_vals):
gep = builder.gep(array_alloca, [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), i)])
builder.store(arg_val, gep)
# Cast array to i8*
array_ptr = builder.bitcast(array_alloca, i8p, name="unified_arg_array_ptr")
result = builder.call(callee, [func_val, argc, array_ptr], name="unified_dynamic_call_array")
# Store result
if dst_vid is not None:
vmap[dst_vid] = result
def lower_extern_call(builder, module, extern_name, args, dst_vid, vmap, resolver, owner):
"""Lower external C ABI call - TRUE UNIFIED IMPLEMENTATION"""
from llvmlite import ir
from instructions.safepoint import insert_automatic_safepoint
import os
i64 = ir.IntType(64)
i8 = ir.IntType(8)
i8p = i8.as_pointer()
# Insert automatic safepoint
if os.environ.get('NYASH_LLVM_AUTO_SAFEPOINT', '1') == '1':
insert_automatic_safepoint(builder, module, "externcall")
# Helper to resolve arguments
def _resolve_arg(vid: int):
if resolver and hasattr(resolver, 'resolve_i64'):
try:
return resolver.resolve_i64(vid, builder.block, owner.preds,
owner.block_end_values, vmap, owner.bb_map)
except:
pass
return vmap.get(vid)
# Normalize extern target names via shared normalizer
from instructions.extern_normalize import normalize_extern_name
extern_name = normalize_extern_name(extern_name)
# Look up extern function in module
func = None
for f in module.functions:
if f.name == extern_name:
func = f
break
if not func:
# Create C ABI function declaration
if extern_name == "nyash.console.log":
func_type = ir.FunctionType(i64, [i8p])
elif extern_name in ["print", "panic", "error"]:
func_type = ir.FunctionType(ir.VoidType(), [i8p])
else:
# Generic extern: i64 return, i64 args
arg_types = [i64] * len(args)
func_type = ir.FunctionType(i64, arg_types)
func = ir.Function(module, func_type, name=extern_name)
# Prepare arguments with C ABI type conversion
call_args = []
for i, arg_id in enumerate(args):
arg_val = _resolve_arg(arg_id)
if arg_val is None:
arg_val = ir.Constant(i64, 0)
# Type conversion for C ABI
if i < len(func.args):
expected_type = func.args[i].type
if expected_type.is_pointer:
# Convert i64 handle to i8* for string parameters
if isinstance(arg_val.type, ir.IntType) and arg_val.type.width == 64:
# Use string handle-to-pointer conversion
try:
to_i8p = None
for f in module.functions:
if f.name == "nyash.string.to_i8p_h":
to_i8p = f
break
if not to_i8p:
to_i8p_type = ir.FunctionType(i8p, [i64])
to_i8p = ir.Function(module, to_i8p_type, name="nyash.string.to_i8p_h")
arg_val = builder.call(to_i8p, [arg_val], name=f"unified_extern_h2p_{i}")
except:
# Fallback: inttoptr conversion
arg_val = builder.inttoptr(arg_val, expected_type, name=f"unified_extern_i2p_{i}")
elif not arg_val.type.is_pointer:
arg_val = builder.inttoptr(arg_val, expected_type, name=f"unified_extern_i2p_{i}")
elif isinstance(expected_type, ir.IntType):
# Convert to expected integer width
if arg_val.type.is_pointer:
arg_val = builder.ptrtoint(arg_val, expected_type, name=f"unified_extern_p2i_{i}")
elif isinstance(arg_val.type, ir.IntType) and arg_val.type.width != expected_type.width:
if arg_val.type.width < expected_type.width:
arg_val = builder.zext(arg_val, expected_type, name=f"unified_extern_zext_{i}")
else:
arg_val = builder.trunc(arg_val, expected_type, name=f"unified_extern_trunc_{i}")
call_args.append(arg_val)
# Make the C ABI call - TRUE UNIFIED
if len(call_args) == len(func.args):
result = builder.call(func, call_args, name=f"unified_extern_{extern_name}")
else:
# Truncate args to match function signature
result = builder.call(func, call_args[:len(func.args)], name=f"unified_extern_{extern_name}_trunc")
# Store result
if dst_vid is not None:
ret_type = func.function_type.return_type
if isinstance(ret_type, ir.VoidType):
vmap[dst_vid] = ir.Constant(i64, 0)
else:
vmap[dst_vid] = result