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>
This commit is contained in:
nyash-codex
2025-12-04 12:06:34 +09:00
parent efca57bd49
commit e46034c6ea
11 changed files with 1329 additions and 0 deletions

View File

@ -0,0 +1,154 @@
"""
Unified MIR Call instruction dispatcher - Phase 134-A Modular Design
This module provides the canonical dispatcher for MIR Call instructions,
routing to specialized handlers based on callee type.
Architecture:
- global_call.py: Global function calls
- method_call.py: Box method calls (BoxCall)
- constructor_call.py: Box constructors (NewBox)
- closure_call.py: Closure creation (NewClosure)
- value_call.py: Dynamic function value calls
- extern_call.py: External C ABI calls
Design Philosophy:
- Unified dispatch: One entry point (lower_mir_call)
- JSON v0/v1 compatibility: Transparent normalization
- Fail-fast: No legacy fallbacks (NotImplementedError removed)
- Modular: Each callee type has dedicated handler
"""
from typing import Dict, Any, Optional
from llvmlite import ir
import os
import json
# Import specialized handlers
from .global_call import lower_global_call
from .method_call import lower_method_call
from .constructor_call import lower_constructor_call
from .closure_call import lower_closure_creation
from .value_call import lower_value_call
from .extern_call import lower_extern_call
# Import compatibility layer
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
from mir_call_compat import MirCallCompat
def lower_mir_call(owner, builder: ir.IRBuilder, mir_call: Dict[str, Any], dst_vid: Optional[int], vmap: Dict, resolver):
"""
Lower unified MirCall instruction - CANONICAL DISPATCHER
This is the single entry point for all MIR Call instruction lowering.
It dispatches to specialized handlers based on callee type.
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
Callee Types:
- Global: Global function call (e.g., print, panic)
- Method: Box method call (e.g., str.substring())
- Constructor: Box constructor (e.g., new StringBox())
- Closure: Closure creation
- Value: Dynamic function value call
- Extern: External C ABI function call
Raises:
ValueError: If callee type is unknown or missing
"""
# 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
# Extract callee and arguments
callee = mir_call.get("callee", {})
args = mir_call.get("args", [])
flags = mir_call.get("flags", {})
effects = mir_call.get("effects", {})
# Normalize callee JSON (v0/v1 compatibility)
if callee:
callee = MirCallCompat.normalize_callee(callee)
# Parse callee type
callee_type = callee.get("type") if callee else None
# 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('name'),
'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
# Dispatch to specialized handler based on callee type
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
method = callee.get("name")
box_name = callee.get("box_name")
receiver = callee.get("receiver")
# v1 JSON: receiver is implicit as first arg if 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)
box_type = callee.get("name")
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:
# Fail-fast: No legacy fallback
raise ValueError(f"Unknown or missing callee type: {callee_type} (callee={callee})")
# Export dispatcher as the main interface
__all__ = ['lower_mir_call']

View File

@ -0,0 +1,87 @@
"""
Closure creation lowering for MIR Call instruction.
Handles lowering of closure creation (NewClosure) to LLVM IR.
"""
from typing import Dict, Any, Optional
from llvmlite import ir
def lower_closure_creation(builder, module, params, captures, me_capture, dst_vid, vmap, resolver, owner):
"""
Lower closure creation - TRUE UNIFIED IMPLEMENTATION
Args:
builder: LLVM IR builder
module: LLVM Module
params: List of parameter definitions for the closure
captures: List of captured variable value IDs
me_capture: Value ID of captured 'me' reference (if any)
dst_vid: Destination value ID (register)
vmap: Value mapping dict
resolver: Value resolver instance
owner: NyashLLVMBuilder instance
Effects:
- Creates closure object with captured values
- Stores closure handle in vmap[dst_vid]
"""
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

View File

