✨ Python LLVM backend implementation (experimental)
- Created llvmlite-based LLVM backend in src/llvm_py/
- Implemented all MIR14 instructions (const, binop, jump, branch, ret, compare, phi, call, boxcall, externcall, typeop, newbox, safepoint, barrier)
- Experimental LoopForm support
- ~2000 lines of clean Python code vs complex Rust/inkwell
- Useful for PHI/SSA validation and rapid prototyping
- Added documentation to CLAUDE.md
This was created while waiting for ChatGPT's investigation of BuilderCursor issues.
2025-09-12 20:55:13 +09:00
|
|
|
"""
|
|
|
|
|
BoxCall instruction lowering
|
|
|
|
|
Core of Nyash's "Everything is Box" philosophy
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import llvmlite.ir as ir
|
|
|
|
|
from typing import Dict, List, Optional
|
|
|
|
|
|
2025-09-13 15:37:58 +09:00
|
|
|
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)
|
|
|
|
|
|
✨ Python LLVM backend implementation (experimental)
- Created llvmlite-based LLVM backend in src/llvm_py/
- Implemented all MIR14 instructions (const, binop, jump, branch, ret, compare, phi, call, boxcall, externcall, typeop, newbox, safepoint, barrier)
- Experimental LoopForm support
- ~2000 lines of clean Python code vs complex Rust/inkwell
- Useful for PHI/SSA validation and rapid prototyping
- Added documentation to CLAUDE.md
This was created while waiting for ChatGPT's investigation of BuilderCursor issues.
2025-09-12 20:55:13 +09:00
|
|
|
def lower_boxcall(
|
|
|
|
|
builder: ir.IRBuilder,
|
|
|
|
|
module: ir.Module,
|
|
|
|
|
box_vid: int,
|
|
|
|
|
method_name: str,
|
|
|
|
|
args: List[int],
|
|
|
|
|
dst_vid: Optional[int],
|
|
|
|
|
vmap: Dict[int, ir.Value],
|
2025-09-13 15:37:58 +09:00
|
|
|
resolver=None,
|
|
|
|
|
preds=None,
|
|
|
|
|
block_end_values=None,
|
|
|
|
|
bb_map=None
|
✨ Python LLVM backend implementation (experimental)
- Created llvmlite-based LLVM backend in src/llvm_py/
- Implemented all MIR14 instructions (const, binop, jump, branch, ret, compare, phi, call, boxcall, externcall, typeop, newbox, safepoint, barrier)
- Experimental LoopForm support
- ~2000 lines of clean Python code vs complex Rust/inkwell
- Useful for PHI/SSA validation and rapid prototyping
- Added documentation to CLAUDE.md
This was created while waiting for ChatGPT's investigation of BuilderCursor issues.
2025-09-12 20:55:13 +09:00
|
|
|
) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Lower MIR BoxCall instruction
|
|
|
|
|
|
|
|
|
|
Current implementation uses method_id approach for plugin boxes.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
builder: Current LLVM IR builder
|
|
|
|
|
module: LLVM module
|
|
|
|
|
box_vid: Box instance value ID (handle)
|
|
|
|
|
method_name: Method 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
|
|
|
|
|
"""
|
|
|
|
|
i64 = ir.IntType(64)
|
2025-09-13 15:37:58 +09:00
|
|
|
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)
|
✨ Python LLVM backend implementation (experimental)
- Created llvmlite-based LLVM backend in src/llvm_py/
- Implemented all MIR14 instructions (const, binop, jump, branch, ret, compare, phi, call, boxcall, externcall, typeop, newbox, safepoint, barrier)
- Experimental LoopForm support
- ~2000 lines of clean Python code vs complex Rust/inkwell
- Useful for PHI/SSA validation and rapid prototyping
- Added documentation to CLAUDE.md
This was created while waiting for ChatGPT's investigation of BuilderCursor issues.
2025-09-12 20:55:13 +09:00
|
|
|
else:
|
2025-09-13 15:37:58 +09:00
|
|
|
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")
|
✨ Python LLVM backend implementation (experimental)
- Created llvmlite-based LLVM backend in src/llvm_py/
- Implemented all MIR14 instructions (const, binop, jump, branch, ret, compare, phi, call, boxcall, externcall, typeop, newbox, safepoint, barrier)
- Experimental LoopForm support
- ~2000 lines of clean Python code vs complex Rust/inkwell
- Useful for PHI/SSA validation and rapid prototyping
- Added documentation to CLAUDE.md
This was created while waiting for ChatGPT's investigation of BuilderCursor issues.
2025-09-12 20:55:13 +09:00
|
|
|
if dst_vid is not None:
|
2025-09-13 15:37:58 +09:00
|
|
|
vmap[dst_vid] = result
|