Files
ViperC/App/lib/core/Gen/Call/CallGenLib.vp
T
TermiNexus 429de97083 fix(call): 泛型特化类跨模块 declare 按模板 vpi 注解对齐形参口径
特化名({sha}.list[str].__init__)在 vpi 参数注解缓存中无精确键——键段取自 class 行文本,只存模板 list[T]——定义侧形参注解取不到,调用点 declare 停在调用点实参口径(elem_size 字面量 i32 vs 定义侧 t.CSizeT=i64;池实参 MemBuddy*/i8* vs 定义侧 MemManager*),并经 .stub.ll 传播到未调用该函数的消费方模块

新增 VpiReader._resolve_vpi_param_annot_seg_template:按 {sha1}.{base}[T].{method} 定位模板条目、用特化实参替换段内类型形参 token 后再解析,未命中返回 None 不兜底;_coerce_args_to_def_params 在精确键未命中且类名含 [ 时改走模板退查并置 tpl_src,使 _coerce_arg_value_to_ty 对同为具体结构体指针但基类型不同补 bitcast;_call_method_on_ptr 的跨模块分支补建 declare 前的 coerce(先例 a83d183 的三处调用点)

同时把 {sha}.{cls}.{method} 的切分由首个 . 改为末个 .(CallGenLib/CallGenBase 两处):method 不含 .,首个 . 会把含模块限定名的类型实参截断(list[3571eabe5a83d798.AST|t.CPtr] 截成无 ] 的类名 ⇒ 模板退查必 miss)

另修 VpiReader 一处 double-free:_subst_template_arg 早退分支返回入参本身,替换前释放原缓冲需先判同一性(三条路径各释放一次)