@ -0,0 +1,122 @@
"""
Box constructor call lowering for MIR Call instruction.
Handles lowering of box constructor calls (NewBox) to LLVM IR.
Supports built-in boxes (StringBox, ArrayBox, etc.) and plugin boxes.
"""
from typing import Dict, Any, Optional
from llvmlite import ir
import os
def lower_constructor_call(builder, module, box_type, args, dst_vid, vmap, resolver, owner):
"""
Lower box constructor - TRUE UNIFIED IMPLEMENTATION
Args:
builder: LLVM IR builder
module: LLVM Module
box_type: Type of box to construct (e.g., "StringBox", "ArrayBox")
args: List of constructor argument value IDs
dst_vid: Destination value ID (register)
vmap: Value mapping dict
resolver: Value resolver instance
owner: NyashLLVMBuilder instance
Effects:
- Emits specialized constructor calls for built-in boxes
- Falls back to generic constructor for plugin boxes
- Stores result handle in vmap[dst_vid]
"""
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

View File

@ -0,0 +1,135 @@
"""
External C ABI function call lowering for MIR Call instruction.
Handles lowering of extern function calls to LLVM IR.
Supports C ABI calling conventions with proper type conversions.
"""
from typing import Dict, Any, Optional
from llvmlite import ir
import os
def lower_extern_call(builder, module, extern_name, args, dst_vid, vmap, resolver, owner):
"""
Lower external C ABI call - TRUE UNIFIED IMPLEMENTATION
Args:
builder: LLVM IR builder
module: LLVM Module
extern_name: Name of the external function
args: List of argument value IDs
dst_vid: Destination value ID (register)
vmap: Value mapping dict
resolver: Value resolver instance
owner: NyashLLVMBuilder instance
Effects:
- Inserts automatic safepoint
- Normalizes extern function names
- Creates C ABI function declaration if needed
- Performs handle-to-pointer conversions for string arguments
- Stores result in vmap[dst_vid]
"""
from instructions.safepoint import insert_automatic_safepoint
from instructions.extern_normalize import normalize_extern_name
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
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

View File

@ -0,0 +1,90 @@
"""
Global function call lowering for MIR Call instruction.
Handles lowering of global function calls (e.g., print, panic, user-defined functions)
to LLVM IR.
"""
from typing import Dict, Any, Optional
from llvmlite import ir
import os
def lower_global_call(builder, module, func_name, args, dst_vid, vmap, resolver, owner):
"""
Lower global function call - TRUE UNIFIED IMPLEMENTATION
Args:
builder: LLVM IR builder
module: LLVM Module
func_name: Name of the global function to call
args: List of argument value IDs
dst_vid: Destination value ID (register)
vmap: Value mapping dict
resolver: Value resolver instance
owner: NyashLLVMBuilder instance
Effects:
- Inserts automatic safepoint
- Creates function declaration if needed
- Emits LLVM call instruction
- Stores result in vmap[dst_vid]
- Marks string-producing functions for type tracking
"""
from instructions.safepoint import insert_automatic_safepoint
# Insert automatic safepoint
if os.environ.get('NYASH_LLVM_AUTO_SAFEPOINT', '1') == '1':
insert_automatic_safepoint(builder, module, "function_call")
# Resolver helper
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)

View File

@ -0,0 +1,175 @@
"""
Box method call lowering for MIR Call instruction.
Handles lowering of box method calls (BoxCall) to LLVM IR.
Implements the "Everything is Box" philosophy with unified method dispatch.
"""
from typing import Dict, Any, Optional
from llvmlite import ir
import os
def lower_method_call(builder, module, box_name, method, receiver, args, dst_vid, vmap, resolver, owner):
"""
Lower box method call - TRUE UNIFIED IMPLEMENTATION
Args:
builder: LLVM IR builder
module: LLVM Module
box_name: Name of the box type (e.g., "ConsoleBox", "StringBox")
method: Method name to call
receiver: Value ID of the receiver object
args: List of argument value IDs
dst_vid: Destination value ID (register)
vmap: Value mapping dict
resolver: Value resolver instance
owner: NyashLLVMBuilder instance
Effects:
- Inserts automatic safepoint
- Emits specialized LLVM calls for known methods
- Falls back to generic plugin invocation for unknown methods
- Stores result in vmap[dst_vid]
- Marks string-producing methods for type tracking
"""
from instructions.safepoint import insert_automatic_safepoint
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)

View File

