594 lines
37 KiB
Plaintext
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) |