效果: 危险级签名不一致 TransPyV 8->4->0、Test/AstTest 6->2->0,ZlibTest/WebTest/FakeDuckTest/ListTest/GenericTest2/VectorGenericTest 保持 0;4 个目标符号 define/declare 共 148 处全部统一为 MemManager*+i64(0 处旧口径);自举达全树逐字节不动点(exe sha256 相同、temp/** 635 文件零差异);AstTest 及既有测试全 PASS 且无 ASan 报告
2026-09-16 09:31:57 +08:00

930 lines
51 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import t, c
from stdint import *
import ast
import llvmlite
import memhub
import string
import stdlib
import lib.core.Gen.Translator as HT
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.core.Gen.GenApi as GenApi
from . import CallGenBase
# ============================================================
# CallGenLib - CallGenMixin 拆分(阶段 C):C 库声明 / 返回类型推断 /
# 变长参数 / memcpy / kwargs 调用辅助方法
#
# 继承 CallGenBase(1 级,TPV self 调用仅支持 1 级父类)。
# 跨 Mixin 调用统一走 HandlesExprCall 委托(经 _g_gen)。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
class CallGenLib(CallGenBase.CallGenBase):
"""C 库声明 / 返回类型推断 / kwargs 调用辅助(继承 Base)"""
def _ensure_c_lib_declare(self, pool: rowptr[memhub.MemBuddy],
mod: llvmlite.LLVMModule | t.CPtr,
name: str) -> llvmlite.Function | t.CPtr:
"""为已知的 C 库函数创建 declare 声明"""
if pool is None or mod is None or name is None:
return None
i8_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
i32_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool)
i64_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int64(pool)
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty)
ret_ty: llvmlite.LLVMType | t.CPtr = None
params: int = 0 # 参数数量
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
# 创建 declare
fn: llvmlite.Function | t.CPtr = llvmlite.create_declare(pool, mod, name, ret_ty)
if fn is None:
return None
# 添加参数
if params == 1:
if string.strcmp(name, "abs") == 0:
llvmlite.add_param(pool, fn, i32_ty, "n")
elif string.strcmp(name, "labs") == 0:
llvmlite.add_param(pool, fn, i64_ty, "n")
else:
llvmlite.add_param(pool, fn, i8_ptr_ty, "s")
elif params == 2:
llvmlite.add_param(pool, fn, i8_ptr_ty, "dst")
llvmlite.add_param(pool, fn, i8_ptr_ty, "src")
elif params == 3:
if string.strcmp(name, "memset") == 0:
llvmlite.add_param(pool, fn, i8_ptr_ty, "dst")
llvmlite.add_param(pool, fn, i32_ty, "c")
llvmlite.add_param(pool, fn, i64_ty, "n")
else:
llvmlite.add_param(pool, fn, i8_ptr_ty, "dst")
llvmlite.add_param(pool, fn, i8_ptr_ty, "src")
llvmlite.add_param(pool, fn, i64_ty, "n")
return fn
def _promote_clib_call_args(self, pool: rowptr[memhub.MemBuddy],
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
call_name: str,
head: llvmlite.Value | t.CPtr,
count: int) -> llvmlite.Value | t.CPtr:
"""裸名 C 库函数实参按标准签名提升(B3)。
跨模块裸名调用(string.memset → @memset)时 _ensure_method_declare 用
实参类型建 declare:第三参字面量 32=i32 把定义 sizet(i64) 固化窄
→ declare/call @memset(i8*,i32,i32) vs 定义 @memset(i8*,i32,i64),
ABI 宽度不一致(VKernel process.vp B3)。调用点先把第三参窄整数
实参提升到 i64,declare 与 call 均按 i64 生成 → 与定义一致。
未命中白名单原样返回 head。
"""
if pool is None or builder is None or call_name is None or head is None:
return head
# 仅裸名(无 '.' 分隔 → 无 sha1 前缀);带前缀走 vpi 定义侧解析
if string.strchr(call_name, 46) is not None:
return head
# 白名单:size_t(i64) 长度参数所在下标(与 _ensure_c_lib_declare 一致)。
# vsnprintf 非变参(第二参即 sizet 长度)——viperlib.vp `export def
# vsnprintf(buf, size: sizet, fmt, ap)`,跨模块裸名调用时字面量 1024
# 固化 i32 vs 定义 i64,与 memset 同族。
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
# 定位长度参数(index size_idx)
cur: llvmlite.Value | t.CPtr = 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: llvmlite.LLVMType | t.CPtr = llvmlite.Int64(pool)
promoted: llvmlite.Value | t.CPtr = None
match cur.Ty:
case llvmlite.LLVMType.Int(bits):
if bits >= 64:
return head
if cur.IsConst != 0:
# 常量:直接 i64 常量(位模式保留,无符号长度语义)
promoted = llvmlite.const_int(pool, 64, cur.IntVal)
else:
# SSA 值:zext 到 i64(size_t 无符号长度)
promoted = llvmlite.build_zext(builder, cur, i64_ty)
case _:
return head
if promoted is None:
return head
# 重建实参链表:index size_idx 替换为提升后的值
new_head: llvmlite.Value | t.CPtr = None
new_tail: llvmlite.Value | t.CPtr = None
cur2: llvmlite.Value | t.CPtr = head
i2: int = 0
while cur2 is not None:
node_v: llvmlite.Value | t.CPtr = 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
#
# B3 同族通用化:_ensure_method_declare 用调用点实参类型建 declare,字面量
# 实参(i32 0/1016/512)把定义侧窄整型形参(i8/i16)固化宽(或把宽形参固化
# 窄)→ declare 与 define 宽度不一致(跨模块 IR/ABI 失配,x86-64 下侥幸正确:
# 调用侧写 32 位寄存器清高 32 位,callee 按 i64 读高位是 0)。本函数在调用点
# 按定义侧(vpi 参数注解)重建/转换实参,随后 _ensure_method_declare 依 head
# 建 declare → call/declare/define 三者一致。
#
# 处理"带 sha1 前缀的被调方限定名"两种形态:
# 1) {sha1}.{func} —— 模块级函数,实参段 0 即首参(无 self 占位);
# 2) {sha1}.{cls}.{method} —— 类方法,vpi 注解段 0 为 self 空段,而实参
# 链首元素正是 self → 段下标与实参下标一一对应(与
# _ensure_method_declare 里直接以 pi 作段索引的约定一致)。
# 此前仅支持形态 1(遇 '.' 直接 return head),方法调用点实参从不按定义侧
# 扩宽 → `default_fs.win_read(0)` 传 i32 而 define 为 uint64(N5)。
# 裸名 C 库函数由 _promote_clib_call_args 与 _ensure_c_lib_declare 标准
# 签名处理。未命中/不可解原样保留(不兜底改型)。
# ============================================================
def _coerce_args_to_def_params(self, pool: rowptr[memhub.MemBuddy],
builder: llvmlite.IRBuilder | t.CPtr,
call_name: str,
head: llvmlite.Value | t.CPtr,
count: int,
cargs: list[ast.AST | t.CPtr] | t.CPtr = None,
self_off: int = 0,
trans: HT.Translator | t.CPtr = None) -> llvmlite.Value | t.CPtr:
"""把跨模块调用实参 coerce 到 vpi 定义侧形参类型,返回重建的实参链
cargs/self_off/trans:实参 AST 列表与其在实参链中的偏移(模块级函数 0、
方法 1——链首元素是 self,而 vpi 段 0 即 self 占位段)。仅用于非常量
(SSA)整型扩宽时的**源符号性**判定(与 ExprGenBase._zext_or_sext_val
同源);缺省时该判定退化为有符号(sext),与 coerce_to_type 默认一致。
"""
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
# 拆 {sha1}.{rest}:rest 含 '.' 则为 {cls}.{method},否则为 {func}
rest: str = call_name + 17
cls_nm: str = None
meth_nm: str = rest
# 取末个 '.':method 不含 '.',首个 '.' 会把含模块限定名的类型实参截断
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'
# 注意:不能用 self._vpi_method_param_annot —— 其形参校验含
# `cls_name is None → return None`,而模块级函数必传 cls_name=None,
# 于是恒返回 None → 本函数永不生效(ABI 收敛首轮/二轮均未生效的根因)。
# 改为直接 ensure 加载 + 直查缓存(键 {sha1}.{func},与 cls_name=None 匹配)。
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)
# 泛型特化类({sha}.list[str].__new__):定义侧注解写在**模板类**上
# (vpi 键段为 "list[T]",见 VpiReader._cache_vpi_param_annots_text 的
# 类名取值),特化名的精确键必然遗漏 ⇒ 逐段退查模板注解并把类型形参
# 替换为特化实参(VpiReader._resolve_vpi_param_annot_seg_template,
# 与 _lookup_vpi_ret_annot 的泛型特化归一化匹配同源)。tpl_src=1 使
# 指针口径也对齐定义侧(MemBuddy* → MemManager*)。
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: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool)
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, llvmlite.Int8(pool))
new_head: llvmlite.Value | t.CPtr = None
new_tail: llvmlite.Value | t.CPtr = None
cur: llvmlite.Value | t.CPtr = head
idx: int = 0
while cur is not None and idx < count:
node_v: llvmlite.Value | t.CPtr = cur
tgt_ty: llvmlite.LLVMType | t.CPtr = 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:
# 实参 AST(源符号性判定用):链下标 idx 减 self 偏移即 AST 下标
arg_idx: int = idx - self_off
arg_node: ast.AST | t.CPtr = None
if cargs is not None and arg_idx >= 0 and arg_idx < len(cargs):
arg_node = cargs.get(arg_idx)
node_v = self._coerce_arg_value_to_ty(
pool, builder, cur, 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 - 单个实参 Value 转换到目标形参类型
#
# 整型:
# - 同宽 → 原样;
# - 窄化(目标更窄,本簇主场景 i32→i8/i16)→ 常量按目标宽度重建常量,
# SSA 用 trunc(trunc 与符号无关,窄化安全);
# - 扩宽(定义侧形参更宽)→ 常量按目标宽度重建常量;SSA 按**源**符号性
# 扩宽(zext/sext,真源 ExprGenBase._zext_or_sext_val;同规则先例
# ExprGenBase.coerce_to_type / CallGenCtor._translate_t_type_cast /
# CallGenLib._promote_clib_call_args)→ declare 由扩宽后的实参链建立,
# declare/call/define 三者同宽(此前 SSA 原样返回 → 与定义侧宽度不一致)。
# 指针:实参与目标皆指针且"泛型 i8* 性"不同时 bitcast(如 i32*→i8*),
# 使 declare 指针类型与 define 一致;同为 i8* 或同为具体 struct 指针
# 则原样保留(避免把 list*/Struct* 无谓降级为 i8* 的 IR 噪声)。
# 其它(结构体值/void 等)→ 原样保留。不可转换亦原样返回(不兜底改型)。
# ============================================================
def _coerce_arg_value_to_ty(self, pool: rowptr[memhub.MemBuddy],
builder: llvmlite.IRBuilder | t.CPtr,
val: llvmlite.Value | t.CPtr,
tgt_ty: llvmlite.LLVMType | t.CPtr,
node: ast.AST | t.CPtr = None,
trans: HT.Translator | t.CPtr = None,
tpl_src: int = 0) -> llvmlite.Value | t.CPtr:
"""把单个实参 Value 转换到目标形参类型,无法安全转换时原样返回
node/trans:实参 AST 与其翻译器(仅 SSA 整型扩宽的源符号性判定用,
委托 ExprGenBase._zext_or_sext_val;缺省退化为有符号 sext)。
tpl_src:目标类型来自**泛型模板类**注解(特化名精确键遗漏 → 退查模板 +
类型形参替换,见 VpiReader._resolve_vpi_param_annot_seg_template)⇒
指针口径亦可对齐定义侧(同为具体结构体指针但基类型不同时 bitcast)。
"""
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 llvmlite.LLVMType.Int(bits_a):
match tgt_ty:
case llvmlite.LLVMType.Int(bits_b):
if bits_b == bits_a:
return val
if bits_b < bits_a:
if val.IsConst != 0:
nv_c: llvmlite.Value | t.CPtr = llvmlite.const_int(
pool, bits_b, val.IntVal)
if nv_c is not None:
return nv_c
else:
nv_t: llvmlite.Value | t.CPtr = llvmlite.build_trunc(
builder, val, tgt_ty)
if nv_t is not None:
return nv_t
# 扩宽(实参窄于形参,N5):字面量/默认值此前保持 i32
# 而 declare/define 已是 i64(_ensure_method_declare
# 只改 declare 不改实参)→ 调用实参与签名失配。
# 仅编译期常量在此重建:const_int 按十进制值打印,
# i32 -1 → i64 -1(sext 语义)、i32 4294967295 →
# i64 4294967295(zext 语义),各自保持原类型数值,
# 无需 AST 判符号性。
if val.IsConst != 0:
nv_w: llvmlite.Value | t.CPtr = llvmlite.const_int(
pool, bits_b, val.IntVal)
if nv_w is not None:
return nv_w
return val
# 非常量(SSA)扩宽:此前原样返回,declare 与 call 保持
# 调用点窄宽度,而定义侧(vpi 形参注解)更宽 → 同一
# 符号 declare/call 与 define 宽度不一致(includes/
# requests/__init__.vp `string.strncpy(..., host_len)`
# 3 参 i32 而 string.vp 定义 `n: t.CSizeT`=i64;同模块/
# 同依赖的 strncpy 常量实参分支已扩宽成 i64,SSA 实参
# 分支未扩宽 → 同一模块两条 declare 宽度不同)。
# 扩宽方向由**源**符号性决定(与 coerce_to_type /
# CallGenCtor 同一真源 ExprGenBase._zext_or_sext_val:
# i1/i8、无符号源 → zext;否则 sext),与目标类型无关。
# 扩宽后 declare 由本函数返回的新实参链建立(param_head
# → _ensure_method_declare),declare/call/define 三者同宽。
if bits_b > bits_a:
if self.Gen.ExprGenBase._zext_or_sext_val(
pool, val, node, trans) != 0:
nv_zw: llvmlite.Value | t.CPtr = llvmlite.build_zext(
builder, val, tgt_ty)
if nv_zw is not None:
return nv_zw
else:
nv_sw: llvmlite.Value | t.CPtr = llvmlite.build_sext(
builder, val, tgt_ty)
if nv_sw is not None:
return nv_sw
return val
case _:
pass
case llvmlite.LLVMType.Ptr(pointee_a):
match tgt_ty:
case llvmlite.LLVMType.Ptr(pointee_b):
ia: int = 0
match pointee_a:
case llvmlite.LLVMType.Int(pa_bits):
if pa_bits == 8:
ia = 1
case _:
pass
ib: int = 0
match pointee_b:
case llvmlite.LLVMType.Int(pb_bits):
if pb_bits == 8:
ib = 1
case _:
pass
if ia != ib:
nv_p: llvmlite.Value | t.CPtr = llvmlite.build_bitcast(
builder, val, tgt_ty)
if nv_p is not None:
return nv_p
elif tpl_src != 0:
# 模板注解来源:两侧同为具体结构体指针但基类型不同
# (实参 MemBuddy* vs 定义侧形参 MemManager*,注解
# `memhub.MemManager | t.CPtr`)→ bitcast 使 declare
# 指针口径与 define 一致(位不变、ABI 无影响)。
# 非模板来源保持既有"具体指针原样保留"策略,
# 避免把 list*/Struct* 无谓降级为 i8* 的 IR 噪声。
nv_p2: llvmlite.Value | t.CPtr = llvmlite.build_bitcast(
builder, val, tgt_ty)
if nv_p2 is not None:
return nv_p2
case _:
pass
case _:
# 聚合值 → 指针:rvalue 结构体/数组落栈取址(N18 同源补齐)。
# FuncGenDef 形参一律 lowering 为 Ptr(struct/array),Viper 无
# "按值传结构体"形参;跨模块(declare 由 stub 建、found_func
# 为 None)时上游 coerce_to_type 不参与,故在此按 tgt_ty 为
# 指针 + 实参为聚合形态补 materialize。
match tgt_ty:
case llvmlite.LLVMType.Ptr(_):
vk_a: int = val.Ty.kind()
if vk_a == llvmlite.LLVMType.Struct or vk_a == llvmlite.LLVMType.Array:
tmp_a: llvmlite.Value | t.CPtr = self.Gen.VarGen._alloca_at_entry(
builder, val.Ty)
if tmp_a is not None:
llvmlite.build_store(builder, val, tmp_a)
return tmp_a
case _:
pass
return val
def _infer_external_func_ret_ty(self, pool: rowptr[memhub.MemBuddy],
func_name: str,
node: ast.AST | t.CPtr,
fatal_if_unknown: int) -> llvmlite.LLVMType | t.CPtr:
"""根据函数名推断外部 includes 函数的返回类型
支持三种函数名形式:
1. 裸名: "parse", "strlen"
2. 别名: "json_parse"(from X import parse as json_parse)
3. 带 SHA1 前缀: "240a9a4157959a9f.parse"(跨模块调用 mangled name)
fatal_if_unknown: 白名单全部未命中时的行为。
=1(纯裸名外部函数,无法收敛)→ fatal_error fail-fast(默认 i32
会截断指针/i64,静默生成导致运行时数据损坏,不兜底原则)。
=0(Phase A 中间遍,_deps_pending 下遍重译可收敛)→ 返回默认
i32 占位,不输出 WARN(下遍依赖就绪后走 text.ll/vpi 精确解析)。
"""
if pool is None or func_name is None:
return llvmlite.Int32(pool)
# 提取裸函数名:去掉 SHA1 前缀(如 "240a9a4157959a9f.parse" → "parse")
# SHA1 前缀是 16 位十六进制 + '.',检查是否有 '.' 分隔
bare_name: str = func_name
dot_pos: str = string.strrchr(func_name, 46) # 46 = ord('.')
if dot_pos is not None:
# '.' 后的部分是裸函数名
bare_name = dot_pos + 1
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, llvmlite.Int8(pool))
# 返回 i8* 的函数(指针返回值,截断会导致错误)
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 解析函数返回 JsonValue*(指针)
# from json.__parser import parse as json_parse → 别名 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 方法(当类型信息丢失时可能走外部函数路径)
# 存疑
# malloc:裸名 CExport 跨模块调用(uhci/desktop 等 mm.malloc → @malloc)
# 在模块归属解析失败(mod_ret_sha1=None)或 vpi/text.ll 未命中时回退到
# 此白名单。缺失导致返回类型被推断为 i32 → call i32 @malloc 截断 64 位
# 指针 → mm.malloc 实际返回非空但调用方判 None(uhci frame list /
# desktop bg_row_buf 根因)。calloc/realloc/alloc 均已登记,malloc 遗漏。
if string.strcmp(bare_name, "malloc") == 0 or string.strcmp(func_name, "malloc") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
if string.strcmp(func_name, "alloc") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
if string.strcmp(func_name, "calloc") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
if string.strcmp(func_name, "realloc") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
if string.strcmp(func_name, "alloc_buf") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
# VLogger 模块函数(跨模块调用时 stub 未注入,返回指针被截断为 i32 导致崩溃)
if string.strcmp(func_name, "get_logger") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
if string.strcmp(func_name, "fmt_buf") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
# Config 模块函数(跨模块调用时 stub 未注入,返回 i8* 被 inttoptr i32 截断导致路径损坏)
if string.strcmp(func_name, "get_includes_binary_dir") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
# HandlesType 模块函数(返回 ast.AST|t.CPtr 联合类型,跨模块推断为 i32 导致指针截断)
if string.strcmp(bare_name, "_parse_type_string") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
# 返回 i64 的函数
if string.strcmp(func_name, "strlen") == 0:
return llvmlite.Int64(pool)
if string.strcmp(func_name, "atoll") == 0:
return llvmlite.Int64(pool)
# llvmlite 函数名长度访问器返回 t.CSizeT(i64):stub/vpi 尚未注入时若
# 默认 i32 会与 declare 的 i64 签名不一致(call i32 vs declare i64),
# 与 function_get_name 指针白名单同理须登记返回类型。
if string.strcmp(bare_name, "function_get_name_len") == 0 or \
string.strcmp(func_name, "function_get_name_len") == 0:
return llvmlite.Int64(pool)
# 返回 double 的函数
if string.strcmp(func_name, "atof") == 0:
return llvmlite.Double(pool)
# 返回 i8 的函数
if string.strcmp(func_name, "samestr") == 0:
return llvmlite.Int8(pool)
# Win32 API 返回指针(VOIDPTR/HANDLE)的函数
# stubs 翻译时未注入,需推断返回 i8* 避免指针被截断为 i32 导致解引用崩溃
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, llvmlite.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 返回 LPSTR(i8*):缺则 64 位指针被截成 i32 →
# inttoptr 后 strlen 读到无效地址(SysLibTest argv 段 ASAN 崩溃,
# 非 ASAN 环境随机表现:IDE 0xC0000005 / 部分环境侥幸可读)。
if string.strcmp(func_name, "GetCommandLineA") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "GetCommandLineW") == 0:
return i8_ptr_ty
# gethostbyname 返回 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 返回函数(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 API 返回 i64 (SIZE_T) 的函数
if string.strcmp(func_name, "VirtualQuery") == 0:
return llvmlite.Int64(pool)
if string.strcmp(func_name, "HeapSize") == 0:
return llvmlite.Int64(pool)
if string.strcmp(func_name, "HeapCompact") == 0:
return llvmlite.Int64(pool)
if string.strcmp(func_name, "GlobalSize") == 0:
return llvmlite.Int64(pool)
# llvmlite build_* 函数返回类型推断
# stubs 翻译时未注入,需推断返回类型避免指针被截断为 i32
# 大多数 build_* 函数返回 Value*(指针),少数终止指令返回 void
if string.strncmp(func_name, "build_", 6) == 0:
# 返回 void 的 build_* 函数(终止指令和无返回值指令)
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 llvmlite.Void(pool)
# 其他 build_* 函数返回 Value*(指针)
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
# llvmlite 类型工厂函数返回 LLVMType*(指针)
# Int1/Int8/Int16/Int32/Int64/Ptr/Func/Array/Struct/Void/Half/Float/Double/FP128/Label
# new_param_node/new_phi_incoming 返回 ParamNode*/PhiIncoming*(指针)
# const_int32/const_int64/ConstFloat/ConstZero/ConstNull/ConstInt 返回 Value*(指针)
# function_get_name/param_get_ty 等访问器返回指针
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 llvmlite.Ptr(pool, llvmlite.Int8(pool))
# 默认 i32:无登记、无模块归属、白名单全部未命中的外部函数。
# fatal_if_unknown=1(纯裸名外部调用,无法收敛)→ fail-fast 报错:
# 默认 i32 会截断指针/i64,静默生成导致运行时数据损坏(不兜底原则)。
# fatal_if_unknown=0(Phase A 中间遍,_deps_pending 下遍重译收敛)→
# 保持默认 i32 占位,不输出 WARN(下遍依赖就绪后走 text.ll/vpi 精确解析)。
if fatal_if_unknown != 0:
self.Gen.TypeGenErr.fatal_error(
node,
f"外部函数返回类型未登记且无法收敛推断(默认 i32 会截断指针/i64,需提供 stub 声明或加入白名单): {func_name}",
"TypeError")
return llvmlite.Int32(pool)
def _get_external_func_param_ty(self, pool: rowptr[memhub.MemBuddy],
func_name: str,
param_idx: int) -> llvmlite.LLVMType | t.CPtr:
"""获取外部函数第 param_idx 个参数的期望类型,None 表示用字面量原类型
对 Win32 API 函数的所有非指针参数返回 i64,匹配 declare 中
ULONG/SIZE_T 被统一翻译为 i64 的情况。
"""
if pool is None or func_name is None:
return None
i64_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int64(pool)
# Win32 API 函数列表:所有非指针参数在 declare 中都是 i64
# (因为 ULONG/DWORD/SIZE_T 在 LLVM stubs 翻译时都被映射为 i64)
win32_apis: list[str] | t.CPtr = [
"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 迭代不支持,用 range + get 替代
api_count: sizet = len(win32_apis)
for ai in range(api_count):
api_name: str = win32_apis.get(ai)
if string.strcmp(api_name, func_name) == 0:
return i64_ty
# 内存分配函数:size 参数在声明中为 i64(size_t),裸名回退时
# 不提升则 call(i32 size) 与 define(i64 size) 不匹配(desktop start
# 第 4 次 mm.malloc(w*4) 返回值被破坏收到 0x0 根因;HelloWorld
# vpsdk 的 malloc 同理)。calloc/realloc 的 size 参数在 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:
"""判断 call_name 是否为跨模块 Viper 函数("{16位小写hex}.func" 形式)
C 库函数(strlen 等裸名)和 Win32 API(VirtualAlloc 等裸名)返回 0,
不参与精确返回类型解析(走 _infer_external_func_ret_ty 白名单)。
判定走唯一真源 GenApi.sha1_prefix_len(C10 收敛:不再接受大写 hex)。
"""
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: rowptr[memhub.MemBuddy],
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
dst_val: llvmlite.Value | t.CPtr,
src_val: llvmlite.Value | t.CPtr,
num_val: llvmlite.Value | t.CPtr) -> llvmlite.Value | t.CPtr:
"""将 memcpy 调用映射到 @llvm.memcpy 内联函数
直接接收三个 Value 参数,避免通过 Next 链表遍历(共享 alloca 对象的
Next 字段可能被其他操作污染,导致链表断裂)。
"""
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
# 在函数顶部声明 void_ty,避免后续遮蔽
void_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Void(pool)
# 声明 @llvm.memcpy(如未声明)
memcpy_func: llvmlite.Function | t.CPtr = self.find_func_in_module(mod, "llvm.memcpy")
if memcpy_func is None:
memcpy_func = llvmlite.create_declare(pool, mod, "llvm.memcpy", void_ty)
if memcpy_func is not None:
i8_ptr_ty_d: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, llvmlite.Int8(pool))
i64_ty_d: llvmlite.LLVMType | t.CPtr = llvmlite.Int64(pool)
i1_ty_d: llvmlite.LLVMType | t.CPtr = llvmlite.Int1(pool)
llvmlite.add_param(pool, memcpy_func, i8_ptr_ty_d, "dst")
llvmlite.add_param(pool, memcpy_func, i8_ptr_ty_d, "src")
llvmlite.add_param(pool, memcpy_func, i64_ty_d, "len")
llvmlite.add_param(pool, memcpy_func, i1_ty_d, "isvolatile")
# 类型转换: dst, src → i8*; num → i64
# 注意: 使用 value_get_ty 访问器获取 Ty,避免本地 Value 类型字段索引偏移
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, llvmlite.Int8(pool))
i64_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int64(pool)
# dst → i8*
dst_coerced: llvmlite.Value | t.CPtr = dst_val
dst_ty: llvmlite.LLVMType | t.CPtr = llvmlite.value_get_ty(dst_val)
if self.Gen.ExprGenBase.is_ptr_type(dst_ty) != 0:
dst_coerced = llvmlite.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 = llvmlite.build_zext(builder, dst_val, i64_ty)
dst_coerced = llvmlite.build_inttoptr(builder, dst_coerced, i8_ptr_ty)
# src → i8*
src_coerced: llvmlite.Value | t.CPtr = src_val
src_ty: llvmlite.LLVMType | t.CPtr = llvmlite.value_get_ty(src_val)
if self.Gen.ExprGenBase.is_ptr_type(src_ty) != 0:
src_coerced = llvmlite.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 = llvmlite.build_zext(builder, src_val, i64_ty)
src_coerced = llvmlite.build_inttoptr(builder, src_coerced, i8_ptr_ty)
# num → i64
num_coerced: llvmlite.Value | t.CPtr = 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
# 添加 i1 false 参数(isvolatile)— llvm.memcpy 需要 4 个参数
false_val: llvmlite.Value | t.CPtr = llvmlite.ConstInt(pool, llvmlite.Int1(pool), 0, "false")
# 使用 includes 中的访问器函数构建参数链表,避免本地 Value 类型字段索引偏移
# (f26cd4c78b4d554c.Value 缺少 GSListNode 的 Next 字段,直接访问 .Next 会修改 .Ty)
llvmlite.value_set_next(dst_coerced, src_coerced)
llvmlite.value_set_next(src_coerced, num_coerced)
if false_val is not None:
llvmlite.value_set_next(num_coerced, false_val)
llvmlite.value_set_next(false_val, None)
else:
llvmlite.value_set_next(num_coerced, None)
# 使用 build_call 发射 call 指令(内部用 includes Value 类型访问字段,索引正确)
# _ll_name_needs_quote 已对 llvm. 前缀豁免引号
result: llvmlite.Value | t.CPtr = llvmlite.build_call(builder, "llvm.memcpy", dst_coerced, 4, void_ty, 0)
# 返回 dst(匹配 C memcpy 语义: 返回 dest 指针)
return dst_coerced
# ============================================================
# _translate_call_with_kwargs — 带关键字参数的函数调用
#
# 支持两种语法:
# add(b=4, a=3) — 全关键字乱序
# greet("Hello", name="World") — 位置 + 关键字混合
#
# 策略: 遍历 found_func.Params 链表,对每个参数:
# 1. 若位置参数仍有剩余(param_idx < can),取位置参数
# 2. 否则按参数名匹配关键字参数
# 3. 按参数顺序构建调用参数链表
# ============================================================
# ============================================================
def _translate_call_with_kwargs(self, pool: rowptr[memhub.MemBuddy],
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
func_name: str,
found_func: llvmlite.Function | t.CPtr,
cargs: list[ast.AST | t.CPtr] | t.CPtr,
can: sizet,
keywords: list[ast.AST | t.CPtr] | t.CPtr,
kw_count: sizet,
trans: HT.Translator | t.CPtr) -> llvmlite.Value | t.CPtr:
"""带关键字参数的函数调用,按参数顺序构建调用链表
使用 llvmlite 访问器函数绕过 stub 类型限制:
found_func.Params/RetTy/Name 和 cur_param.Name/Ty/Next 在 stub
类型中字段不足,直接访问会被 TransPyC 静默跳过。
"""
head: llvmlite.Value | t.CPtr = None
tail: llvmlite.Value | t.CPtr = None
actual_count: sizet = 0
# memcpy 专用: 收集前三个 Value 的独立引用(避免共享 alloca 对象的 Next 被污染)
mc_dst1: llvmlite.Value | t.CPtr = None
mc_src1: llvmlite.Value | t.CPtr = None
mc_num1: llvmlite.Value | t.CPtr = None
# 遍历函数参数链表(使用访问器获取 Head)
param_idx: sizet = 0
cur_param: llvmlite.Param | t.CPtr = llvmlite.function_get_param_head(found_func)
while cur_param is not None:
arg_val: llvmlite.Value | t.CPtr = None
param_ty: llvmlite.LLVMType | t.CPtr = llvmlite.param_get_ty(cur_param)
if param_idx < can:
# 优先使用位置参数
arg: ast.AST | t.CPtr = cargs.get(param_idx)
if arg is not None:
# 传形参类型(char 字面量 → str 形参需物化字符串,见 translate_constant)
arg_val = self.Gen.ExprGen.translate_value(
builder, pool, mod, arg, None, 0, trans, param_ty)
else:
# 查找匹配的关键字参数
param_name: str = llvmlite.param_get_name(cur_param)
if param_name is not None:
for ki in range(kw_count):
kw: ast.Keyword | t.CPtr = (ast.Keyword | t.CPtr)(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
if arg_val is not None:
# 类型转换(按参数类型,使用访问器)
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 专用: 保存前三个参数的独立引用
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 = llvmlite.param_get_next(cur_param)
# 使用模块中的实际函数名(可能带 SHA1 前缀,使用访问器)
# declare 和 define 统一用 found_func.Name:
# - 普通函数的 declare/define 都带 SHA1 前缀(forward_declare_functions 和 stub 注入都用 mangled_name)
# - t.CExtern/t.State/t.CExport 函数的 declare/define 都用裸名
# 旧逻辑(IsDeclared==1 时用 func_name 裸名)是错误的:stub 注入的 declare Name 已带 SHA1 前缀,
# 用裸名会导致 call @_PathToModuleName 而 define 是 @"sha1._PathToModuleName",链接报 undefined reference
call_name: str = func_name
found_name: str = llvmlite.function_get_name(found_func)
if found_name is not None:
call_name = found_name
# 返回类型(使用访问器)
call_ret_ty: llvmlite.LLVMType | t.CPtr = llvmlite.function_get_ret_ty(found_func)
if call_ret_ty is None:
# found_func 已有 declare 却读不到返回类型(访问器异常,罕见防御):
# 白名单未命中也不 fail-fast,保持默认 i32(有声明即有类型来源)。
call_ret_ty = self._infer_external_func_ret_ty(pool, func_name, None, 0)
# memcpy → @llvm.memcpy 内联函数(避免 monomorphization 跨模块 @memcpy 声明缺失)
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 llvmlite.build_call(builder, call_name, head, actual_count, call_ret_ty, 0)