@ -0,0 +1,112 @@
"""
Dynamic function value call lowering for MIR Call instruction.
Handles lowering of first-class function calls (calling through a function value)
to LLVM IR.
"""
from typing import Dict, Any, Optional
from llvmlite import ir
def lower_value_call(builder, module, func_vid, args, dst_vid, vmap, resolver, owner):
"""
Lower dynamic function value call - TRUE UNIFIED IMPLEMENTATION
Args:
builder: LLVM IR builder
module: LLVM Module
func_vid: Value ID holding the function/closure to call
args: List of argument value IDs
dst_vid: Destination value ID (register)
vmap: Value mapping dict
resolver: Value resolver instance
owner: NyashLLVMBuilder instance
Effects:
- Dispatches to appropriate dynamic call function based on argument count
- Handles both function handles and closure handles
- Stores result in vmap[dst_vid]
"""
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

View File

@ -0,0 +1,120 @@
"""
JSON v0/v1 compatibility layer for MIR Call instructions.
This module provides utilities to normalize JSON v0 and v1 formats
for MIR Call instructions, making it easier to remove v0 in Phase 124+.
JSON v0 format:
{"method": "log", "box_type": "ConsoleBox", "receiver": 42}
JSON v1 format:
{"name": "log", "box_name": "ConsoleBox", "receiver": 42}
"""
from typing import Dict, Any, Optional
class MirCallCompat:
"""JSON v0/v1 compatibility processor for MIR Call instructions."""
@staticmethod
def normalize_callee(callee_json: Dict[str, Any]) -> Dict[str, Any]:
"""
Normalize JSON v0/v1 callee format to unified internal format.
Args:
callee_json: Callee dict from MIR Call instruction
Returns:
Normalized dict with consistent keys:
{
"type": "Global" | "Method" | "Constructor" | "Closure" | "Value" | "Extern",
"name": str (function/method name),
"box_name": str (for Method/Constructor),
"receiver": int (for Method),
... other fields
}
Examples:
v0: {"method": "log", "box_type": "ConsoleBox"}
v1: {"name": "log", "box_name": "ConsoleBox"}
{"type": "Method", "name": "log", "box_name": "ConsoleBox"}
"""
if not callee_json:
return {}
# Extract type (should already be present in both v0/v1)
callee_type = callee_json.get("type")
# Normalize method/function name (v0: "method", v1: "name")
name = callee_json.get("name") or callee_json.get("method")
# Normalize box type name (v0: "box_type", v1: "box_name")
box_name = callee_json.get("box_name") or callee_json.get("box_type")
# Build normalized result
normalized = {
"type": callee_type,
}
if name is not None:
normalized["name"] = name
if box_name is not None:
normalized["box_name"] = box_name
# Copy other fields as-is
for key in ["receiver", "value", "params", "captures", "me_capture", "certainty"]:
if key in callee_json:
normalized[key] = callee_json[key]
return normalized
@staticmethod
def detect_format_version(callee_json: Dict[str, Any]) -> int:
"""
Detect whether callee JSON is v0 or v1 format.
Args:
callee_json: Callee dict from MIR Call instruction
Returns:
0 for v0 format (uses "method"/"box_type")
1 for v1 format (uses "name"/"box_name")
Raises:
ValueError: If format cannot be determined
"""
if not callee_json:
raise ValueError("Empty callee JSON")
# Check for v0 markers
if "method" in callee_json or "box_type" in callee_json:
return 0
# Check for v1 markers
if "name" in callee_json or "box_name" in callee_json:
return 1
# No clear markers - check callee type
if callee_json.get("type") in ["Global", "Method", "Constructor", "Extern"]:
# Modern format (v1)
return 1
raise ValueError(f"Unknown callee format: {callee_json}")
@staticmethod
def is_v0_format(callee_json: Dict[str, Any]) -> bool:
"""Check if callee JSON uses v0 format."""
try:
return MirCallCompat.detect_format_version(callee_json) == 0
except ValueError:
return False
@staticmethod
def is_v1_format(callee_json: Dict[str, Any]) -> bool:
"""Check if callee JSON uses v1 format."""
try:
return MirCallCompat.detect_format_version(callee_json) == 1
except ValueError:
return False