Files

594 lines
37 KiB
Plaintext

from stdint import *
import ast
import mlirlite
import memhub
import string
import stdlib
import lib.core.Gen.Translator as HT
import lib.core.Gen.GenTypes as GenTypes
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.core.Gen.GenApi as GenApi
from . import CallGenBase
# ============================================================
# CallGenLib - CallGenMixin split (stage C): C-lib declares / return-type inference
novirtual class CallGenLib(CallGenBase.CallGenBase):
"""C-lib declarations / return-type inference / kwargs call helpers (extends Base)"""
def _ensure_c_lib_declare(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, name: str) -> mlirlite.Function*:
"""Create a declare for a known C-lib function"""
if pool is None or mod is None or name is None: return None
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
ret_ty: mlirlite.LLVMType* = None
params: int = 0 # parameter count
if string.strcmp(name, "strlen") == 0 or string.strcmp(name, "strlength") == 0:
ret_ty = i64_ty
params = 1
elif string.strcmp(name, "memset") == 0:
ret_ty = i8_ptr_ty
params = 3
elif string.strcmp(name, "memcpy") == 0:
ret_ty = i8_ptr_ty
params = 3
elif string.strcmp(name, "memmove") == 0:
ret_ty = i8_ptr_ty
params = 3
elif string.strcmp(name, "memcmp") == 0:
ret_ty = i32_ty
params = 3
elif string.strcmp(name, "strcmp") == 0:
ret_ty = i32_ty
params = 2
elif string.strcmp(name, "strncmp") == 0:
ret_ty = i32_ty
params = 3
elif string.strcmp(name, "strcpy") == 0:
ret_ty = i8_ptr_ty
params = 2
elif string.strcmp(name, "strncpy") == 0:
ret_ty = i8_ptr_ty
params = 3
elif string.strcmp(name, "strcat") == 0:
ret_ty = i8_ptr_ty
params = 2
elif string.strcmp(name, "puts") == 0:
ret_ty = i32_ty
params = 1
elif string.strcmp(name, "atoi") == 0:
ret_ty = i32_ty
params = 1
elif string.strcmp(name, "atol") == 0:
ret_ty = i64_ty
params = 1
elif string.strcmp(name, "abs") == 0:
ret_ty = i32_ty
params = 1
elif string.strcmp(name, "labs") == 0:
ret_ty = i64_ty
params = 1
else: return None
# create the declare
fn: mlirlite.Function* = mlirlite.create_declare(pool, mod, name, ret_ty)
if fn is None: return None
# add parameters
if params == 1:
if string.strcmp(name, "abs") == 0: mlirlite.add_param(pool, fn, i32_ty, "n")
elif string.strcmp(name, "labs") == 0: mlirlite.add_param(pool, fn, i64_ty, "n")
else: mlirlite.add_param(pool, fn, i8_ptr_ty, "s")
elif params == 2:
mlirlite.add_param(pool, fn, i8_ptr_ty, "dst")
mlirlite.add_param(pool, fn, i8_ptr_ty, "src")
elif params == 3:
if string.strcmp(name, "memset") == 0:
mlirlite.add_param(pool, fn, i8_ptr_ty, "dst")
mlirlite.add_param(pool, fn, i32_ty, "c")
mlirlite.add_param(pool, fn, i64_ty, "n")
else:
mlirlite.add_param(pool, fn, i8_ptr_ty, "dst")
mlirlite.add_param(pool, fn, i8_ptr_ty, "src")
mlirlite.add_param(pool, fn, i64_ty, "n")
return fn
def _promote_clib_call_args(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, call_name: str, head: mlirlite.Value*, count: int) -> mlirlite.Value*:
"""Promote bare-name C-lib call arguments to their standard signature widths"""
if pool is None or builder is None or call_name is None or head is None: return head
# bare names only (no '.' -> no sha1 prefix); prefixed ones use vpi definition-side parsing
if string.strchr(call_name, 46) is not None: return head
# whitelist: index of the size_t (i64) length param (matches _ensure_c_lib_declare)
size_idx: int = -1
if string.strcmp(call_name, "memset") == 0: size_idx = 2
elif string.strcmp(call_name, "memcpy") == 0: size_idx = 2
elif string.strcmp(call_name, "memmove") == 0: size_idx = 2
elif string.strcmp(call_name, "memcmp") == 0: size_idx = 2
elif string.strcmp(call_name, "strncmp") == 0: size_idx = 2
elif string.strcmp(call_name, "strncpy") == 0: size_idx = 2
elif string.strcmp(call_name, "strncat") == 0: size_idx = 2
elif string.strcmp(call_name, "vsnprintf") == 0: size_idx = 1
if size_idx < 0 or count <= size_idx: return head
# locate the length param (index size_idx)
cur: mlirlite.Value* = head
idx: int = 0
while cur is not None and idx < size_idx:
cur = cur.Next
idx += 1
if cur is None or cur.Ty is None: return head
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
promoted: mlirlite.Value* = None
match cur.Ty:
case mlirlite.LLVMType.Int(bits):
if bits >= 64: return head
if cur.IsConst != 0:
# constant: use an i64 constant directly (bit pattern kept, unsigned length)
promoted = mlirlite.const_int(pool, 64, cur.IntVal)
else:
# SSA value: zext to i64 (size_t is an unsigned length)
promoted = mlirlite.build_zext(builder, cur, i64_ty)
case _:
return head
if promoted is None: return head
# rebuild the arg list: replace index size_idx with the promoted value
new_head: mlirlite.Value* = None
new_tail: mlirlite.Value* = None
cur2: mlirlite.Value* = head
i2: int = 0
while cur2 is not None:
node_v: mlirlite.Value* = cur2
if i2 == size_idx: node_v = promoted
if new_head is None: new_head = node_v
else: new_tail.Next = node_v
new_tail = node_v
cur2 = cur2.Next
i2 += 1
return new_head
# _coerce_args_to_def_params: coerce cross-module call args to vpi definition-side param
# types so call/declare/define widths match; bare C-lib names use the C-lib path instead.
def _coerce_args_to_def_params(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, call_name: str, head: mlirlite.Value*, count: int, cargs: list[ast.AST*]* = None, self_off: int = 0, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Coerce cross-module call args to vpi definition-side param types; returns the rebuilt chain"""
if pool is None or builder is None or call_name is None or head is None: return head
if self._is_sha1_mangled_call(call_name) == 0: return head
# split {sha1}.{rest}: if rest has '.', it is {cls}.{method}, else {func}
rest: str = call_name + 17
cls_nm: str = None
meth_nm: str = rest
# take the last '.': the method has none, and the first '.' would truncate qualified type args
dot_r: str = string.strrchr(rest, 46)
if dot_r is not None:
cls_len: sizet = uint64(dot_r) - uint64(rest)
if cls_len <= 0: return head
cls_buf: bytes = stdlib.malloc(cls_len + 1)
if cls_buf is None: return head
string.strncpy(cls_buf, rest, cls_len)
cls_buf[cls_len] = '\0'
cls_nm = cls_buf
meth_nm = dot_r + 1
if meth_nm is None or meth_nm[0] == '\0':
if cls_nm is not None: stdlib.free(cls_nm)
return head
sha1_buf: bytes = stdlib.malloc(17)
if sha1_buf is None:
if cls_nm is not None: stdlib.free(cls_nm)
return head
string.strncpy(sha1_buf, call_name, 16)
sha1_buf[16] = '\0'
# ensure-load + direct cache lookup (not _vpi_method_param_annot, which misses module funcs)
if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0: VpiReader._ensure_vpi_param_annot_loaded(sha1_buf, self.Gen.CallGenBase.TempDir)
prm_annots: str = VpiReader._lookup_vpi_param_annots(sha1_buf, cls_nm, meth_nm)
# generic-specialized class: annotations live on the template class, so fall back to template lookup
tpl_src: int = 0
if prm_annots is None:
if cls_nm is not None and string.strchr(cls_nm, 91) is not None: tpl_src = 1
else:
stdlib.free(sha1_buf)
if cls_nm is not None: stdlib.free(cls_nm)
return head
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
new_head: mlirlite.Value* = None
new_tail: mlirlite.Value* = None
cur: mlirlite.Value* = head
idx: int = 0
while cur is not None and idx < count:
node_v: mlirlite.Value* = cur
tgt_ty: mlirlite.LLVMType* = None
if tpl_src != 0: tgt_ty = VpiReader._resolve_vpi_param_annot_seg_template(pool, sha1_buf, cls_nm, meth_nm, idx, i32_ty, i8_ptr_ty)
else: tgt_ty = VpiReader._resolve_vpi_param_annot_seg(pool, sha1_buf, cls_nm, meth_nm, idx, i32_ty, i8_ptr_ty)
if tgt_ty is not None and cur.Ty is not None:
# Argument AST (for source-symbolic checks): AST index = chain index idx - self offset
arg_idx: int = idx - self_off
arg_node: ast.AST* = None
if cargs is not None and arg_idx >= 0 and arg_idx < len(cargs): arg_node = cargs.get(arg_idx)
# Value-reference params (&T / &temp T): auto-address the argument (cross-module callee).
# The mode comes from the definition-side vpi annotation segment (shared resolver); if the
# arg is not a pointer it is addressed (ref: lvalue) or copied into a temp slot (temp).
vpi_cur_sha: str = None
if trans is not None: vpi_cur_sha = trans.ModuleSha1
mode_cm: int = self.Gen.CallGenBase._vpi_auto_addr_mode(call_name, vpi_cur_sha, idx)
if mode_cm != 0 and trans is not None and trans.Module is not None:
node_v = self.Gen.CallGenBase._apply_auto_addr_arg(builder, pool, trans.Module, trans, mode_cm, arg_node, node_v)
# "str / bytes are not generic pointer wildcards": void* / t.CPtr / rawptr may
# convert to any pointer, but str/bytes may not, even though both are i8* at LLVM level.
if self.Gen.ExprGenBase._str_rejects_ptr_kind(cur) != 0:
if VpiReader._vpi_param_annot_seg_is(sha1_buf, cls_nm, meth_nm, idx, "str") != 0 or VpiReader._vpi_param_annot_seg_is(sha1_buf, cls_nm, meth_nm, idx, "bytes") != 0:
self.Gen.ExprGenBase.fail_if_str_annot_agg_ptr(cur, 1, arg_node)
node_v = self._coerce_arg_value_to_ty(pool, builder, node_v, tgt_ty, arg_node, trans, tpl_src)
if new_head is None: new_head = node_v
else: new_tail.Next = node_v
new_tail = node_v
cur = cur.Next
idx += 1
stdlib.free(sha1_buf)
if cls_nm is not None: stdlib.free(cls_nm)
return new_head
# _coerce_arg_value_to_ty: convert one arg Value to the target param type -- same-width ints
# pass through, narrow/wide ints trunc/extend, mismatched pointers bitcast; else unchanged.
def _coerce_arg_value_to_ty(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, val: mlirlite.Value*, tgt_ty: mlirlite.LLVMType*, node: ast.AST* = None, trans: HT.Translator* = None, tpl_src: int = 0) -> mlirlite.Value*:
"""Convert one argument Value to the target param type; leaves it unchanged when unsafe"""
if pool is None or builder is None or val is None or tgt_ty is None or val.Ty is None: return val
match val.Ty:
case mlirlite.LLVMType.Int(bits_a):
match tgt_ty:
case mlirlite.LLVMType.Int(bits_b):
if bits_b == bits_a: return val
if bits_b < bits_a:
if val.IsConst != 0:
nv_c: mlirlite.Value* = mlirlite.const_int(pool, bits_b, val.IntVal)
if nv_c is not None: return nv_c
else:
nv_t: mlirlite.Value* = mlirlite.build_trunc(builder, val, tgt_ty)
if nv_t is not None: return nv_t
# widening (arg narrower than param): rebuild constants by decimal value
if val.IsConst != 0:
nv_w: mlirlite.Value* = mlirlite.const_int(pool, bits_b, val.IntVal)
if nv_w is not None: return nv_w
return val
# SSA widening: direction from source signedness (i1/i8/unsigned -> zext, else sext)
if bits_b > bits_a:
if self.Gen.ExprGenBase._zext_or_sext_val(pool, val, node, trans) != 0:
nv_zw: mlirlite.Value* = mlirlite.build_zext(builder, val, tgt_ty)
if nv_zw is not None: return nv_zw
else:
nv_sw: mlirlite.Value* = mlirlite.build_sext(builder, val, tgt_ty)
if nv_sw is not None: return nv_sw
return val
case _:
pass
case mlirlite.LLVMType.Ptr(_):
match tgt_ty:
case mlirlite.LLVMType.Ptr(_):
# same-level pointers: bitcast when the arg type differs from the definition
# side (aligned so the first call site's declare matches; llc otherwise errors).
if self.Gen.AssignGen._stg_ty_equal(pool, val.Ty, tgt_ty) == 0:
nv_p: mlirlite.Value* = mlirlite.build_bitcast(builder, val, tgt_ty)
if nv_p is not None: return nv_p
case _:
pass
case _:
# aggregate -> pointer: spill rvalue struct/array and take its address (params lower to Ptr)
match tgt_ty:
case mlirlite.LLVMType.Ptr(_):
vk_a: int = val.Ty.kind()
if vk_a == mlirlite.LLVMType.Struct or vk_a == mlirlite.LLVMType.Array:
tmp_a: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, val.Ty)
if tmp_a is not None:
mlirlite.build_store(builder, val, tmp_a)
return tmp_a
case _:
pass
return val
def _infer_external_func_ret_ty(self, pool: memhub.MemBuddy*, func_name: str, node: ast.AST*, fatal_if_unknown: int) -> mlirlite.LLVMType*:
"""Infer the return type of an external includes function; a whitelist miss may fail fast"""
if pool is None or func_name is None: return mlirlite.Int32(pool)
# extract the bare function name by stripping the SHA1 prefix
# (16 hex digits + '.'), e.g. "240a9a4157959a9f.parse" -> "parse".
bare_name: str = func_name
dot_pos: str = string.strrchr(func_name, 46) # 46 = ord('.')
if dot_pos is not None:
# the part after '.' is the bare function name
bare_name = dot_pos + 1
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
# functions returning i8* (truncating the pointer would be wrong)
if string.strcmp(bare_name, "strchr") == 0 or string.strcmp(func_name, "strchr") == 0: return i8_ptr_ty
if string.strcmp(bare_name, "strrchr") == 0 or string.strcmp(func_name, "strrchr") == 0: return i8_ptr_ty
if string.strcmp(bare_name, "strstr") == 0 or string.strcmp(func_name, "strstr") == 0: return i8_ptr_ty
if string.strcmp(bare_name, "strcpy") == 0 or string.strcmp(func_name, "strcpy") == 0: return i8_ptr_ty
if string.strcmp(bare_name, "strncpy") == 0 or string.strcmp(func_name, "strncpy") == 0: return i8_ptr_ty
if string.strcmp(bare_name, "memset") == 0 or string.strcmp(func_name, "memset") == 0: return i8_ptr_ty
if string.strcmp(bare_name, "memset32") == 0 or string.strcmp(func_name, "memset32") == 0: return i8_ptr_ty
if string.strcmp(bare_name, "memcpy") == 0 or string.strcmp(func_name, "memcpy") == 0: return i8_ptr_ty
if string.strcmp(bare_name, "memmove") == 0 or string.strcmp(func_name, "memmove") == 0: return i8_ptr_ty
# JSON parse functions return JsonValue* (pointer); include the json_parse alias
# (from json.__parser import parse as json_parse).
if string.strcmp(bare_name, "parse") == 0 or string.strcmp(func_name, "parse") == 0 or string.strcmp(func_name, "json_parse") == 0: return i8_ptr_ty
# MemBuddy methods (may use the external-function path when type info is lost).
# malloc: bare-name CExport cross-module call (uhci/desktop mm.malloc -> @malloc), else ret is inferred i32 and the 64-bit pointer truncates.
if string.strcmp(bare_name, "malloc") == 0 or string.strcmp(func_name, "malloc") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
if string.strcmp(func_name, "alloc") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
if string.strcmp(func_name, "calloc") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
if string.strcmp(func_name, "realloc") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
if string.strcmp(func_name, "alloc_buf") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
# VLogger module funcs (stubs not injected cross-module, pointers truncated to i32)
if string.strcmp(func_name, "get_logger") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
if string.strcmp(func_name, "fmt_buf") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
# Config module funcs (stubs not injected cross-module, i8* truncated to i32)
if string.strcmp(func_name, "get_includes_binary_dir") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
# HandlesType module func (returns ast.AST* union; inferred as i32 cross-module)
if string.strcmp(bare_name, "_parse_type_string") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
# Functions returning i64
if string.strcmp(func_name, "strlen") == 0: return mlirlite.Int64(pool)
if string.strcmp(func_name, "atoll") == 0: return mlirlite.Int64(pool)
# mlirlite name-length accessors return sizet (i64); the default i32 clashes with the declare
if string.strcmp(bare_name, "function_get_name_len") == 0 or string.strcmp(func_name, "function_get_name_len") == 0: return mlirlite.Int64(pool)
# Functions returning double
if string.strcmp(func_name, "atof") == 0: return mlirlite.Double(pool)
# Functions returning i8
if string.strcmp(func_name, "samestr") == 0: return mlirlite.Int8(pool)
# Win32 APIs returning a pointer (VOIDPTR/HANDLE); no stubs are injected, so infer
# i8* to keep the pointer from being truncated to i32 and crashing on dereference.
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
if string.strcmp(func_name, "VirtualAlloc") == 0: return i8_ptr_ty
if string.strcmp(func_name, "HeapAlloc") == 0: return i8_ptr_ty
if string.strcmp(func_name, "HeapReAlloc") == 0: return i8_ptr_ty
if string.strcmp(func_name, "HeapCreate") == 0: return i8_ptr_ty
if string.strcmp(func_name, "GetProcessHeap") == 0: return i8_ptr_ty
if string.strcmp(func_name, "GlobalAlloc") == 0: return i8_ptr_ty
if string.strcmp(func_name, "GlobalFree") == 0: return i8_ptr_ty
if string.strcmp(func_name, "GlobalLock") == 0: return i8_ptr_ty
if string.strcmp(func_name, "LocalAlloc") == 0: return i8_ptr_ty
if string.strcmp(func_name, "LocalFree") == 0: return i8_ptr_ty
if string.strcmp(func_name, "GetProcAddress") == 0: return i8_ptr_ty
if string.strcmp(func_name, "GetModuleHandleA") == 0: return i8_ptr_ty
if string.strcmp(func_name, "GetModuleHandleW") == 0: return i8_ptr_ty
# GetCommandLineA/W return LPSTR (i8*); without it a 64-bit pointer is truncated to i32
if string.strcmp(func_name, "GetCommandLineA") == 0: return i8_ptr_ty
if string.strcmp(func_name, "GetCommandLineW") == 0: return i8_ptr_ty
# gethostbyname returns hostent* (i8*)
if string.strcmp(func_name, "gethostbyname") == 0: return i8_ptr_ty
if string.strcmp(func_name, "LoadLibraryA") == 0: return i8_ptr_ty
if string.strcmp(func_name, "LoadLibraryW") == 0: return i8_ptr_ty
# Win32 handle-returning functions (HANDLE = void* = i8*)
if string.strcmp(func_name, "GetStdHandle") == 0: return i8_ptr_ty
if string.strcmp(func_name, "CreateFileA") == 0: return i8_ptr_ty
if string.strcmp(func_name, "CreateFileW") == 0: return i8_ptr_ty
if string.strcmp(func_name, "FindFirstFileA") == 0: return i8_ptr_ty
if string.strcmp(func_name, "FindFirstFileW") == 0: return i8_ptr_ty
# Win32 APIs returning i64 (SIZE_T)
if string.strcmp(func_name, "VirtualQuery") == 0: return mlirlite.Int64(pool)
if string.strcmp(func_name, "HeapSize") == 0: return mlirlite.Int64(pool)
if string.strcmp(func_name, "HeapCompact") == 0: return mlirlite.Int64(pool)
if string.strcmp(func_name, "GlobalSize") == 0: return mlirlite.Int64(pool)
# mlirlite build_* return-type inference: most return Value*, terminators return void
if string.strncmp(func_name, "build_", 6) == 0:
# build_* returning void (terminators and void-returning ops)
if string.strcmp(func_name, "build_ret") == 0 or string.strcmp(func_name, "build_ret_void") == 0 or string.strcmp(func_name, "build_store") == 0 or string.strcmp(func_name, "build_br") == 0 or string.strcmp(func_name, "build_cond_br") == 0 or string.strcmp(func_name, "build_unreachable") == 0 or string.strcmp(func_name, "build_switch") == 0:
return mlirlite.Void(pool)
# other build_* return Value* (pointer)
return mlirlite.Ptr(pool, mlirlite.Int8(pool))
# mlirlite type factories / const constructors / accessors return pointers (LLVMType* / Value*)
if string.strcmp(func_name, "Int1") == 0 or string.strcmp(func_name, "Int8") == 0 or string.strcmp(func_name, "Int16") == 0 or string.strcmp(func_name, "Int32") == 0 or string.strcmp(func_name, "Int64") == 0 or string.strcmp(func_name, "Ptr") == 0 or string.strcmp(func_name, "Func") == 0 or string.strcmp(func_name, "Array") == 0 or string.strcmp(func_name, "Struct") == 0 or string.strcmp(func_name, "Void") == 0 or string.strcmp(func_name, "Half") == 0 or string.strcmp(func_name, "Float") == 0 or string.strcmp(func_name, "Double") == 0 or string.strcmp(func_name, "FP128") == 0 or string.strcmp(func_name, "Label") == 0 or string.strcmp(func_name, "new_param_node") == 0 or string.strcmp(func_name, "new_phi_incoming") == 0 or string.strcmp(func_name, "const_int32") == 0 or string.strcmp(func_name, "const_int64") == 0 or string.strcmp(func_name, "ConstFloat") == 0 or string.strcmp(func_name, "ConstZero") == 0 or string.strcmp(func_name, "ConstNull") == 0 or string.strcmp(func_name, "ConstInt") == 0 or string.strcmp(func_name, "function_get_name") == 0 or string.strcmp(func_name, "param_get_ty") == 0 or string.strcmp(func_name, "value_get_ty") == 0 or string.strcmp(func_name, "value_get_next") == 0 or string.strcmp(func_name, "function_get_ret_ty") == 0 or string.strcmp(func_name, "function_get_params") == 0 or string.strcmp(func_name, "function_get_param_head") == 0 or string.strcmp(func_name, "param_get_next") == 0:
return mlirlite.Ptr(pool, mlirlite.Int8(pool))
# Default i32 placeholder: fatal_if_unknown=1 fails fast; =0 (Phase A mid-pass) stays silent.
if fatal_if_unknown != 0: self.Gen.TypeGenErr.fatal_error(node, f"external function return type is unregistered and cannot be inferred conclusively (defaulting to i32 would truncate pointers/i64; provide a stub declaration or add it to the whitelist): {func_name}", "TypeError")
return mlirlite.Int32(pool)
def _get_external_func_param_ty(self, pool: memhub.MemBuddy*, func_name: str, param_idx: int) -> mlirlite.LLVMType*:
"""Return the expected type of an external function's param_idx-th parameter, or None (fallback only)"""
if pool is None or func_name is None: return None
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
# Win32 API list: every non-pointer parameter is i64 in declare
# (ULONG/DWORD/SIZE_T are all mapped to i64 during LLVM stub translation)
win32_apis: list[str]* = ["VirtualAlloc", "VirtualFree", "VirtualProtect", "VirtualQuery", "VirtualLock", "VirtualUnlock", "HeapCreate", "HeapDestroy", "HeapAlloc", "HeapReAlloc", "HeapFree", "HeapSize", "HeapValidate", "HeapCompact", "GlobalAlloc", "GlobalFree", "GlobalLock", "GlobalUnlock", "GlobalSize", "LocalAlloc", "LocalFree", "GetLastError", "SetLastError", "Sleep", "SleepEx", "GetTickCount", "GetProcessHeap"]
# list iteration is unsupported; use range + get instead
api_count: sizet = len(win32_apis)
for ai in range(int(api_count)):
api_name: str = win32_apis.get(ai)
if string.strcmp(api_name, func_name) == 0: return i64_ty
# Allocators: size params are declared i64 (size_t); without promotion on bare-name fallback call/define widths mismatch; calloc/realloc size is idx 1
if string.strcmp(func_name, "malloc") == 0 or string.strcmp(func_name, "alloc") == 0 or string.strcmp(func_name, "alloc_buf") == 0:
if param_idx == 0: return i64_ty
if string.strcmp(func_name, "calloc") == 0:
if param_idx == 0 or param_idx == 1: return i64_ty
if string.strcmp(func_name, "realloc") == 0:
if param_idx == 1: return i64_ty
return None
def _is_sha1_mangled_call(self, call_name: str) -> int:
"""Return whether call_name is a cross-module Viper function ("{16 hex}.func")"""
if call_name is None: return 0
if GenApi.sha1_prefix_len(call_name) != 0: return 1
return 0
def _emit_llvm_memcpy_intrinsic(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, dst_val: mlirlite.Value*, src_val: mlirlite.Value*, num_val: mlirlite.Value*) -> mlirlite.Value*:
"""Map a memcpy call to the @llvm.memcpy intrinsic"""
if pool is None or builder is None or mod is None: return None
if dst_val is None or src_val is None or num_val is None: return None
# Declare void_ty at the top of the function to avoid later shadowing
void_ty: mlirlite.LLVMType* = mlirlite.Void(pool)
# Declare @llvm.memcpy (if not already declared)
memcpy_func: mlirlite.Function* = self.find_func_in_module(mod, "llvm.memcpy")
if memcpy_func is None:
memcpy_func = mlirlite.create_declare(pool, mod, "llvm.memcpy", void_ty)
if memcpy_func is not None:
i8_ptr_ty_d: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
i64_ty_d: mlirlite.LLVMType* = mlirlite.Int64(pool)
i1_ty_d: mlirlite.LLVMType* = mlirlite.Int1(pool)
mlirlite.add_param(pool, memcpy_func, i8_ptr_ty_d, "dst")
mlirlite.add_param(pool, memcpy_func, i8_ptr_ty_d, "src")
mlirlite.add_param(pool, memcpy_func, i64_ty_d, "len")
mlirlite.add_param(pool, memcpy_func, i1_ty_d, "isvolatile")
# Coercions: dst, src -> i8*; num -> i64
# Use the value_get_ty accessor for Ty to avoid local Value field-index offset
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
# dst → i8*
dst_coerced: mlirlite.Value* = dst_val
dst_ty: mlirlite.LLVMType* = mlirlite.value_get_ty(dst_val)
if self.Gen.ExprGenBase.is_ptr_type(dst_ty) != 0: dst_coerced = mlirlite.build_bitcast(builder, dst_val, i8_ptr_ty)
else:
dst_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(dst_ty)
if dst_bits != 0:
if dst_bits < 64: dst_coerced = mlirlite.build_zext(builder, dst_val, i64_ty)
dst_coerced = mlirlite.build_inttoptr(builder, dst_coerced, i8_ptr_ty)
# src → i8*
src_coerced: mlirlite.Value* = src_val
src_ty: mlirlite.LLVMType* = mlirlite.value_get_ty(src_val)
if self.Gen.ExprGenBase.is_ptr_type(src_ty) != 0: src_coerced = mlirlite.build_bitcast(builder, src_val, i8_ptr_ty)
else:
src_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(src_ty)
if src_bits != 0:
if src_bits < 64: src_coerced = mlirlite.build_zext(builder, src_val, i64_ty)
src_coerced = mlirlite.build_inttoptr(builder, src_coerced, i8_ptr_ty)
# num → i64
num_coerced: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, num_val, i64_ty)
if dst_coerced is None or src_coerced is None or num_coerced is None: return None
# Append the i1 false arg (isvolatile) - llvm.memcpy needs 4 parameters
false_val: mlirlite.Value* = mlirlite.ConstInt(pool, mlirlite.Int1(pool), 0, "false")
# Build the arg list via include accessors to avoid local Value field-index offset
# (f26cd4c78b4d554c.Value lacks GSListNode.Next; direct .Next access corrupts .Ty)
mlirlite.value_set_next(dst_coerced, src_coerced)
mlirlite.value_set_next(src_coerced, num_coerced)
if false_val is not None:
mlirlite.value_set_next(num_coerced, false_val)
mlirlite.value_set_next(false_val, None)
else: mlirlite.value_set_next(num_coerced, None)
# Emit the call via build_call (it accesses include Value fields with correct indices)
# _ll_name_needs_quote already exempts the llvm. prefix from quoting
result: mlirlite.Value* = mlirlite.build_call(builder, "llvm.memcpy", dst_coerced, 4, void_ty, 0)
# Return dst (matches C memcpy semantics: returns the dest pointer)
return dst_coerced
# _translate_call_with_kwargs: keyword-arg call; builds args in found_func.Params order (positional first, else match by param name)
# ============================================================
def _translate_call_with_kwargs(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, func_name: str, found_func: mlirlite.Function*, cargs: list[ast.AST*]*, can: sizet, keywords: list[ast.AST*]*, kw_count: sizet, trans: HT.Translator*) -> mlirlite.Value*:
"""Translate a call with keyword arguments, building args in parameter order"""
head: mlirlite.Value* = None
tail: mlirlite.Value* = None
actual_count: sizet = 0
# memcpy-only: collect independent refs of the first three Values (avoid shared alloca Next pollution)
mc_dst1: mlirlite.Value* = None
mc_src1: mlirlite.Value* = None
mc_num1: mlirlite.Value* = None
# Default-arg lookup for omitted params (same source as CallGenMixin non-kwargs path):
# module SHA1 is the "{sha1}.func" prefix or current module; the name+SHA1-keyed snapshot survives resets.
kw_callee_sha: str = None
kw_found_name: str = mlirlite.function_get_name(found_func)
if kw_found_name is not None:
if GenApi.sha1_prefix_len(kw_found_name) != 0:
kw_sha_buf: str = pool.alloc(17)
if kw_sha_buf is not None:
string.strncpy(kw_sha_buf, kw_found_name, 16)
kw_sha_buf[16] = '\0'
kw_callee_sha = kw_sha_buf
if kw_callee_sha is None and self.Gen.TypeGenBase is not None: kw_callee_sha = self.Gen.TypeGenBase.CurrentModuleSha1
kw_gd: GenTypes.FuncDefaultsEntry* = None
if kw_callee_sha is not None: kw_gd = self.Gen.CallGenLeafKit.find_func_defaults(func_name, kw_callee_sha)
kw_default_count: int = 0
kw_default_off: int = 0
if kw_gd is not None:
kw_default_count = kw_gd.DefaultCount
kw_default_off = kw_gd.ParamCount - kw_gd.DefaultCount
if kw_default_off < 0: kw_default_off = 0
# Walk the function parameter list (accessor gets Head)
param_idx: sizet = 0
cur_param: mlirlite.Param* = mlirlite.function_get_param_head(found_func)
while cur_param is not None:
arg_val: mlirlite.Value* = None
param_ty: mlirlite.LLVMType* = mlirlite.param_get_ty(cur_param)
if param_idx < can:
# Prefer the positional argument
arg: ast.AST* = cargs.get(param_idx)
if arg is not None:
# Pass the param type (char literal -> str param materializes a string; see translate_constant)
arg_val = self.Gen.ExprGen.translate_value(builder, pool, mod, arg, None, 0, trans, param_ty)
else:
# Find the matching keyword argument
param_name: str = mlirlite.param_get_name(cur_param)
if param_name is not None:
for ki in range(int(kw_count)):
kw: ast.Keyword* = ast.Keyword*(keywords.get(ki))
if kw is None or kw.arg is None or kw.value is None: continue
if self.Gen.CallGenCall._kwarg_name_matches(kw.arg, param_name) == 1:
arg_val = self.Gen.ExprGen.translate_value(builder, pool, mod, kw.value, None, 0, trans, param_ty)
break
# Param supplied by neither position nor keyword: fill with the definition-site default
# (index = param_idx - param_count + default_count); else fail-fast, never silently drop/zero.
if arg_val is None:
if kw_default_count > 0 and int(param_idx) >= kw_default_off:
kw_di: int = int(param_idx) - kw_default_off
if kw_di >= 0 and kw_di < kw_default_count:
arg_val = self.Gen.CallGenLeafKit._build_const_from_snapshot(pool, mod, kw_gd, kw_di, trans, builder)
if arg_val is None:
kw_node: ast.AST* = None
if keywords is not None and kw_count > 0: kw_node = keywords.get(0)
self.Gen.TypeGenErr.fatal_error(kw_node, f"a parameter of call '{func_name}' is passed neither positionally nor by keyword, " f"and the definition has no default value to fill in (refusing to silently drop/zero a parameter)", "TypeError")
if arg_val is not None:
# Coerce by parameter type (via accessor)
if param_ty is not None: arg_val = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val, param_ty)
arg_val.Next = None
if head is None: head = arg_val
else: tail.Next = arg_val
tail = arg_val
# memcpy-only: save independent refs of the first three args
if actual_count == 0: mc_dst1 = arg_val
elif actual_count == 1: mc_src1 = arg_val
elif actual_count == 2: mc_num1 = arg_val
actual_count += 1
param_idx += 1
cur_param = mlirlite.param_get_next(cur_param)
# declare and define both use found_func.Name: normal funcs carry the SHA1 prefix, t.CExtern/t.State/t.CExport use bare names (bare names desync call/define symbols)
call_name: str = func_name
found_name: str = mlirlite.function_get_name(found_func)
if found_name is not None: call_name = found_name
# Return type (via accessor)
call_ret_ty: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(found_func)
if call_ret_ty is None:
# found_func has a declare but return type is unreadable (accessor anomaly, rare guard):
# even if the whitelist misses, do not fail-fast; keep the default i32.
call_ret_ty = self._infer_external_func_ret_ty(pool, func_name, None, 0)
# memcpy -> @llvm.memcpy intrinsic (avoid a missing cross-module @memcpy declare under monomorphization)
if string.strcmp(func_name, "memcpy") == 0 and actual_count >= 3: return self._emit_llvm_memcpy_intrinsic(pool, builder, mod, mc_dst1, mc_src1, mc_num1)
return mlirlite.build_call(builder, call_name, head, actual_count, call_ret_ty, 0)