Borrow exclusivity now covers direct uses of the owner, not just creating another borrow. A write or move of an owner (`c.x = 1`, `del`/move, a method call whose `self` is `&mut` or implicit) conflicts with any live borrow; a read (`c.x`, passing it on) conflicts with a live `&mut` borrow. `_BorrowCheckAccess` resolves the root binding of the access place, applies the NLL dead-borrow rule, and is wired into name loads, attribute loads, assignment targets, own moves (field/argument/assignment), and method receivers (the receiver's `self` kind is read from the parameter registry; implicit `self` is treated conservatively as a write). Reads inside a borrow expression are suppressed so `&mut x` is not reported against its own new borrow. The LSP mirrors the same three checks (write target, owner read, method receiver) with the same messages.
685 lines
44 KiB
Plaintext
685 lines
44 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: char*) -> 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, "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, "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: char*, 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: char*, 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: char* = call_name + 17
|
|
cls_nm: char* = None
|
|
meth_nm: char* = rest
|
|
# take the last '.': the method has none, and the first '.' would truncate qualified type args
|
|
dot_r: char* = 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: char* = VpiReader._lookup_vpi_param_annots(sha1_buf, cls_nm, meth_nm)
|
|
# Ownership: an `own` parameter takes the argument's ownership. A local `own` binding is
|
|
# moved in; `new` / calls are already owned; a borrowed value is rejected.
|
|
if trans is not None and cargs is not None and self.Gen.CallGenBase._param_own_known(call_name) != 0:
|
|
arg_n_o: int = int(len(cargs))
|
|
ai_o: int = 0
|
|
while ai_o < arg_n_o:
|
|
an_o: ast.AST* = cargs.get(sizet(ai_o))
|
|
ai_o += 1
|
|
if an_o is None: continue
|
|
pidx_o: int = ai_o - 1 + self_off
|
|
if self.Gen.CallGenBase._param_is_own(call_name, pidx_o) == 0: continue
|
|
if an_o.kind() == ast.ASTKind.Name:
|
|
nm_o: ast.Name* = ast.Name*(an_o)
|
|
if nm_o.id is None: continue
|
|
src_o: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm_o.id)
|
|
if src_o is not None and self.Gen.VarGen._SlifeOwned(src_o) != 0:
|
|
# Moving the binding into the callee conflicts with any live borrow of it.
|
|
self.Gen.StmtGen._BorrowCheckAccess(trans, an_o, 3, an_o)
|
|
sh_o: mlirlite.Value* = self.Gen.VarGen._SlifeShadow(src_o)
|
|
if sh_o is not None: self.Gen.VarGen._SlifeNoteStore(pool, builder, sh_o, 0)
|
|
self.Gen.VarGen._FreedMark(src_o)
|
|
continue
|
|
self.Gen.TypeGenErr.fatal_error(an_o, f"cannot pass '{nm_o.id}' to an `own` parameter: it is not owned here; move an `own` binding or pass `new ...`", "LifetimeError")
|
|
elif an_o.kind() == ast.ASTKind.New:
|
|
nw_o: ast.New* = ast.New*(an_o)
|
|
if nw_o is not None and nw_o.Source == ast.NEW_SRC_MEMGR:
|
|
self.Gen.TypeGenErr.fatal_error(an_o, "cannot pass a `new memgr(...)` allocation to an `own` parameter: pool memory is reclaimed by its manager, not by `free`", "LifetimeError")
|
|
elif an_o.kind() == ast.ASTKind.UnaryOp:
|
|
self.Gen.TypeGenErr.fatal_error(an_o, "cannot pass a borrow to an `own` parameter: the callee takes ownership", "LifetimeError")
|
|
# Reference parameters: a mutable reference only accepts `&mut value` (or a `&mut`
|
|
# binding); a read-only borrow may fill a read-only parameter.
|
|
if trans is not None and cargs is not None and trans.StrictOBS != 0 \
|
|
and self.Gen.CallGenBase._param_ref_known(call_name) != 0:
|
|
arg_n_r: int = int(len(cargs))
|
|
ai_r: int = 0
|
|
while ai_r < arg_n_r:
|
|
an_r: ast.AST* = cargs.get(sizet(ai_r))
|
|
ai_r += 1
|
|
if an_r is None: continue
|
|
pidx_r: int = ai_r - 1 + self_off
|
|
pk_r: int = self.Gen.CallGenBase._param_ref_kind(call_name, pidx_r)
|
|
if pk_r != 2: continue
|
|
sk_r: int = self.Gen.TypeGenResolve.ref_expr_kind(an_r)
|
|
if sk_r == 0 and an_r.kind() == ast.ASTKind.Name:
|
|
nm_r2: ast.Name* = ast.Name*(an_r)
|
|
if nm_r2.id is not None:
|
|
sk_r = self.Gen.VarGen.var_ref_kind(trans.SymTab, nm_r2.id)
|
|
if sk_r == 0: continue
|
|
if sk_r == 1:
|
|
self.Gen.TypeGenErr.fatal_error(an_r, "cannot pass a read-only borrow to a `&mut` parameter: create it with `&mut value`", "LifetimeError")
|
|
# Strict OBS: a nullable binding/field may be None and must not flow into a non-nullable
|
|
# parameter. Same-module callees use the nullable registry, cross-module ones use their
|
|
# vpi param annotations; an unknown callee signature is left alone.
|
|
if trans is not None and trans.StrictOBS != 0 and cargs is not None:
|
|
arg_n_chk: int = int(len(cargs))
|
|
ai_chk: int = 0
|
|
while ai_chk < arg_n_chk:
|
|
an_chk: ast.AST* = cargs.get(sizet(ai_chk))
|
|
ai_chk += 1
|
|
if an_chk is None: continue
|
|
if self.Gen.ExprGenAttr._base_is_nullable(pool, trans, an_chk) == 0: continue
|
|
pidx_chk: int = ai_chk - 1 + self_off
|
|
checked_chk: int = 0
|
|
allow_chk: int = 0
|
|
if self.Gen.CallGenBase._param_nullable_known(call_name) != 0:
|
|
checked_chk = 1
|
|
if self.Gen.CallGenBase._param_is_nullable(call_name, pidx_chk) != 0: allow_chk = 1
|
|
elif prm_annots is not None:
|
|
checked_chk = 1
|
|
if VpiReader._vpi_param_annot_seg_has_nullable(prm_annots, pidx_chk) != 0: allow_chk = 1
|
|
if checked_chk == 0: continue
|
|
if allow_chk == 0:
|
|
pf_chk: int = GenApi.sha1_prefix_len(call_name)
|
|
short_chk: char* = call_name + pf_chk if pf_chk != 0 else call_name
|
|
self.Gen.TypeGenErr.fatal_error(an_chk, f"parameter {ai_chk} of '{short_chk}' is not nullable: a nullable value may be None; declare it as `T?` or unwrap it with `match` first", "TypeError")
|
|
# 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 None and trans is not None and trans.Module is not None:
|
|
# vpi text could not be resolved in this file's context (e.g. a generic
|
|
# class name). The injected declare was built with the caller's context,
|
|
# so its parameter type is the authoritative ABI type.
|
|
dcl: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(trans.Module, call_name)
|
|
if dcl is not None:
|
|
p_d: mlirlite.Param* = mlirlite.function_get_param_head(dcl)
|
|
pi_d: int = 0
|
|
while p_d is not None and pi_d < idx:
|
|
p_d = mlirlite.param_get_next(p_d)
|
|
pi_d += 1
|
|
if p_d is not None: tgt_ty = mlirlite.param_get_ty(p_d)
|
|
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: char* = 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, tgt_ty)
|
|
# "str / bytes are not generic pointer wildcards": void* / 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 mlirlite.LLVMType.Int(bits_pi):
|
|
# A pointer passed where the callee's parameter degraded to an integer
|
|
# (e.g. `rawptr` resolved as uint64 in a stale/mis-typed stub): convert at the
|
|
# call boundary instead of failing the implicit-cast rule, so passing a
|
|
# rawptr never breaks the build and the callee still receives the address.
|
|
if bits_pi > 0:
|
|
nv_p2: mlirlite.Value* = mlirlite.build_ptrtoint(builder, val, tgt_ty)
|
|
if nv_p2 is not None: return nv_p2
|
|
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: char*, 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: char* = func_name
|
|
dot_pos: char* = 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 ExportT 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: char*, 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: char* = 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: char*) -> 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: char*, 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: char* = None
|
|
kw_found_name: char* = 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: char* = 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: char* = 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, None, trans)
|
|
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.ExternT/t.State/t.ExportT use bare names (bare names desync call/define symbols)
|
|
call_name: char* = func_name
|
|
found_name: char* = 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) |