259 lines
8.0 KiB
Python
259 lines
8.0 KiB
Python
|
|
"""
|
||
|
|
Phase 133: Console LLVM Bridge - ConsoleBox 統合モジュール
|
||
|
|
|
||
|
|
目的:
|
||
|
|
- ConsoleBox メソッド (log/println/warn/error/clear) の LLVM IR 変換を1箇所に集約
|
||
|
|
- BoxCall lowering 側の分岐を削除し、箱化モジュール化を実現
|
||
|
|
|
||
|
|
設計原則:
|
||
|
|
- Rust VM の TypeRegistry (slot 400-403) と完全一致
|
||
|
|
- Phase 122 の println/log エイリアス統一を踏襲
|
||
|
|
- ABI は nyash.console.* シリーズで統一 (i8* ptr, i64 len)
|
||
|
|
"""
|
||
|
|
|
||
|
|
import llvmlite.ir as ir
|
||
|
|
from typing import Dict, List, Optional, Any
|
||
|
|
|
||
|
|
# Console method mapping (Phase 122 unified)
|
||
|
|
# slot 400: log/println (alias)
|
||
|
|
# slot 401: warn
|
||
|
|
# slot 402: error
|
||
|
|
# slot 403: clear
|
||
|
|
|
||
|
|
CONSOLE_METHODS = {
|
||
|
|
"log": "nyash.console.log",
|
||
|
|
"println": "nyash.console.log", # Phase 122: log のエイリアス
|
||
|
|
"warn": "nyash.console.warn",
|
||
|
|
"error": "nyash.console.error",
|
||
|
|
"clear": "nyash.console.clear",
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def _declare(module: ir.Module, name: str, ret, args):
|
||
|
|
"""Declare or get existing function"""
|
||
|
|
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_i8_ptr(builder: ir.IRBuilder, module: ir.Module, v: ir.Value) -> ir.Value:
|
||
|
|
"""
|
||
|
|
Convert any value to i8* for console output.
|
||
|
|
|
||
|
|
Handles:
|
||
|
|
- i64 handle → nyash.string.to_i8p_h(i64) → i8*
|
||
|
|
- i8* → pass through
|
||
|
|
- pointer to array → GEP to first element
|
||
|
|
"""
|
||
|
|
i64 = ir.IntType(64)
|
||
|
|
i8p = ir.IntType(8).as_pointer()
|
||
|
|
|
||
|
|
if hasattr(v, 'type'):
|
||
|
|
# Already i8*, pass through
|
||
|
|
if isinstance(v.type, ir.PointerType):
|
||
|
|
pointee = v.type.pointee
|
||
|
|
if isinstance(pointee, ir.IntType) and pointee.width == 8:
|
||
|
|
return v
|
||
|
|
# Pointer to array: GEP to first element
|
||
|
|
if isinstance(pointee, ir.ArrayType):
|
||
|
|
c0 = ir.IntType(32)(0)
|
||
|
|
return builder.gep(v, [c0, c0], name="cons_arr_gep")
|
||
|
|
|
||
|
|
# i64 handle: convert via bridge
|
||
|
|
if isinstance(v.type, ir.IntType):
|
||
|
|
if v.type.width == 64:
|
||
|
|
bridge = _declare(module, "nyash.string.to_i8p_h", i8p, [i64])
|
||
|
|
return builder.call(bridge, [v], name="str_h2p_cons")
|
||
|
|
# Other int widths: extend to i64, then convert
|
||
|
|
v_i64 = builder.zext(v, i64) if v.type.width < 64 else builder.trunc(v, i64)
|
||
|
|
bridge = _declare(module, "nyash.string.to_i8p_h", i8p, [i64])
|
||
|
|
return builder.call(bridge, [v_i64], name="str_h2p_cons")
|
||
|
|
|
||
|
|
# Fallback: null pointer
|
||
|
|
return ir.Constant(i8p, None)
|
||
|
|
|
||
|
|
|
||
|
|
def emit_console_call(
|
||
|
|
builder: ir.IRBuilder,
|
||
|
|
module: ir.Module,
|
||
|
|
method_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,
|
||
|
|
) -> bool:
|
||
|
|
"""
|
||
|
|
Emit ConsoleBox method call to LLVM IR.
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
True if method was handled, False if not a Console method
|
||
|
|
|
||
|
|
Args:
|
||
|
|
builder: LLVM IR builder
|
||
|
|
module: LLVM module
|
||
|
|
method_name: Console method name (log/println/warn/error/clear)
|
||
|
|
args: Argument value IDs
|
||
|
|
dst_vid: Destination value ID (usually None for Console methods)
|
||
|
|
vmap: Value map
|
||
|
|
resolver: Optional type resolver
|
||
|
|
preds: Predecessor map
|
||
|
|
block_end_values: Block end values
|
||
|
|
bb_map: Basic block map
|
||
|
|
ctx: Build context
|
||
|
|
"""
|
||
|
|
# Check if this is a Console method
|
||
|
|
if method_name not in CONSOLE_METHODS:
|
||
|
|
return False
|
||
|
|
|
||
|
|
i64 = ir.IntType(64)
|
||
|
|
i8p = ir.IntType(8).as_pointer()
|
||
|
|
|
||
|
|
# Get target runtime function name
|
||
|
|
runtime_fn_name = CONSOLE_METHODS[method_name]
|
||
|
|
|
||
|
|
# Extract resolver/preds from ctx if available
|
||
|
|
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
|
||
|
|
|
||
|
|
def _res_i64(vid: int):
|
||
|
|
"""Resolve value ID to i64 via resolver or vmap"""
|
||
|
|
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)
|
||
|
|
|
||
|
|
# clear() takes no arguments
|
||
|
|
if method_name == "clear":
|
||
|
|
callee = _declare(module, runtime_fn_name, ir.VoidType(), [])
|
||
|
|
builder.call(callee, [], name="console_clear")
|
||
|
|
if dst_vid is not None:
|
||
|
|
vmap[dst_vid] = ir.Constant(i64, 0)
|
||
|
|
return True
|
||
|
|
|
||
|
|
# log/println/warn/error take 1 string argument
|
||
|
|
if not args:
|
||
|
|
# No argument provided, use empty string
|
||
|
|
arg0_ptr = ir.Constant(i8p, None)
|
||
|
|
else:
|
||
|
|
arg0_vid = args[0]
|
||
|
|
|
||
|
|
# Try to get pointer directly from resolver.string_ptrs (fast path)
|
||
|
|
arg0_ptr = None
|
||
|
|
if r is not None and hasattr(r, 'string_ptrs'):
|
||
|
|
try:
|
||
|
|
arg0_ptr = r.string_ptrs.get(int(arg0_vid))
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
|
||
|
|
# Fallback: resolve value and convert to i8*
|
||
|
|
if arg0_ptr is None:
|
||
|
|
arg0_val = vmap.get(arg0_vid)
|
||
|
|
if arg0_val is None and r is not None:
|
||
|
|
arg0_val = _res_i64(arg0_vid)
|
||
|
|
if arg0_val is None:
|
||
|
|
arg0_val = ir.Constant(i64, 0)
|
||
|
|
|
||
|
|
arg0_ptr = _ensure_i8_ptr(builder, module, arg0_val)
|
||
|
|
|
||
|
|
# Emit call: void @nyash.console.{log,warn,error}(i8* %ptr)
|
||
|
|
# Note: Current ABI uses i8* only (null-terminated), not i8* + i64 len
|
||
|
|
callee = _declare(module, runtime_fn_name, i64, [i8p])
|
||
|
|
builder.call(callee, [arg0_ptr], name=f"console_{method_name}")
|
||
|
|
|
||
|
|
# Console methods return void (treated as 0)
|
||
|
|
if dst_vid is not None:
|
||
|
|
vmap[dst_vid] = ir.Constant(i64, 0)
|
||
|
|
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
# Phase 133: Diagnostic helpers
|
||
|
|
|
||
|
|
def get_console_method_info(method_name: str) -> Optional[Dict[str, Any]]:
|
||
|
|
"""
|
||
|
|
Get Console method metadata for debugging/diagnostics.
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dict with keys: runtime_fn, slot, arity, is_alias
|
||
|
|
None if not a Console method
|
||
|
|
"""
|
||
|
|
if method_name not in CONSOLE_METHODS:
|
||
|
|
return None
|
||
|
|
|
||
|
|
# Slot mapping (from TypeRegistry)
|
||
|
|
slot_map = {
|
||
|
|
"log": 400,
|
||
|
|
"println": 400, # Alias
|
||
|
|
"warn": 401,
|
||
|
|
"error": 402,
|
||
|
|
"clear": 403,
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
"runtime_fn": CONSOLE_METHODS[method_name],
|
||
|
|
"slot": slot_map[method_name],
|
||
|
|
"arity": 0 if method_name == "clear" else 1,
|
||
|
|
"is_alias": method_name == "println",
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def validate_console_abi(module: ir.Module) -> List[str]:
|
||
|
|
"""
|
||
|
|
Validate that Console runtime functions are correctly declared.
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
List of validation errors (empty if all OK)
|
||
|
|
"""
|
||
|
|
errors = []
|
||
|
|
i8p = ir.IntType(8).as_pointer()
|
||
|
|
i64 = ir.IntType(64)
|
||
|
|
void = ir.VoidType()
|
||
|
|
|
||
|
|
expected = {
|
||
|
|
"nyash.console.log": (i64, [i8p]),
|
||
|
|
"nyash.console.warn": (i64, [i8p]),
|
||
|
|
"nyash.console.error": (i64, [i8p]),
|
||
|
|
"nyash.console.clear": (void, []),
|
||
|
|
}
|
||
|
|
|
||
|
|
for fn_name, (ret_ty, arg_tys) in expected.items():
|
||
|
|
found = None
|
||
|
|
for f in module.functions:
|
||
|
|
if f.name == fn_name:
|
||
|
|
found = f
|
||
|
|
break
|
||
|
|
|
||
|
|
if found is None:
|
||
|
|
continue # Not yet declared, OK
|
||
|
|
|
||
|
|
# Check signature
|
||
|
|
if str(found.return_value.type) != str(ret_ty):
|
||
|
|
errors.append(f"{fn_name}: return type mismatch (expected {ret_ty}, got {found.return_value.type})")
|
||
|
|
|
||
|
|
if len(found.args) != len(arg_tys):
|
||
|
|
errors.append(f"{fn_name}: arg count mismatch (expected {len(arg_tys)}, got {len(found.args)})")
|
||
|
|
else:
|
||
|
|
for i, (expected_ty, actual_arg) in enumerate(zip(arg_tys, found.args)):
|
||
|
|
if str(actual_arg.type) != str(expected_ty):
|
||
|
|
errors.append(f"{fn_name}: arg {i} type mismatch (expected {expected_ty}, got {actual_arg.type})")
|
||
|
|
|
||
|
|
return errors
|