Files
hakorune/src/llvm_py/instructions/call.py
nyash-codex 419214a5a9 feat(naming): Python NamingHelper実装 - Rust NamingBoxのミラー完成
Phase 25.4 メンテナンス: Python LLVM側のNamingBox SSOT統一

## 📦 実装内容

### 1. Python NamingHelper作成
- 新規作成: `src/llvm_py/naming_helper.py`
- Rust `src/mir/naming.rs` と完全同一の意味論を実装
- 3つの関数:
  - `encode_static_method(box_name, method, arity)` → "BoxName.method/arity"
  - `canonical_box_name(raw)` → "main" → "Main"
  - `normalize_static_global_name(func_name)` → "main._nop/0" → "Main._nop/0"
- doctest 9個全てPASS 

### 2. Python LLVM側の統一修正
- `instructions/boxcall.py:437` - f"Main.{method_name}/{arity}" → encode_static_method()
- `instructions/call.py:170-173` - traced_names タプル生成をNamingHelper経由に変更
- `pyvm/intrinsic.py:17, 50` - "Main.esc_json/1", "Main.dirname/1" → encode_static_method()
- `builders/entry.py:16` - 'Main.main/1' → encode_static_method("Main", "main", 1)

## 🎯 技術的成果
- **意味論一致**: Rust ↔ Python で完全同一の命名規則
- **保守性向上**: ハードコード4箇所 → NamingHelper一元管理
- **テスト完備**: doctest 9個でRust NamingBoxと同一動作を保証

## テスト結果
 python3 -m py_compile: 全ファイル構文OK
 python3 -m doctest naming_helper.py: 9 tests passed

## 参考
- Phase 25.4-A (Rust側): fa9cea51, bceb20ed
- Rust NamingBox SSOT: src/mir/naming.rs

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 09:38:49 +09:00

204 lines
8.0 KiB
Python

"""
Call instruction lowering
Handles regular function calls (not BoxCall or ExternCall)
"""
import llvmlite.ir as ir
from typing import Dict, List, Optional, Any
from trace import debug as trace_debug
from instructions.safepoint import insert_automatic_safepoint
from naming_helper import encode_static_method
def lower_call(
builder: ir.IRBuilder,
module: ir.Module,
func_name: str,
args: List[int],
dst_vid: Optional[int],
vmap: Dict[int, ir.Value],
resolver=None,
preds=None,
block_end_values=None,
bb_map=None,
ctx: Optional[Any] = None,
) -> None:
"""
Lower MIR Call instruction
Args:
builder: Current LLVM IR builder
module: LLVM module
func_name: Function name to call
args: List of argument value IDs
dst_vid: Optional destination for return value
vmap: Value map
resolver: Optional resolver for type handling
"""
# If BuildCtx is provided, prefer its maps for consistency.
if ctx is not None:
try:
if getattr(ctx, 'resolver', None) is not None:
resolver = ctx.resolver
if getattr(ctx, 'preds', None) is not None and preds is None:
preds = ctx.preds
if getattr(ctx, 'block_end_values', None) is not None and block_end_values is None:
block_end_values = ctx.block_end_values
if getattr(ctx, 'bb_map', None) is not None and bb_map is None:
bb_map = ctx.bb_map
except Exception:
pass
# Insert an automatic safepoint after the function call
try:
import os
if os.environ.get('NYASH_LLVM_AUTO_SAFEPOINT', '1') == '1':
insert_automatic_safepoint(builder, module, "function_call")
except Exception:
pass
# Short-hands with ctx (backward-compatible fallback)
r = resolver
p = preds
bev = block_end_values
bbm = bb_map
if ctx is not None:
try:
r = getattr(ctx, 'resolver', r)
p = getattr(ctx, 'preds', p)
bev = getattr(ctx, 'block_end_values', bev)
bbm = getattr(ctx, 'bb_map', bbm)
except Exception:
pass
# Resolver helpers (prefer resolver when available)
def _res_i64(vid: int):
if r is not None and p is not None and bev is not None and bbm is not None:
try:
return r.resolve_i64(vid, builder.block, p, bev, vmap, bbm)
except Exception:
return None
return vmap.get(vid)
def _res_ptr(vid: int):
if r is not None and p is not None and bev is not None:
try:
return r.resolve_ptr(vid, builder.block, p, bev, vmap)
except Exception:
return None
return vmap.get(vid)
# 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
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. Special-case well-known C symbols
# (print/println and nyash.console.*) to use pointer-based signature i8* -> i64.
i64 = ir.IntType(64)
i8p = ir.IntType(8).as_pointer()
name = actual_name if isinstance(actual_name, str) else "unknown_fn"
is_console = False
try:
if isinstance(name, str):
is_console = (name in ("print", "println")) or name.startswith("nyash.console.")
except Exception:
is_console = False
if is_console:
func_type = ir.FunctionType(i64, [i8p])
else:
# Default i64(int64,...) prototype
func_type = ir.FunctionType(i64, [i64] * len(args))
func = ir.Function(module, func_type, name=name)
# If calling a Dev-only predicate name (e.g., 'condition_fn') that lacks a body,
# synthesize a trivial definition that returns non-zero to satisfy linker during bring-up.
if isinstance(actual_name, str) and actual_name == 'condition_fn':
try:
if func is not None and len(list(func.blocks)) == 0:
b = ir.IRBuilder(func.append_basic_block('entry'))
rty = func.function_type.return_type
if isinstance(rty, ir.IntType):
b.ret(ir.Constant(rty, 1))
else:
b.ret_void()
except Exception:
pass
# Prepare arguments
call_args = []
for i, arg_id in enumerate(args):
arg_val = None
if i < len(func.args):
expected_type = func.args[i].type
if hasattr(expected_type, 'is_pointer') and expected_type.is_pointer:
arg_val = _res_ptr(arg_id)
else:
arg_val = _res_i64(arg_id)
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)
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, 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, name=f"call_p2i_{i}")
call_args.append(arg_val)
# Make the call
result = builder.call(func, call_args, name=f"call_{func_name}")
# NamingBox SSOT: Optional trace for final debugging
traced_names = (
encode_static_method("Main", "node_json", 3),
encode_static_method("Main", "esc_json", 1),
"main"
)
if isinstance(actual_name, str) and actual_name in traced_names:
trace_debug(f"[TRACE] call {actual_name} args={len(call_args)}")
# Store result if needed
if dst_vid is not None:
vmap[dst_vid] = result
# Heuristic: mark known string-producing functions as string handles
try:
name_for_tag = actual_name if isinstance(actual_name, str) else str(actual_name)
if resolver is not None and hasattr(resolver, 'mark_string'):
if any(key in name_for_tag for key in [
'esc_json', 'node_json', 'dirname', 'join', 'read_all', 'toJson'
]):
resolver.mark_string(dst_vid)
# Additionally, create a pointer view via bridge for println pointer-API
if resolver is not None and hasattr(resolver, 'string_ptrs'):
i64 = ir.IntType(64)
i8p = ir.IntType(8).as_pointer()
if hasattr(result, 'type') and isinstance(result.type, ir.IntType) and result.type.width == 64:
bridge = None
for f in module.functions:
if f.name == 'nyash.string.to_i8p_h':
bridge = f; break
if bridge is None:
bridge = ir.Function(module, ir.FunctionType(i8p, [i64]), name='nyash.string.to_i8p_h')
pv = builder.call(bridge, [result], name=f"ret_h2p_{dst_vid}")
resolver.string_ptrs[int(dst_vid)] = pv
except Exception:
pass