Files

1444 lines
113 KiB
Plaintext
Raw Permalink 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 lib.core.Gen.GenTypes as GenTypes
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdlib
import viperlib
import lib.core.Gen.Translator as HT
import lib.core.Gen.Type.TypeSpec as TSpec
import lib.Stub.StubGen.VpiReader as VpiReader
from . import CallGenBase
import lib.core.Gen.GenApi as GenApi
# CallGenMixin - call dispatch mixin (top level after split; phase A moved low-level parts out).
# Constants (kept consistent with TPC's HandlesExprCall).
define _CEXPORT_MAX = 256
define ASM_MAX_OPERANDS = 16
define ASM_TEXT_BUF_SIZE = 2048
define ASM_CONST_BUF_SIZE = 512
define ASM_ARGS_BUF_SIZE = 512
define ASM_LINE_BUF_SIZE = 4096
define LLVMIR_MAX_OPS = 16
define LLVMIR_TEMPLATE_BUF = 512
# CallGenMixin - call dispatch hub (top level, inherits LeafKit).
novirtual class CallGenMixin(CallGenBase.CallGenBase):
"""Call dispatch mixin (top-level; inherits CallGenBase directly)."""
# _CheckStaticCallCrossModule - reject cross-module calls to static symbols (mod.fn(...)).
# Checked centrally at translate_call entry (a per-branch check would miss some paths).
def _CheckStaticCallCrossModule(self, pool: memhub.MemBuddy*, trans: HT.Translator*, cl: ast.Call*) -> int:
if cl is None or cl.func is None or trans is None: return 0
if cl.func.kind() != ast.ASTKind.Attribute: return 0
if trans.ModuleSha1 is None or trans._imported_modules is None: return 0
# Fast path: zero cost when the project has no static symbols (no sha1/table scan).
if self.Gen.CallGenBase.StaticCount <= 0: return 0
sc_at: ast.Attribute* = ast.Attribute*(cl.func)
if sc_at.value is None or sc_at.attr is None: return 0
sc_mod: str = None
if sc_at.value.kind() == ast.ASTKind.Name:
sc_nm: ast.Name* = ast.Name*(sc_at.value)
sc_mod = sc_nm.id
elif sc_at.value.kind() == ast.ASTKind.Attribute: sc_mod = self.Gen.ExprGenAttr._attr_module_path_str(pool, sc_at.value)
if sc_mod is None: return 0
if self.Gen.ImportsGen.is_module_imported(trans._imported_modules, sc_mod) == 0: return 0
sc_sha1: str = self.Gen.StructGen._resolve_module_sha1(sc_mod, trans._from_imports)
if sc_sha1 is None: return 0
if string.strncmp(sc_sha1, trans.ModuleSha1, 16) == 0: return 0
if self.Gen.CallGenBase.IsStaticSymbol(sc_sha1, sc_at.attr) == 0: return 0
self.Gen.TypeGenErr.fatal_error(cl, f"'{sc_mod}.{sc_at.attr}' is a static symbol; cross-module use is forbidden (static is visible only inside its defining module)", "NameError")
return 0
# _FrameArgsEscapeClearsMethod - 方法调用版"同命"裁剪(与函数版同口径,但位有 self 偏移)
# 位 0 = 接收者(self),位 (i+1) = 用户第 i 个实参。
# 某位可清 ⇔ 该位对应的实参是帧生命期(FRAME/VALUE),且**其余全部实参(含接收者)也在本帧**。
# 现场:`h.set_q(l)` 里 h = `new heap slife S()`(生命期本就是 FRAME:作用域出口释放)、
# l 是栈数组 → 两都在本帧 → 不该报。
def _FrameArgsEscapeClearsMethod(self, trans: HT.Translator*, recv_node: ast.AST*, cargs: list[ast.AST*]*, can: sizet, only_fld: int) -> int:
"""返回方法调用中可安全清掉的位掩码(0 = 一位都清不掉)"""
if trans is None or only_fld == 0: return 0
cleared: int = 0
bi: int = 1
while bi <= int(can):
if ((only_fld >> bi) & 1) == 0:
bi += 1
continue
all_frame: int = 1
if recv_node is not None:
lr: int = self.Gen.AssignGen.life_of_expr(trans, recv_node, None)
if lr != GenTypes.LIFE_FRAME and lr != GenTypes.LIFE_VALUE: all_frame = 0
j: sizet = 0
while j < can:
if all_frame == 0: break
if int(j) != bi - 1:
a_j: ast.AST* = cargs.get(j)
if a_j is not None:
lj: int = self.Gen.AssignGen.life_of_expr(trans, a_j, None)
if lj != GenTypes.LIFE_FRAME and lj != GenTypes.LIFE_VALUE: all_frame = 0
j += 1
if all_frame != 0: cleared = cleared | (1 << bi)
bi += 1
return cleared
# _FrameArgsEscapeClears - "同命"精化(P3 同命规则在**调用点**的对应物):
# 候选位 only_fld 只由"写进某个**形参对象**的字段"造成;若调用点交出的其它实参也都在本帧
# (LIFE_FRAME)或纯值语义(LIFE_VALUE),则对象与本帧同命、并不真的逃逸 → 可清。
# 判据保守:任何一个其它实参的生命期为 HEAP/STATIC/未知,就**保留**该位(宁可多报)。
def _FrameArgsEscapeClears(self, trans: HT.Translator*, cargs: list[ast.AST*]*, can: sizet, only_fld: int) -> int:
"""返回可安全清掉的位掩码(0 = 一位都清不掉)"""
if trans is None or cargs is None or only_fld == 0: return 0
cleared: int = 0
ai: sizet = 0
while ai < can:
if ((only_fld >> int(ai)) & 1) != 0:
all_frame: int = 1
j: sizet = 0
while j < can:
if j != ai:
a_j: ast.AST* = cargs.get(j)
if a_j is not None:
lj: int = self.Gen.AssignGen.life_of_expr(trans, a_j, None)
if lj != GenTypes.LIFE_FRAME and lj != GenTypes.LIFE_VALUE:
all_frame = 0
break
j += 1
if all_frame != 0: cleared = cleared | (1 << int(ai))
ai += 1
return cleared
# translate_call - translate Call(func, args) (module-level version).
def _MarkWillFreeKwargs(self, trans: HT.Translator*, wf_mask: int, cargs: list[ast.AST*]*, can: sizet, keywords: list[ast.AST*]*, kw_count: sizet, found_func: mlirlite.Function*) -> int:
"""P2 willFree:关键字(或"位置+关键字"混合)实参路径的失效标记。
形参序必须与 _translate_call_with_kwargs 的遍历一致:位置实参占前 can 个形参,
其余按关键字名匹配到形参位。调用方必须在本函数**之后**——那时实参才翻译完。"""
if trans is None or found_func is None or wf_mask == 0: return 0
param_idx: sizet = 0
cur_param: mlirlite.Param* = mlirlite.function_get_param_head(found_func)
while cur_param is not None:
arg_node_kw: ast.AST* = None
if param_idx < can:
if cargs is not None: arg_node_kw = cargs.get(param_idx)
else:
pname_kw: str = mlirlite.param_get_name(cur_param)
if pname_kw is not None and keywords is not None:
ki_kw: sizet
for ki_kw in range(int(kw_count)):
kw_node: ast.AST* = keywords.get(ki_kw)
if kw_node is None: continue
kw_el: ast.Keyword* = ast.Keyword*(kw_node)
if kw_el is None or kw_el.arg is None or kw_el.value is None: continue
if self.Gen.CallGenCall._kwarg_name_matches(kw_el.arg, pname_kw) == 1:
arg_node_kw = kw_el.value
break
if arg_node_kw is not None and ((wf_mask >> int(param_idx)) & 1) != 0:
self.Gen.CallGenCall._MarkWillFreeArgName(trans, arg_node_kw)
param_idx += 1
cur_param = mlirlite.param_get_next(cur_param)
return 0
def translate_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, node: ast.AST*, funcs_ptr: rawptr, func_count: int, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Translate a Call node and return the result Value."""
cl: ast.Call* = ast.Call*(node)
# Static symbols must not be used cross-module: fail fast on mod.fn(...) hitting another
# module's static; the check lives in _CheckStaticCallCrossModule to keep this huge function inferable.
self._CheckStaticCallCrossModule(pool, trans, cl)
if cl is None: return None
func_name: str = self.Gen.ExprGenBase.get_func_name(cl.func)
cargs: list[ast.AST*]* = cl.args
can: sizet = 0
if cargs is not None: can = len(cargs)
# M3 §4.3 判据5:`slife` 对象由编译器独占释放,禁止手动 free(否则出口再释放一次 = double-free)。
# 只认裸名/模块限定的 free;实参须是变量名且该槽已登记为 owned slife。
if trans is not None and cargs is not None and func_name is not None:
if string.strcmp(func_name, "free") == 0 and len(cargs) == 1:
a0_mf: ast.AST* = cargs.get(0)
if a0_mf is not None and a0_mf.kind() == ast.ASTKind.Name:
an_mf: ast.Name* = ast.Name*(a0_mf)
if an_mf.id is not None:
slot_mf: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, an_mf.id)
if slot_mf is None: slot_mf = self.Gen.VarGen.lookup_module_var(trans.SymTab, an_mf.id)
if self.Gen.VarGen._SlifeOwned(slot_mf) != 0:
self.Gen.TypeGenErr.fatal_error(a0_mf, f"cannot free '{an_mf.id}': it is a compiler-owned `slife` object (freed at scope exit); a manual free would double-free it. Use `new heap hlife ...` to manage it yourself", "LifetimeError")
# 调用点生命期检查(M2.3 v1b 步骤4):被调者"会逃逸或直接返回"的形参位上,实参不得
# 交出本帧栈地址(&local / 局部数组退让)。此处拿不到被调者形参类型,故传一个哑指针
# 类型——只用于让 life_of_expr 判定"裸局部数组名处于指针位置 = 数组退让 = 栈地址"。
if cargs is not None and func_name is not None:
esc_mask: int = GenApi.get_param_escapes(func_name)
# loc_mask:1=掩码来自**本文件**摘要表(只有这时才有"硬逃逸位"记录可用);
# 走下面的跨模块回退时置 0 —— 跨模块的掩码是合并值,不能再按"字段位"裁剪(会变成漏报)。
loc_mask: int = 1
# 跨模块回退解析出的被调者模块 sha1(最终裁决要用它去查对方 .vpi 的指令契约)
mod_sha_ec: str = None
if esc_mask == 0:
loc_mask = 0
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
at_ec: ast.Attribute* = ast.Attribute*(cl.func)
mod_p_ec: str = None
if at_ec.value is not None and at_ec.value.kind() == ast.ASTKind.Name:
mn_ec: ast.Name* = ast.Name*(at_ec.value)
if mn_ec.id is not None: mod_p_ec = mn_ec.id
elif at_ec.value is not None and at_ec.value.kind() == ast.ASTKind.Attribute:
mod_p_ec = self.Gen.ExprGenAttr._attr_module_path_str(pool, at_ec.value)
if mod_p_ec is not None:
fi_ec: str = None
if trans is not None: fi_ec = trans._from_imports
mod_sha_ec = self.Gen.StructGen._resolve_module_sha1(mod_p_ec, fi_ec)
if mod_sha_ec is None and trans is not None and trans._from_imports is not None:
mod_sha_ec = self._lookup_mod_sha1(pool, trans._from_imports, func_name, 0)
if mod_sha_ec is not None:
pk_ec: int = self.Gen.CallGenBase.lookup_escape_via_vpi(pool, mod_sha_ec, None, func_name)
if pk_ec != 0: esc_mask = int(pk_ec & 4294967295)
# 跨模块常量特化:本文件摘要表查不到(= 被调者在别的模块)时,用实参常量套用
# 该模块 .vpi 里落盘的守卫记录(无记录/无常量 → 原样保留,保守)
if esc_mask != 0:
cl_ec: int = self.Gen.CallGenBase.lookup_escape_clears_via_vpi(pool, mod_sha_ec, None, func_name, cargs, can, 0)
if cl_ec != 0: esc_mask = esc_mask & (~cl_ec)
# 跨过程常量特化(P3 收尾):本文件能拿到被调者函数体时,用常量实参把摘要重算一遍——
# `if y: a = x` 在 `f(l, False)` 这一调用点上是死分支,不该按"逃逸"拦。
# 跨模块拿不到 AST → 原样返回(保守,与不启用特化时完全一致)。
if esc_mask != 0:
esc_mask = GenApi.refine_escape_mask(pool, func_name, 0, cargs, can, esc_mask)
# 同命精化:只写进**形参对象**字段的位(字段位 = 逃逸位 & ~硬位),若调用点交出的其它实参
# 也都在本帧,则两者同命、并不真的逃逸(`g(l, &local_s)`)。跨模块(loc_mask=0)不裁剪。
if esc_mask != 0 and loc_mask != 0:
hard_m: int = GenApi.get_param_escapes_hard(func_name)
fld_only: int = esc_mask & (~hard_m)
if fld_only != 0:
cl_sf: int = self._FrameArgsEscapeClears(trans, cargs, can, fld_only)
if cl_sf != 0: esc_mask = esc_mask & (~cl_sf)
# ==== 声明式指令最终裁决(@life(escape/noescape):unsafe 但强制)====
# 必须放在所有推断/裁剪**之后**:常量特化、同命裁剪、跨模块回退都改不动契约。
# escape(...) 无条件置位、noescape(...) 无条件清位。
fe_m: int = GenApi.get_param_escape_force(func_name)
ne_m: int = GenApi.get_param_noescape_force(func_name)
if fe_m == 0 and ne_m == 0 and mod_sha_ec is not None:
# 跨模块:本文件指令表为空(按文件重置),改查对方模块 .vpi 的行尾 esce=/escn=
fe_m = self.Gen.CallGenBase.lookup_escape_dir_via_vpi(mod_sha_ec, None, func_name, 0)
ne_m = self.Gen.CallGenBase.lookup_escape_dir_via_vpi(mod_sha_ec, None, func_name, 1)
if fe_m != 0 or ne_m != 0:
esc_mask = (esc_mask | fe_m) & (~ne_m)
if esc_mask != 0:
_dummy_ptr: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
for ai in range(int(can)):
if ((esc_mask >> int(ai)) & 1) != 0:
# 泄漏点:把"被调者 + 形参位"交给检查方,确认真要报错后才去采集(同模块才有行号)
GenApi.fail_if_stack_addr_escape(cargs.get(ai), "an escaping callee parameter", trans, _dummy_ptr, func_name, int(ai))
# P2 willFree:查被调者的形参失效标注(本文件表 → 跨模块 .vpi 回退)。
# 标记动作在**实参全部翻译完成之后**(见下方 "# Default-argument filling" 之前),
# 否则 `stdlib.free(p)` 自己那次读取 p 会被误判为"使用已失效变量"。
wf_mask: int = 0
if cargs is not None and func_name is not None:
wf_mask = GenApi.get_param_willfree(func_name)
if wf_mask == 0:
mod_sha_wf: str = None
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
at_wf: ast.Attribute* = ast.Attribute*(cl.func)
mod_p_wf: str = None
if at_wf.value is not None and at_wf.value.kind() == ast.ASTKind.Name:
mn_wf: ast.Name* = ast.Name*(at_wf.value)
if mn_wf.id is not None: mod_p_wf = mn_wf.id
elif at_wf.value is not None and at_wf.value.kind() == ast.ASTKind.Attribute:
mod_p_wf = self.Gen.ExprGenAttr._attr_module_path_str(pool, at_wf.value)
if mod_p_wf is not None:
fi_wf: str = None
if trans is not None: fi_wf = trans._from_imports
mod_sha_wf = self.Gen.StructGen._resolve_module_sha1(mod_p_wf, fi_wf)
if mod_sha_wf is None and trans is not None and trans._from_imports is not None:
mod_sha_wf = self._lookup_mod_sha1(pool, trans._from_imports, func_name, 0)
if mod_sha_wf is not None:
wf_mask = self.Gen.CallGenBase.lookup_willfree_via_vpi(pool, mod_sha_wf, None, func_name)
# Generic function calls (PEP 695 def f[T]) are intercepted first: infer type args and
# monomorphize here (templates emit no plain function); hit_box=1 means handled-with-void.
h0_hit: rawptr = stdlib.malloc(4)
if h0_hit is not None: h0_hit[0] = 0
_tc_h0_generic_func_res: mlirlite.Value* = self.Gen.CallGenDispatch._tc_h0_generic_func(pool, builder, mod, cl, funcs_ptr, func_count, trans, func_name, cargs, can, h0_hit)
h0_hit_val: int = 0
if h0_hit is not None:
h0_hit_val = h0_hit[0]
stdlib.free(h0_hit)
if _tc_h0_generic_func_res is not None: return _tc_h0_generic_func_res
if h0_hit_val != 0: return None
# super()/ctraits detection: a void method yields None but is handled, so hit_box must
# separate "handled" from "missed"; otherwise super() falls through and reports undefined.
h1_hit: rawptr = stdlib.malloc(4)
if h1_hit is not None: h1_hit[0] = 0
_tc_h1_super_ctraits_res: mlirlite.Value* = self.Gen.CallGenDispatch._tc_h1_super_ctraits(pool, builder, mod, cl, funcs_ptr, func_count, trans, func_name, cargs, can, h1_hit)
h1_hit_val: int = 0
if h1_hit is not None:
h1_hit_val = h1_hit[0]
stdlib.free(h1_hit)
if _tc_h1_super_ctraits_res is not None: return _tc_h1_super_ctraits_res
if h1_hit_val != 0:
# Hit super()/ctraits with no return value (void method/macro call): treated as handled.
return None
# ---- func_name is None: generic instantiation + type casts ----
if func_name is None:
# Subscript as the Call func: a generic class constructor (e.g. list[int](pool)).
if cl.func is not None and cl.func.kind() == ast.ASTKind.Subscript and trans is not None:
sub_node: ast.Subscript* = ast.Subscript*(cl.func)
if sub_node.value is not None and sub_node.value.kind() == ast.ASTKind.Name:
gen_nm_node: ast.Name* = ast.Name*(sub_node.value)
gen_class_name: str = gen_nm_node.id
# Check whether this is a generic class template.
template_cd: ast.ClassDef* = self.Gen.ClassGenGeneric._find_generic_template(gen_class_name)
if template_cd is not None:
# Extract the type arguments.
gen_type_args: list[str]* = self.Gen.CallGenCtor._extract_type_args_from_slice(pool, sub_node.slice, trans._from_imports)
if gen_type_args is not None and len(gen_type_args) > 0:
# Specialize the generic class and return the specialized name.
spec_name: str = self.Gen.ClassGenGeneric._specialize_generic_class(trans, gen_class_name, gen_type_args)
if spec_name is not None:
# Generic ctor call: list[str](pool, 8) -> __new__ + __before_init__ + __init__.
# Do not use spec_name as func_name (star-import fallback would pick a wrong SHA1).
return self.Gen.CallGenCtor._translate_generic_ctor_call(pool, builder, mod, spec_name, cl, trans, gen_class_name)
else:
# No generic class template: maybe an explicit generic-function call xxx[T](x);
# VPC compiles generic functions as plain functions, so drop [T] if a same-name function exists.
if self.find_func_in_module(mod, gen_class_name) is not None: func_name = gen_class_name
else:
# Builtin pointer/type subscript cast: (rawptr[list[int]])(lst), (t.CPtr[T])(x),
# (t.CArray[i8, N])(x); rawptr/CPtr/CArray are type subscripts, not generic templates.
if cl.args is not None and len(cl.args) == 1:
sub_cast_ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, cl.func, trans._imported_modules, trans._from_imports, trans, 0)
if sub_cast_ty is not None:
sub_cast_arg: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cl.args.get(0), funcs_ptr, func_count, trans)
if sub_cast_arg is not None:
return self.Gen.CallGenCtor._translate_t_type_cast(pool, builder, sub_cast_arg, sub_cast_ty, cl.args.get(0), trans)
# Generic class template not found: the defining module may not be imported.
gt_err: str = pool.alloc(256)
if gt_err is not None:
viperlib.snprintf(gt_err, 256, "Generic class '%s': template not found; the defining module may not be imported", gen_class_name)
self.Gen.TypeGenErr.fatal_error(gen_nm_node, gt_err)
self.Gen.TypeGenErr.fatal_error(gen_nm_node, "Generic class template not found")
# Union type casts were retired; only Subscript / Name callees remain.
if func_name is None: return None
# c.Asm/c.LLVMIR special calls return None when void but are handled, so use hit_box to
# separate "missed"; otherwise they fall through to the external-function path and mis-report.
h2_hit: rawptr = stdlib.malloc(4)
if h2_hit is not None: h2_hit[0] = 0
_tc_h2_c_special_res: mlirlite.Value* = self.Gen.CallGenDispatch._tc_h2_c_special(pool, builder, mod, cl, funcs_ptr, func_count, trans, func_name, cargs, can, h2_hit)
h2_hit_val: int = 0
if h2_hit is not None:
h2_hit_val = h2_hit[0]
stdlib.free(h2_hit)
if _tc_h2_c_special_res is not None: return _tc_h2_c_special_res
if h2_hit_val != 0:
# Hit c.Asm/c.LLVMIR with no return value (void): treated as handled.
return None
_tc_h3_stdarg_cast_res: mlirlite.Value* = self.Gen.CallGenDispatch._tc_h3_stdarg_cast(pool, builder, mod, cl, funcs_ptr, func_count, trans, func_name, cargs, can)
if _tc_h3_stdarg_cast_res is not None: return _tc_h3_stdarg_cast_res
_tc_h4_type_casts_res: mlirlite.Value* = self.Gen.CallGenDispatch._tc_h4_type_casts(pool, builder, mod, cl, funcs_ptr, func_count, trans, func_name, cargs, can)
if _tc_h4_type_casts_res is not None: return _tc_h4_type_casts_res
_tc_h5_sizeof_len_isinst_res: mlirlite.Value* = self.Gen.CallGenDispatch._tc_h5_sizeof_len_isinst(pool, builder, mod, cl, funcs_ptr, func_count, trans, func_name, cargs, can)
if _tc_h5_sizeof_len_isinst_res is not None: return _tc_h5_sizeof_len_isinst_res
# Method dispatch may succeed yet return None (e.g. void __before_init__/__init__), so hit_box
# must separate "recognized method" from "missed"; otherwise the object name is looked up as a module.
h6_hit: rawptr = stdlib.malloc(4)
if h6_hit is not None: h6_hit[0] = 0
_tc_h6_method_dispatch_res: mlirlite.Value* = self.Gen.CallGenDispatch._tc_h6_method_dispatch(pool, builder, mod, cl, funcs_ptr, func_count, trans, func_name, cargs, can, h6_hit)
h6_hit_val: int = 0
if h6_hit is not None:
h6_hit_val = h6_hit[0]
stdlib.free(h6_hit)
if _tc_h6_method_dispatch_res is not None: return _tc_h6_method_dispatch_res
if h6_hit_val != 0:
# Hit the method path with no return value (void semantics): treated as handled.
return None
# Fetch keyword arguments.
keywords: list[ast.AST*]* = cl.keywords
kw_count: sizet = 0
if keywords is not None: kw_count = len(keywords)
# Struct constructor: Point() / Point(10, 20) / Point(x=10, y=20). Enforce namespace isolation
# (bare name needs import/local def, module-qualified needs the import); use find_struct_by_module.
struct_ty: mlirlite.LLVMType* = None
gen_ctor_entry: GenTypes.StructEntry* = None
# Module-qualified construction first: resolve the class by module SHA1 before the bare-name
# branch, else cross-module same-name classes resolve to the wrong entry (Scene3D root cause).
mod_qual_done: int = 0
if trans is not None and cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
at_qc0: ast.Attribute* = ast.Attribute*(cl.func)
mod_path0: str = None
if at_qc0.value is not None and at_qc0.value.kind() == ast.ASTKind.Name:
mod_nm0: ast.Name* = ast.Name*(at_qc0.value)
if mod_nm0.id is not None: mod_path0 = mod_nm0.id
elif at_qc0.value is not None and at_qc0.value.kind() == ast.ASTKind.Attribute: mod_path0 = self.Gen.ExprGenAttr._attr_module_path_str(pool, at_qc0.value)
if mod_path0 is not None:
if self.Gen.ImportsGen.is_module_imported_or_known(trans._imported_modules, mod_path0) != 0:
mod_sha1_0: str = self._lookup_module_sha1(mod_path0)
if mod_sha1_0 is not None:
gen_ctor_entry = self.Gen.StructGen.find_struct_by_module(func_name, mod_sha1_0)
mod_qual_done = 1
vis: int = self.Gen.StructGen.is_struct_visible(func_name)
if mod_qual_done == 0 and vis != 0:
if trans is not None: gen_ctor_entry = self.Gen.StructGen.find_struct_by_module(func_name, trans.ModuleSha1)
if gen_ctor_entry is None: gen_ctor_entry = self.Gen.StructGen.find_struct(func_name)
# Forward-declaration placeholder (registered for a bare generic template without type args) is not a
# real struct: leave struct_ty unset so generic inference specializes it by the actual arguments.
if gen_ctor_entry is not None and gen_ctor_entry.IsForwardDecl != 0 and self.Gen.ClassGenGeneric._find_generic_template(func_name) is not None: gen_ctor_entry = None
if gen_ctor_entry is not None: struct_ty = gen_ctor_entry.Ty
elif trans is not None and cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
at_qc: ast.Attribute* = ast.Attribute*(cl.func)
if at_qc.value is not None and at_qc.value.kind() == ast.ASTKind.Name:
mod_nm_qc: ast.Name* = ast.Name*(at_qc.value)
imp_ok: int = self.Gen.ImportsGen.is_module_imported_or_known(trans._imported_modules, mod_nm_qc.id)
if imp_ok != 0:
# Module-qualified construction: resolve by project module SHA1, not trans.ModuleSha1
# (the current file); otherwise the bare-name fallback picks the wrong same-name class.
mod_sha1_qc2: str = None
if mod_nm_qc.id is not None: mod_sha1_qc2 = self._lookup_module_sha1(mod_nm_qc.id)
if mod_sha1_qc2 is not None: gen_ctor_entry = self.Gen.StructGen.find_struct_by_module(func_name, mod_sha1_qc2)
if gen_ctor_entry is None: gen_ctor_entry = self.Gen.StructGen.find_struct(func_name)
if gen_ctor_entry is not None: struct_ty = gen_ctor_entry.Ty
elif at_qc.value is not None and at_qc.value.kind() == ast.ASTKind.Attribute:
# Multi-level module path construction: win32.fileio.File(...); build the path via
# attr_module_path_str, check the full import, then find the struct by that module's SHA1.
mod_path_qc: str = self.Gen.ExprGenAttr._attr_module_path_str(pool, at_qc.value)
if mod_path_qc is not None:
imp_ok_qc: int = self.Gen.ImportsGen.is_module_imported(trans._imported_modules, mod_path_qc)
if imp_ok_qc != 0:
mod_sha1_qc: str = self._lookup_module_sha1(mod_path_qc)
if mod_sha1_qc is not None: gen_ctor_entry = self.Gen.StructGen.find_struct_by_module(func_name, mod_sha1_qc)
if gen_ctor_entry is None: gen_ctor_entry = self.Gen.StructGen.find_struct(func_name)
if gen_ctor_entry is not None: struct_ty = gen_ctor_entry.Ty
# Aliased class construction (from X import Class as Alias): the struct is registered under its
# original name, so resolve alias -> original before find_struct to avoid a link-time undefined reference.
if gen_ctor_entry is None and trans is not None and cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
if trans._from_imports is not None:
ctor_sha1: str = self._lookup_mod_sha1(pool, trans._from_imports, func_name, 0)
if ctor_sha1 is not None:
ctor_orig: str = self.Gen.ImportsGen.lookup_from_import_original(trans._from_imports, func_name)
if ctor_orig is not None:
# Copy into a separate NUL-terminated buffer (the from_imports buffer is not terminated).
co_len: sizet = 0
while ctor_orig[co_len] != ' ' and ctor_orig[co_len] != '\0': co_len += 1
co_buf: str = pool.alloc(co_len + 1)
if co_buf is not None:
for co_i in range(int(co_len)): co_buf[co_i] = ctor_orig[co_i]
co_buf[co_len] = '\0'
# Look up the struct by original name (visibility already ensured by the alias).
gen_ctor_entry = self.Gen.StructGen.find_struct_by_module(co_buf, ctor_sha1)
if gen_ctor_entry is None: gen_ctor_entry = self.Gen.StructGen.find_struct(co_buf)
if gen_ctor_entry is not None:
struct_ty = gen_ctor_entry.Ty
func_name = co_buf
if struct_ty is not None:
if can == 0 and kw_count == 0:
# OOP struct: call __before_init__ even with no args; use gen_ctor_entry to avoid same-name lookup errors.
ctor_is_oop: int = 0
if gen_ctor_entry is not None: ctor_is_oop = gen_ctor_entry.IsOOP
if ctor_is_oop != 0: return self.Gen.CallGenCtor._translate_struct_ctor(pool, builder, mod, struct_ty, func_name, cargs, sizet(0), trans, cl.IsVolatile)
# @t.NoVTable classes (no methods, pointer/class semantics): no-arg construction must still
# allocate and return a pointer, else the LHS stores a null pointer and dereferencing crashes.
ctor_is_novt: int = 0
if gen_ctor_entry is not None: ctor_is_novt = gen_ctor_entry.IsNoVTable
if ctor_is_novt != 0: return self.Gen.CallGenCtor._translate_struct_ctor(pool, builder, mod, struct_ty, func_name, cargs, sizet(0), trans, cl.IsVolatile)
# Plain struct: check for default values (access via entry directly).
gen_has_defaults: int = 0
if gen_ctor_entry is not None:
for gdi in range(int(gen_ctor_entry.FieldCount)):
gdf: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(gen_ctor_entry, gdi)
if gdf is not None and gdf.DefaultVal is not None:
gen_has_defaults = 1
break
else: gen_has_defaults = self.Gen.StructGen.struct_has_defaults(func_name)
if gen_has_defaults == 1:
# Has defaults: use the constructor path to apply them.
return self.Gen.CallGenCtor._translate_struct_ctor(pool, builder, mod, struct_ty, func_name, cargs, sizet(0), trans, cl.IsVolatile)
# No defaults: return zeroinitializer.
ret: mlirlite.Value* = mlirlite.ConstZero(pool, struct_ty)
return ret
if kw_count > 0:
# Keyword arguments present: assign by field name.
return self.Gen.CallGenCtor._translate_struct_ctor_kw(pool, builder, mod, struct_ty, func_name, cargs, can, keywords, kw_count, trans, cl.IsVolatile)
return self.Gen.CallGenCtor._translate_struct_ctor(pool, builder, mod, struct_ty, func_name, cargs, can, trans, cl.IsVolatile)
# Bare generic class construction: Box(42) -> infer type args from the actual arguments,
# specialize, construct. Also supports module-qualified vector.Vector(...); placeholders must not block inference.
gen_ctor_is_fwd_generic: int = 0
if gen_ctor_entry is not None and gen_ctor_entry.IsForwardDecl != 0:
if self.Gen.ClassGenGeneric._find_generic_template(func_name) is not None: gen_ctor_is_fwd_generic = 1
if struct_ty is None and (gen_ctor_entry is None or gen_ctor_is_fwd_generic != 0) and trans is not None:
is_gen_bare: int = 0
if cl.func is not None and cl.func.kind() == ast.ASTKind.Name: is_gen_bare = 1
elif cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
at_gb: ast.Attribute* = ast.Attribute*(cl.func)
if at_gb.value is not None and at_gb.value.kind() == ast.ASTKind.Name:
am_gb: ast.Name* = ast.Name*(at_gb.value)
if am_gb.id is not None:
if self.Gen.ImportsGen.is_module_imported(trans._imported_modules, am_gb.id) != 0: is_gen_bare = 1
if is_gen_bare != 0:
template_cd_bare: ast.ClassDef* = self.Gen.ClassGenGeneric._find_generic_template(func_name)
if template_cd_bare is not None and template_cd_bare.type_params is not None:
tp_bare: list[str]* = template_cd_bare.type_params
tp_count_bare: sizet = len(tp_bare)
if tp_count_bare > 0:
inferred_args_bare: list[str]* = list[str](pool, 8)
if inferred_args_bare is not None:
# Map each type param to the positional arg whose __init__ annotation matches it (not by index);
ctx_args_bare: list[str]* = self.Gen.CallGenCtor._infer_ctor_type_args_from_ctx(pool, trans, func_name, int(tp_count_bare), cl)
tpi_bare: sizet = 0
for tpi_bare in range(int(tp_count_bare)):
aip_bare: int = self.Gen.CallGenCtor._find_ctor_type_param_arg_index(func_name, tp_bare.get(tpi_bare))
if aip_bare < 0: aip_bare = int(tpi_bare)
ais_bare: sizet = sizet(aip_bare)
# [D25 fail-fast] Never default to "int": with no hint and too few args it would
# wrongly specialize to list[int]/Box[int] (4-byte elements, pointer truncation/crash).
ta_bare: str = None
if ais_bare < can: ta_bare = self.Gen.CallGenCtor._infer_generic_ctor_type_arg(pool, cargs.get(ais_bare), func_name, trans)
if ta_bare is None:
# [C2] Keyword-only arg (Box(v=42)): match the keyword by __init__ param name
# and infer from it (order-independent; returns -1 if the slot is positional).
kwi_bare: int = self.Gen.CallGenCtor._find_ctor_type_param_kw_arg_index(func_name, tp_bare.get(tpi_bare), keywords, kw_count, int(can))
if kwi_bare >= 0:
kwn_bare: ast.Keyword* = ast.Keyword*(keywords.get(sizet(kwi_bare)))
if kwn_bare is not None and kwn_bare.value is not None: ta_bare = self.Gen.CallGenCtor._infer_generic_ctor_type_arg(pool, kwn_bare.value, func_name, trans)
if ta_bare is None and ctx_args_bare is not None:
# [C1] The annotation already gives type args (e.g. s: Stack[int] = Stack()): specialize with them.
ta_bare = ctx_args_bare.get(tpi_bare)
if ta_bare is None:
self.Gen.TypeGenErr.fatal_error(cl, f"Cannot infer type argument {tpi_bare + 1} of generic class '{func_name}' from the arguments, " f"silent default to int is forbidden (it produces wrong specializations such as list[int] -> elements treated as 4 bytes/i32 -> pointer truncation crash); " f"annotate the argument type or annotate explicitly, e.g. {func_name}[Type](...)", "TypeError")
inferred_args_bare.append(ta_bare)
spec_name_bare: str = self.Gen.ClassGenGeneric._specialize_generic_class(trans, func_name, inferred_args_bare)
if spec_name_bare is not None: return self.Gen.CallGenCtor._translate_generic_ctor_call(pool, builder, mod, spec_name_bare, cl, trans, func_name)
# REnum variant construction: ClassName.VariantName(args...), e.g. LLVMType.Int(1). Must run
# before cross-module lookup, otherwise ClassName is mistaken for a module name.
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
rc_at: ast.Attribute* = ast.Attribute*(cl.func)
if rc_at.value is not None and rc_at.value.kind() == ast.ASTKind.Name:
rc_cls_node: ast.Name* = ast.Name*(rc_at.value)
if rc_cls_node.id is not None:
rc_cls_name: str = rc_cls_node.id
# Check whether ClassName is a registered REnum; also use _is_renum_by_entry (a __tag
# makes it an REnum) so a missing variant table does not silently fall through to a misleading error.
if self.Gen.ClassGenEnum._is_renum_class_name(rc_cls_name) != 0 or self.Gen.ClassGenEnum._is_renum_by_entry(rc_cls_name) != 0:
# Look up the variant name func_name in that REnum class.
rc_tag: int = self.Gen.ClassGenEnum._lookup_renum_variant_in_class(rc_cls_name, func_name)
if rc_tag < 0:
# [D32 fail-fast] It is an REnum but the variant is missing: the table is incomplete; report it here.
self.Gen.TypeGenErr.fatal_error(cl, f"REnum class '{rc_cls_name}': variant '{func_name}' not found in the variant table" f" ({self.Gen.ClassGenEnum.RenumTableDump()}); " f"the variant table is not registered or the variant name does not match", "NameError")
if rc_tag >= 0: return self.Gen.CallGenCtor._translate_renum_construct(pool, builder, mod, rc_cls_name, func_name, rc_tag, cargs, can, trans, cl.IsVolatile)
elif rc_at.value is not None and rc_at.value.kind() == ast.ASTKind.Attribute:
# Module-qualified REnum variant: mod.Class.Variant(args...), e.g. mlirlite.LLVMType.Struct(...).
# Only require the leftmost module imported and the class name in the global REnum table.
rc_mq_path: str = self.Gen.ExprGenAttr._attr_module_path_str(pool, rc_at.value)
if rc_mq_path is not None:
rc_mq_dot: str = string.strrchr(rc_mq_path, 46) # 46 = ord('.')
if rc_mq_dot is not None and rc_mq_dot[1] != '\0':
# Module path = everything before the last segment (multi-level supported); use
# strlen integer differences, not pointer subtraction (which emits illegal IR).
rc_mq_len: sizet = string.strlen(rc_mq_path) - string.strlen(rc_mq_dot + 1) - 1
rc_mq_mod: str = pool.alloc(rc_mq_len + 1)
if rc_mq_mod is not None:
string.memcpy(rc_mq_mod, rc_mq_path, rc_mq_len)
rc_mq_mod[rc_mq_len] = '\0'
rc_mq_imp: int = 1
if trans is not None and trans._imported_modules is not None: rc_mq_imp = self.Gen.ImportsGen.is_module_imported(trans._imported_modules, rc_mq_mod)
if rc_mq_imp != 0:
rc_mq_cls: str = rc_mq_dot + 1
# Check whether the class name is a registered REnum class.
if self.Gen.ClassGenEnum._is_renum_class_name(rc_mq_cls) != 0:
rc_mq_tag: int = self.Gen.ClassGenEnum._lookup_renum_variant_in_class(rc_mq_cls, func_name)
if rc_mq_tag >= 0: return self.Gen.CallGenCtor._translate_renum_construct(pool, builder, mod, rc_mq_cls, func_name, rc_mq_tag, cargs, can, trans, cl.IsVolatile)
# Bare async call (VPC-M1/M2): calling an async function (task(5,6)) emits create_<name>(...).
# Intercept before bare-name lookup: async functions register only create_/resume_, not the bare name.
if cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
if func_name is not None and trans is not None:
if self.Gen.AsyncGen.is_async_func(trans, func_name) != 0:
can_buf: str = pool.alloc(64)
if can_buf is not None: viperlib.snprintf(can_buf, 64, "create_%s", func_name)
create_an: str = self.Gen.FuncGenBase._mangle_func_name(trans, can_buf, 0)
create_fn_an: mlirlite.Function* = self.find_func_in_module(mod, create_an)
if create_fn_an is not None:
frame_ty_an: mlirlite.LLVMType* = self.Gen.AsyncGen._lookup_coro_frame(trans, func_name)
if frame_ty_an is not None:
ret_ptr_ty_an: mlirlite.LLVMType* = mlirlite.Ptr(pool, frame_ty_an)
i8_ptr_ty_an: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
# M2: async call arguments (task(5, 6) -> create_task(5, 6)).
arg_head_an: mlirlite.Value* = None
arg_tail_an: mlirlite.Value* = None
arg_cnt_an: int = 0
if cl.args is not None:
nb_an: sizet = len(cl.args)
j_an: sizet = 0
for j_an in range(int(nb_an)):
av_an: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cl.args.get(j_an), None, 0, trans)
if av_an is None: av_an = mlirlite.const_int32(pool, 0)
av_an.Next = None
if arg_head_an is None:
arg_head_an = av_an
arg_tail_an = av_an
else:
arg_tail_an.Next = av_an
arg_tail_an = av_an
arg_cnt_an = arg_cnt_an + 1
child_an: mlirlite.Value* = mlirlite.build_call(builder, create_an, arg_head_an, arg_cnt_an, ret_ptr_ty_an, 0)
if child_an is not None: return mlirlite.build_bitcast(builder, child_an, i8_ptr_ty_an)
return mlirlite.const_int32(pool, 0)
# Find the function: exact match by bare name in the table (same module), then suffix match in
# the module list (SHA1-prefixed). Module-qualified/attribute calls must not suffix-match (would hit methods).
actual_funcs: GenTypes.FuncEntry* = funcs_ptr
actual_func_count: int = func_count
if actual_funcs is None and trans is not None:
actual_funcs = trans._funcs
actual_func_count = trans._func_count
found_func: mlirlite.Function* = None
if cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
# Bare-name call: allow exact + suffix matching. Custom decorator wrapper rewrite: original
# name -> __decor_wrap_{name} (wrapper is generated at function-translation end and is reachable).
decor_wrap_nm: str = self.Gen.ClassGenBase.lookup_decor_wrapper(func_name)
if decor_wrap_nm is not None: found_func = self.find_func_in_module(mod, decor_wrap_nm)
else:
# Bare-name call to a function defined in this module: hit the module function table first (authoritative for module-level names), then fall back to the module list scan.
mod_fn_tb: mlirlite.Function* = None
if actual_funcs is not None:
# Prefer same-module functions: the _funcs table is polluted by dependency stubs,
# so pass trans.ModuleSha1 to match the "{ModuleSha1}.{name}" entry first (AstTest root cause).
tbl_owner_sha1: str = None
if trans is not None: tbl_owner_sha1 = trans.ModuleSha1
mod_fn_tb = self.Gen.CallGenLeafKit.find_func_in_table(actual_funcs, actual_func_count, func_name, tbl_owner_sha1)
if mod_fn_tb is not None: found_func = mod_fn_tb
else:
# No table entry: fall back to scanning FuncHead, also passing owner_sha1 so an
# inactive same-name forward declaration does not get mis-bound to an earlier stub.
ff_owner_sha1: str = None
if trans is not None: ff_owner_sha1 = trans.ModuleSha1
found_func = self.find_func_in_module(mod, func_name, ff_owner_sha1)
# Reject suffix matches that hit a class method (SHA1.Class.method): a bare-name call sharing a
# name with a module function would bind to the method and recurse infinitely (stack overflow).
if found_func is not None:
bn_name: str = mlirlite.function_get_name(found_func)
if bn_name is not None:
bn_dots: int = 0
bni2: sizet = 0
while bn_name[bni2] != '\0':
if bn_name[bni2] == '.': bn_dots += 1
bni2 += 1
if bn_dots >= 2:
# Extract segment 2 (SHA1.Class.method -> Class; SHA1.submod.func -> submod).
bn_seg2: str = pool.alloc(64)
if bn_seg2 is not None:
bn_seg2[0] = '\0'
bsi2: sizet = 0
while bn_name[bsi2] != '\0' and bsi2 < 17: bsi2 += 1
bse2: sizet = bsi2
while bn_name[bse2] != '\0' and bn_name[bse2] != '.': bse2 += 1
bsl2: sizet = bse2 - bsi2
if bsl2 > 0 and bsl2 < 63:
string.strncpy(bn_seg2, bn_name + bsi2, bsl2)
bn_seg2[bsl2] = '\0'
# Segment 2 is a registered class -> it is a method, reject (use the module SHA1
# path); when X is neither a class nor an imported module it is still a method, reject.
bn_seg2_cls: int = 0
if self.Gen.StructGen.get_struct_type(bn_seg2) is not None: bn_seg2_cls = 1
bn_seg2_mod: int = 0
if trans is not None and trans._imported_modules is not None:
if self.Gen.ImportsGen.is_module_imported_or_known(trans._imported_modules, bn_seg2) != 0: bn_seg2_mod = 1
if bn_seg2_cls != 0 or bn_seg2_mod == 0: found_func = None
else:
# Module-qualified/attribute call: exact match first; suffix match accepts only module
# functions. Reject suffix hits that are methods (SHA1.Class.method), keeping SHA1.func / SHA1.submod.func.
found_func = self.find_func_in_module(mod, func_name)
if found_func is not None:
# Module-qualified call (mod.func): require found_func's SHA1 prefix to equal that module,
# else the stub suffix match may hit another module's same-name function (wrong symbol/crash).
xm_at: ast.Attribute* = ast.Attribute*(cl.func)
if xm_at is not None and xm_at.value is not None and trans is not None and trans._imported_modules is not None:
# Module path: one-level Name (syslibtest_impl.run) or nested Attribute (os._win32.exists).
# The nested case must be validated too, else it suffix-matches this module's own define (recursion).
xm_mod_path: str = None
if xm_at.value.kind() == ast.ASTKind.Name:
xm_nm: ast.Name* = ast.Name*(xm_at.value)
if xm_nm.id is not None: xm_mod_path = xm_nm.id
elif xm_at.value.kind() == ast.ASTKind.Attribute: xm_mod_path = self.Gen.ExprGenAttr._attr_module_path_str(pool, xm_at.value)
if xm_mod_path is not None:
if self.Gen.ImportsGen.is_module_imported_or_known(trans._imported_modules, xm_mod_path) != 0:
mod_sha1_chk: str = self._lookup_module_sha1(xm_mod_path)
if mod_sha1_chk is None and trans._from_imports is not None:
# import X as Y (e.g. import fat32_diskio as diskio): resolve the alias before
# lookup, else this check is skipped and the suffix match hits a same-name local function.
mod_sha1_chk = self.Gen.StructGen._resolve_module_sha1(xm_mod_path, trans._from_imports)
if mod_sha1_chk is not None:
ff_nm2: str = mlirlite.function_get_name(found_func)
if ff_nm2 is not None:
if string.strncmp(ff_nm2, mod_sha1_chk, 16) != 0:
# CExport / t.State 型跨模块函数**定义与声明都用裸名**(stdlib.free、
# CPython 的 PyList_Size 等):裸名不是"绑错了模块",必须放行。
# 否则这里会被清成 None → 关键字实参路径整体跳过 → 静默生成
# 零实参调用(现场:stdlib.free(p = p) → llvm.call @free() → mlir-opt
# 报 incorrect number of operands (0) for callee)。
cx_ok: int = self.Gen.CallGenBase.is_cexport_func(mod_sha1_chk, func_name)
if cx_ok == 0: cx_ok = self._is_state_func_from_vpi(pool, mod_sha1_chk, func_name)
if cx_ok == 0: found_func = None
ff_name: str = mlirlite.function_get_name(found_func)
if ff_name is not None:
ff_dots: int = 0
ffi2: sizet = 0
while ff_name[ffi2] != '\0':
if ff_name[ffi2] == '.': ff_dots += 1
ffi2 += 1
if ff_dots >= 2:
# Extract segment 2 (SHA1.Class.method -> Class; SHA1.submod.func -> submod).
seg2: str = pool.alloc(64)
if seg2 is not None:
seg2[0] = '\0'
si: sizet = 0
while ff_name[si] != '\0' and si < 17: si += 1
seg_start: sizet = si
seg_end: sizet = si
while ff_name[seg_end] != '\0' and ff_name[seg_end] != '.': seg_end += 1
seg_len: sizet = seg_end - seg_start
if seg_len > 0 and seg_len < 63:
string.strncpy(seg2, ff_name + seg_start, seg_len)
seg2[seg_len] = '\0'
# Segment 2 is a registered class -> it is a method, reject (use the module SHA1
# path); when X is neither a class nor an imported module it is still a method, reject (else a module call would bind to Logger.info).
is_seg2_cls: int = 0
if self.Gen.StructGen.get_struct_type(seg2) is not None: is_seg2_cls = 1
is_seg2_mod: int = 0
if trans is not None and trans._imported_modules is not None:
if self.Gen.ImportsGen.is_module_imported_or_known(trans._imported_modules, seg2) != 0: is_seg2_mod = 1
if is_seg2_cls != 0 or is_seg2_mod == 0: found_func = None
# abs(obj): dispatch to __abs__ when the argument is a class instance whose class (with MRO)
# defines it; otherwise keep the existing builtin/int/float abs path.
if found_func is None and cl.func is not None and cl.func.kind() == ast.ASTKind.Name and string.strcmp(func_name, "abs") == 0 and can == 1:
abs_arg: ast.AST* = cargs.get(0)
if abs_arg is not None:
abs_cls: str = self.Gen.CallGenProp._resolve_obj_class_name(pool, trans, abs_arg)
if abs_cls is not None:
abs_sc: str = self.Gen.ExprGenOps._resolve_dunder_by_class(pool, mod, abs_cls, "__abs__", trans)
if abs_sc is not None:
abs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, abs_arg, None, 0, trans)
if abs_val is not None: return self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, abs_sc, "__abs__", abs_val, None, 0, trans)
# Bare C library calls: if not found and known, auto-create a declare (memset/memmove/memcmp/strcmp etc.).
if found_func is None and cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
found_func = self.Gen.CallGenLib._ensure_c_lib_declare(pool, mod, func_name)
# builtins auto-link: when no C library matches, link ord/chr etc. from the builtins module by
# creating a {builtins_sha1}.{name} declare with the agreed signature (ord(i8)->i32, chr(i32)->i8).
if found_func is None:
bi_sha1: str = self._lookup_module_sha1("builtins")
if bi_sha1 is not None:
bi_ret: mlirlite.LLVMType* = None
bi_p1: mlirlite.LLVMType* = None
bi_p2: mlirlite.LLVMType* = None
if string.strcmp(func_name, "ord") == 0:
bi_ret = mlirlite.Int32(pool)
bi_p1 = mlirlite.Int8(pool)
elif string.strcmp(func_name, "chr") == 0:
bi_ret = mlirlite.Int8(pool)
bi_p1 = mlirlite.Int32(pool)
elif string.strcmp(func_name, "min") == 0 or string.strcmp(func_name, "max") == 0:
# min/max numeric comparison (builtins.vp takes/returns double; int/float args are coerced).
bi_ret = mlirlite.Double(pool)
bi_p1 = mlirlite.Double(pool)
bi_p2 = mlirlite.Double(pool)
if bi_ret is not None:
bi_sym: str = func_name
if self.Gen.CallGenBase.is_cexport_func(bi_sha1, func_name) == 0:
bi_sym_len: sizet = string.strlen(bi_sha1) + string.strlen(func_name) + 2
bi_sym_buf: str = pool.alloc(bi_sym_len)
if bi_sym_buf is not None:
viperlib.snprintf(bi_sym_buf, bi_sym_len, "%s.%s", bi_sha1, func_name)
bi_sym = bi_sym_buf
bi_decl: mlirlite.Function* = mlirlite.create_declare(pool, mod, bi_sym, bi_ret)
if bi_decl is not None:
mlirlite.add_param(pool, bi_decl, bi_p1, "v")
if bi_p2 is not None: mlirlite.add_param(pool, bi_decl, bi_p2, "w")
found_func = bi_decl
# Use accessors to bypass stub type restrictions.
found_ret_ty: mlirlite.LLVMType* = None
found_params: rawptr = None
if found_func is not None:
found_ret_ty = mlirlite.function_get_ret_ty(found_func)
found_params = mlirlite.function_get_params(found_func)
if found_ret_ty is not None: call_ret_ty: mlirlite.LLVMType* = found_ret_ty
else:
# found_func=None (cross-module stub not injected): first get the exact return type from the vpi
# annotation cache (handles tuple[T1,T2] -> anonymous struct), then fall back to the whitelist inference.
vpi_ret_ty: mlirlite.LLVMType* = None
# Source module SHA1 for cross-module return-type resolution (assigned only in the Attribute
# branch). Must be initialized to None, else a bare call reads stack garbage and crashes.
mod_ret_sha1: str = None
# Module path (assigned only for Attribute); declared outside the if so the bare-name fallback can read it safely.
mod_path_ret: str = None
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
at_ret: ast.Attribute* = ast.Attribute*(cl.func)
# Resolve the callee module path: both one-level (stdlib.free) and multi-level
# (win32.win32file.CreatePipe). A missing multi-level path would wrongly fail-fast a resolvable call.
if at_ret.value is not None and at_ret.value.kind() == ast.ASTKind.Name:
mod_ret_nm: ast.Name* = ast.Name*(at_ret.value)
mod_path_ret = mod_ret_nm.id
elif at_ret.value is not None and at_ret.value.kind() == ast.ASTKind.Attribute: mod_path_ret = self.Gen.ExprGenAttr._attr_module_path_str(pool, at_ret.value)
if mod_path_ret is not None:
# import X as Y (e.g. import lib.core.Gen.GenApi as GenApi): an exact module-name lookup
# cannot resolve the alias and would wrongly fail-fast; use _resolve_module_sha1 (alias restore + exact/suffix).
fi_from_rt: str = None
if trans is not None: fi_from_rt = trans._from_imports
mod_ret_sha1 = self.Gen.StructGen._resolve_module_sha1(mod_path_ret, fi_from_rt)
if mod_ret_sha1 is not None:
# Lazy in-function import: VpiReader imports HandlesExprCall, so a top-level import would cycle.
VpiReader._ensure_vpi_ret_annot_loaded(mod_ret_sha1, self.Gen.CallGenBase.TempDir)
ret_annot_fc: str = VpiReader._lookup_vpi_ret_annot(mod_ret_sha1, None, func_name)
if ret_annot_fc is None:
# Package vpi miss (the function is defined in a submodule, e.g. fltk.Fl_Input_value in
# fltk._cfl_input): use re-export to locate the defining module, then query its vpi.
rr_src: str = self.Gen.ImportsGen.lookup_reexport(mod_ret_sha1, func_name)
if rr_src is None: rr_src = self._lookup_reexport_from_vpi(pool, mod_ret_sha1, func_name)
if rr_src is not None:
rr_total: sizet = string.strlen(rr_src)
rr_i: sizet = 0
while rr_i < rr_total and ret_annot_fc is None:
while rr_i < rr_total and rr_src[rr_i] == ' ': rr_i += 1
if rr_i >= rr_total: break
rr_start: sizet = rr_i
while rr_i < rr_total and rr_src[rr_i] != ' ': rr_i += 1
rr_len: sizet = rr_i - rr_start
rr_buf: str = pool.alloc(rr_len + 1)
if rr_buf is not None:
string.strncpy(rr_buf, rr_src + rr_start, rr_len)
rr_buf[rr_len] = '\0'
rr_sha1: str = self._lookup_module_sha1(rr_buf)
if rr_sha1 is not None:
VpiReader._ensure_vpi_ret_annot_loaded(rr_sha1, self.Gen.CallGenBase.TempDir)
ret_annot_fc = VpiReader._lookup_vpi_ret_annot(rr_sha1, None, func_name)
if ret_annot_fc is not None: vpi_ret_ty = VpiReader._resolve_vpi_ret_annot_str(pool, ret_annot_fc, mlirlite.Int32(pool), mlirlite.Ptr(pool, mlirlite.Int8(pool)))
if vpi_ret_ty is not None: call_ret_ty = vpi_ret_ty
elif mod_ret_sha1 is not None:
# Cross-module bare CExport function (e.g. mm.malloc -> @malloc): resolve the return type
# exactly from the dependency module, else the default i32 truncates the pointer (mm.malloc root cause).
cross_ret: mlirlite.LLVMType* = self._lookup_method_ret_ty_via_vpi(pool, mod_ret_sha1, None, func_name)
if cross_ret is None:
if trans is not None and trans._declare_only == 0: cross_ret = self._LookupMethodRetTyFromTextLL(pool, mod_ret_sha1, None, func_name, 1)
if cross_ret is not None: call_ret_ty = cross_ret
else:
# Dependency text.ll not generated yet (Phase A middle pass): fatal_if_unknown=0, only a temporary placeholder.
call_ret_ty = self.Gen.CallGenLib._infer_external_func_ret_ty(pool, func_name, node, 0)
else:
# Bare call (cl.func is Name, mod_ret_sha1=None): from-import/closure/function-pointer targets are resolvable (placeholder, no fail-fast); pure external C functions fail-fast.
bare_resolvable: int = 0
if self.Gen.CallGenLeafKit.is_closure_name(func_name) != 0: bare_resolvable = 1
if trans is not None and trans.SymTab is not None:
if self.Gen.VarGen.lookup_var(trans.SymTab, func_name) is not None: bare_resolvable = 1
if trans is not None and trans._from_imports is not None:
if self._lookup_mod_sha1(pool, trans._from_imports, func_name, 0) is not None: bare_resolvable = 1
# M3 session functions: a name hit in the session table is also a placeholder (no fail-fast);
# the return type comes from repl_try_session_func_call, and this call_ret_ty is unused.
if trans is not None and trans.ReplCtx is not None and cl.func is not None and cl.func.kind() == ast.ASTKind.Name and self.Gen.VarGen.repl_find_func_entry(trans.ReplCtx, func_name) is not None:
bare_resolvable = 1
call_ret_ty = self.Gen.CallGenLib._infer_external_func_ret_ty(pool, func_name, node, 1 - bare_resolvable)
if found_func is None:
fp_alloca: mlirlite.Value* = None
if cl.func is None or cl.func.kind() == ast.ASTKind.Name:
# Call to a closure variable captured inside a closure: read the closure object pointer from env;
# lookup_var must not hit the parent function's alloca (SSA disorder -> llc error).
if trans is not None and GenApi.is_nonlocal_name(trans, func_name) != 0:
fp_alloca = self.Gen.StmtGenDecl.load_nonlocal_var(trans, func_name)
if fp_alloca is not None:
# translate_closure_call expects i8** storage (one load gives the closure object
# pointer) but load_nonlocal_var returns the pointer directly, so stash it in a temporary alloca.
i8_ty_nl: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty_nl: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_nl)
tmp_alloca_nl: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, i8_ptr_ty_nl)
if tmp_alloca_nl is not None:
mlirlite.build_store(builder, fp_alloca, tmp_alloca_nl)
fp_alloca = tmp_alloca_nl
else: fp_alloca = self.Gen.VarGen.lookup_var(trans.SymTab, func_name)
if fp_alloca is not None:
# Callable object __call__: obj(args) -> obj.__call__(args) when the variable is a class
# instance whose class (with MRO) defines __call__; otherwise keep the closure/function-pointer path.
if trans is not None and self.Gen.CallGenLeafKit.is_closure_name(func_name) == 0 and self.Gen.CallGenLeafKit._is_func_ptr_alloca(fp_alloca) == 0:
c_obj: mlirlite.Value* = self.Gen.ExprGenBase._deref_if_ptr_ptr(builder, fp_alloca)
if c_obj is None: c_obj = fp_alloca
c_struct: mlirlite.LLVMType* = None
if c_obj is not None: c_struct = self.Gen.StructGen.get_struct_type_from_value(c_obj)
if c_struct is not None:
c_cls: str = self.Gen.StructGen.get_class_name_by_type(pool, c_struct)
if c_cls is not None:
c_call_cls: str = self.Gen.ExprGenOps._resolve_dunder_by_class(pool, mod, c_cls, "__call__", trans)
if c_call_cls is not None: return self.Gen.CallGenCall._translate_method_call(pool, builder, mod, c_call_cls, "__call__", c_obj, cl, trans)
# Closure variable (nested function hoisted and registered): extract fn_ptr/env_ptr and call indirectly.
if self.Gen.CallGenLeafKit.is_closure_name(func_name) != 0:
return self.Gen.CallGenLeafKit.translate_closure_call(pool, builder, mod, fp_alloca, cargs, can, trans)
# Function-pointer variable (t.Callable annotation -> i8** alloca): build the signature from args and call indirectly.
if self.Gen.CallGenLeafKit._is_func_ptr_alloca(fp_alloca) != 0:
# Distinguish t.Callable (true function pointer) from t.CPtr (closure pointer): a t.CPtr
# holds {fn_ptr, env_ptr} and must pass env, else a direct call jumps into closure data and crashes.
ve_fp: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, func_name)
is_cptr_annot_fp: int = 0
fp_annot: str = self.Gen.VarGen.var_annot_class_name(ve_fp)
if fp_annot is not None:
# [S4] CPtr marker = annotation base name exactly "CPtr": t.Callable[[A],R] also resolves to
# Ptr(i8) (same Spec), so only the annotation name can carry the marker; substring matching would misfire.
if TSpec.SpecTypeNameBaseIs(fp_annot, "CPtr") != 0: is_cptr_annot_fp = 1
if is_cptr_annot_fp == 0:
return self.Gen.CallGenLeafKit._translate_func_ptr_call(pool, builder, mod, fp_alloca, func_name, cargs, can, trans)
return self.Gen.CallGenLeafKit.translate_closure_call(pool, builder, mod, fp_alloca, cargs, can, trans)
# Other i8* variables go through the closure call path.
return self.Gen.CallGenLeafKit.translate_closure_call(pool, builder, mod, fp_alloca, cargs, can, trans)
# ---- M3: session-function table hit -> take the ctx function-pointer slot and call indirectly ----
# Placed last in the found_func-is-None block so locals/closures shadow session functions; only foreign bare names reach the table.
sfn_box: rawptr = None
if trans is not None and trans.ReplCtx is not None and (cl.func is None or cl.func.kind() == ast.ASTKind.Name):
sfn_box = stdlib.malloc(4)
if sfn_box is not None:
sfn_box[0] = 0
sfn_val: mlirlite.Value* = self.Gen.VarGen.repl_try_session_func_call(trans, pool, builder, mod, func_name, cargs, int(can), int(kw_count), node, sfn_box)
sfn_handled: int = int(sfn_box[0])
stdlib.free(sfn_box)
if sfn_handled != 0:
# Hit (including a deliberate M3 error): do not take any "undefined/not imported" path; void returns None as expected.
return sfn_val
# 带关键字实参却解析不出被调者签名 → fail-fast(不静默降级)。
# 静默降级会让下面的位置实参循环一个都不翻译,生成零实参调用(现场:stdlib.free(p = p)
# 变成 llvm.call @free(),mlir-opt 报 incorrect number of operands (0) for callee)。
if kw_count > 0 and (found_func is None or found_params is None):
self.Gen.TypeGenErr.fatal_error(cl, f"call '{func_name}' passes keyword argument(s) but the callee signature could not be resolved; arguments must not be dropped silently", "TypeError")
# Translate and link arguments; with keyword args and a known definition, match by parameter name.
if kw_count > 0 and found_func is not None and found_params is not None:
kw_res: mlirlite.Value* = self.Gen.CallGenLib._translate_call_with_kwargs(pool, builder, mod, func_name, found_func, cargs, can, keywords, kw_count, trans)
# P2 willFree:关键字实参路径——实参已在上面的调用里翻译完,此刻按形参序标记失效
if wf_mask != 0 and trans is not None:
self._MarkWillFreeKwargs(trans, wf_mask, cargs, can, keywords, kw_count, found_func)
return kw_res
head: mlirlite.Value* = None
tail: mlirlite.Value* = None
# memcpy only: collect independent references to the first three Values (avoid polluting a shared alloca's Next).
mc_dst2: mlirlite.Value* = None
mc_src2: mlirlite.Value* = None
mc_num2: mlirlite.Value* = None
# "str/bytes is not a generic pointer wildcard" pre-step: same-module bare functions read param
# annotations from this module's .vpi; load that cache once before the loop (bare names only).
same_mod_vpi: int = 0
if trans is not None and trans.ModuleSha1 is not None and cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0:
VpiReader._ensure_vpi_param_annot_loaded(trans.ModuleSha1, self.Gen.CallGenBase.TempDir)
same_mod_vpi = 1
for ai in range(int(can)):
arg: ast.AST* = cargs.get(ai)
if arg is not None:
# Fetch the param type first: a single-quoted char literal (CONST_CHAR) passed to str/pointer
# must materialize a 1-char string, else coercion treats the char value as a pointer and crashes.
fp: mlirlite.Param* = None
fpty: mlirlite.LLVMType* = None
if found_func is not None:
fp = mlirlite.function_get_param_head(found_func)
fpi: int = 0
while fp is not None and fpi < ai:
fp = mlirlite.param_get_next(fp)
fpi += 1
if fp is not None: fpty = mlirlite.param_get_ty(fp)
# Value-reference params (&T / &temp T): auto-address the argument (same-module callee).
ao_name: str = None
if found_func is not None: ao_name = mlirlite.function_get_name(found_func)
arg_val: mlirlite.Value* = self.Gen.CallGenBase.translate_call_arg(builder, pool, mod, trans, ao_name, int(ai), arg, fpty, "param")
if arg_val is not None:
# Arg promotion: when found_func is None (stub not injected), infer the expected type from
# the external signature and promote literals (e.g. i32->i64) to avoid declare/call mismatches.
if found_func is None and arg_val.Ty is not None:
# Array arguments decay to element pointers per C semantics, consistent with _translate_method_call.
if self.Gen.ExprGenBase.is_ptr_type(arg_val.Ty) != 0:
arr_pointee_fc: mlirlite.LLVMType* = arg_val.Ty.Pointee
if arr_pointee_fc is not None:
match arr_pointee_fc:
case mlirlite.LLVMType.Array(arr_et_fc, _):
decayed_fc: mlirlite.Value* = mlirlite.build_gep_array(builder, arr_pointee_fc, arr_et_fc, arg_val, mlirlite.const_int32(pool, 0))
if decayed_fc is not None: arg_val = decayed_fc
case _:
pass
# Known vararg C functions (snprintf/sprintf/vsprintf): coerce fixed args by signature
# (C ABI requires i64 size), else call/declare mismatch and Win64 leaves stack garbage in the high 4 bytes.
kvc_ty: mlirlite.LLVMType* = self.Gen.CallGenVa._get_known_vararg_cfunc_param_ty(pool, func_name, ai)
if kvc_ty is not None: arg_val = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val, kvc_ty, arg)
# Prefer the definition-side vpi param annotation for module-qualified calls: the
# whitelist treats all non-pointer Win32 args as i64, but ULONG is now i32 (call/declare mismatch).
ext_param_ty: mlirlite.LLVMType* = None
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
at_pt: ast.Attribute* = ast.Attribute*(cl.func)
mpath_pt: str = None
if at_pt.value is not None and at_pt.value.kind() == ast.ASTKind.Name:
nm_pt: ast.Name* = ast.Name*(at_pt.value)
mpath_pt = nm_pt.id
elif at_pt.value is not None and at_pt.value.kind() == ast.ASTKind.Attribute: mpath_pt = self.Gen.ExprGenAttr._attr_module_path_str(pool, at_pt.value)
if mpath_pt is not None:
fi_pt: str = None
if trans is not None: fi_pt = trans._from_imports
sha_pt: str = self.Gen.StructGen._resolve_module_sha1(mpath_pt, fi_pt)
if sha_pt is not None:
if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0: VpiReader._ensure_vpi_param_annot_loaded(sha_pt, self.Gen.CallGenBase.TempDir)
ext_param_ty = VpiReader._resolve_vpi_param_annot_seg(pool, sha_pt, None, func_name, ai, mlirlite.Int32(pool), mlirlite.Ptr(pool, mlirlite.Int8(pool)))
# Fallback only: use the whitelist when the definition-side annotation is unavailable.
if ext_param_ty is None: ext_param_ty = self.Gen.CallGenLib._get_external_func_param_ty(pool, func_name, ai)
if ext_param_ty is not None:
val_bits_ap: int = self.Gen.ExprGenBase.get_llvm_type_bits(arg_val.Ty)
tgt_bits_ap: int = self.Gen.ExprGenBase.get_llvm_type_bits(ext_param_ty)
# A pointer target reports bit width 0, so the integer-width guard alone skips it and
# the arg stays an integer (e.g. the null-pointer constant 0 stays i32) in the call,
# mismatching the declared pointer param (llvm.call operand type mismatch). Coerce
# pointer targets through coerce_to_type too: it materializes the compile-time constant
# 0 as a null pointer and fails fast on any other implicit int<->ptr conversion.
if self.Gen.ExprGenBase.is_ptr_type(ext_param_ty) != 0:
arg_val = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val, ext_param_ty, arg)
elif val_bits_ap != 0 and tgt_bits_ap != 0 and val_bits_ap != tgt_bits_ap:
if val_bits_ap == 1 and val_bits_ap < tgt_bits_ap: arg_val = mlirlite.build_zext(builder, arg_val, ext_param_ty)
else: arg_val = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val, ext_param_ty, arg)
elif found_func is not None:
# found_func present: coerce to the param type (e.g. stdlib.malloc(size: sizet)); a
# CDefine/i32 literal passed to an i64 param would otherwise leave call(i32) vs define(i64) mismatched.
if fp is not None:
if fpty is not None:
# "str/bytes is not a generic pointer wildcard": a same-module bare function whose
# param annotation is exactly str/bytes but receives an aggregate pointer fails fast (same rule as the cross-module path).
sm_is_str: int = 0
if same_mod_vpi != 0:
if VpiReader._vpi_param_annot_seg_is(trans.ModuleSha1, None, func_name, ai, "str") != 0 or VpiReader._vpi_param_annot_seg_is(trans.ModuleSha1, None, func_name, ai, "bytes") != 0:
sm_is_str = 1
if sm_is_str != 0: self.Gen.ExprGenBase.fail_if_str_annot_agg_ptr(arg_val, 1, arg)
arg_val = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val, fpty, arg, trans)
elif found_func.IsVarArg == 1:
# Win64 variadic 8-byte slots: pass sub-64-bit integers as i64 and promote f32 to f64;
# passing the narrow type leaves old stack data in the high 4 bytes that va_arg(i64) reads as garbage.
# The widening direction follows the argument's signedness (unsigned -> zext), matching
# CallGenVa._check_and_promote_variadic_call: an unconditional sext turns uint8 0x8E into
# 0xFFFFFFFFFFFFFF8E (the "sext family" recorded in Debugger/VKERNELERR.md).
ab_va: int = self.Gen.ExprGenBase.get_llvm_type_bits(arg_val.Ty)
af_va: int = self.Gen.ExprGenBase.get_llvm_float_bits(arg_val.Ty)
i64_va: mlirlite.LLVMType* = mlirlite.Int64(pool)
if ab_va != 0 and ab_va < 64:
if self.Gen.ExprGenOps.should_zext_widen(pool, arg_val, None, arg, trans) != 0: arg_val = mlirlite.build_zext(builder, arg_val, i64_va)
else: arg_val = mlirlite.build_sext(builder, arg_val, i64_va)
elif af_va == 32: arg_val = mlirlite.build_fpext(builder, arg_val, mlirlite.Double(pool))
arg_val.Next = None
if head is None: head = arg_val
else: tail.Next = arg_val
tail = arg_val
# memcpy only: save independent references to the first three arguments.
if ai == 0: mc_dst2 = arg_val
elif ai == 1: mc_src2 = arg_val
elif ai == 2: mc_num2 = arg_val
# P2 willFree:实参已全部翻译完,此刻把被标注形参位上的实参变量标为"已失效"。
# 只处理位置实参且实参是简单变量名(与编译器"标记实参变量槽"的口径一致);
# 该槽其后被读/取址/再传(含再传 free)时由变量读取路径报 LifetimeError。
# 关键字实参路径(_translate_call_with_kwargs)不支持该标记:见文档记录的限制。
if wf_mask != 0 and cargs is not None and trans is not None:
for ai_wf in range(int(can)):
if ((wf_mask >> int(ai_wf)) & 1) == 0: continue
arg_wf: ast.AST* = cargs.get(ai_wf)
if arg_wf is None or arg_wf.kind() != ast.ASTKind.Name: continue
nm_wf: ast.Name* = ast.Name*(arg_wf)
if nm_wf.id is None: continue
self.Gen.VarGen._FreedMark(self.Gen.VarGen.lookup_var(trans.SymTab, nm_wf.id))
# Default-argument filling: when fewer args than params, fill the gaps; the defaults array is
# right-aligned to args (defaults[0] maps to args[offset], offset = param_count - default_count).
if can > 0 or (can == 0 and cargs is not None):
df_defaults: rawptr = None
df_default_count: int = 0
df_param_count: int = 0
# Compute the callee module SHA1 as a qualifier key (module.func -> that module; bare -> current),
# so usb.uhci.init(1 arg) does not wrongly match mm.init (5 params) and add extra arguments.
dfx_callee_sha: str = None
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
dfx_at: ast.Attribute* = ast.Attribute*(cl.func)
dfx_mod_path: str = None
if dfx_at.value is not None and dfx_at.value.kind() == ast.ASTKind.Name:
dfx_mn: ast.Name* = ast.Name*(dfx_at.value)
if dfx_mn.id is not None: dfx_mod_path = dfx_mn.id
elif dfx_at.value is not None and dfx_at.value.kind() == ast.ASTKind.Attribute:
# Nested module path (os._win32.exists): resolve the full path, like the call-name path below.
dfx_mod_path = self.Gen.ExprGenAttr._attr_module_path_str(pool, dfx_at.value)
if dfx_mod_path is not None:
# Resolve the alias before the SHA1 lookup: _lookup_module_sha1 is an exact module-name lookup
# and returns None for `import X.Y as Z` (Z is not a module name). Without the alias restore the
# qualifier degrades to the current module SHA1, so find_func_defaults misses the defining module
# and the defaults are never filled (rhttps.get(url, verify) emits 2 operands for a 3-param callee).
dfx_fi: str = None
if trans is not None: dfx_fi = trans._from_imports
dfx_callee_sha = self.Gen.StructGen._resolve_module_sha1(dfx_mod_path, dfx_fi)
if dfx_callee_sha is None and self.Gen.TypeGenBase is not None: dfx_callee_sha = self.Gen.TypeGenBase.CurrentModuleSha1
# Look up the FuncEntry in the module table qualified by the defining module SHA1: the table
# contains bare-name dependency stubs, so an unqualified hit would pick mm.init for usb.uhci.init (N17).
if trans is not None and trans._funcs is not None:
df_entry: GenTypes.FuncEntry* = self.Gen.CallGenLeafKit.find_func_entry_in_table_owned(trans._funcs, trans._func_count, func_name, dfx_callee_sha)
if df_entry is not None:
df_defaults = df_entry.Defaults
df_default_count = df_entry.DefaultCount
df_param_count = df_entry.ParamCount
# Always query the global defaults table: the module table's Defaults point into the pool and
# dangle after 1b reset, so use the permanent stdlib snapshot to rebuild defaults (else gd_entry is uninitialized).
gd_entry: GenTypes.FuncDefaultsEntry* = self.Gen.CallGenLeafKit.find_func_defaults(func_name, dfx_callee_sha)
# Package re-export fallback (e.g. json.write defined in includes/json/__writer.vp): the defaults
# table is keyed by the defining module SHA1, so re-resolve the re-export and query again (else defaults are skipped).
if gd_entry is None and dfx_callee_sha is not None:
rr_src_dx: str = self.Gen.ImportsGen.lookup_reexport(dfx_callee_sha, func_name)
if rr_src_dx is None: rr_src_dx = self._lookup_reexport_from_vpi(pool, dfx_callee_sha, func_name)
if rr_src_dx is not None:
rr_sha_dx: str = self._lookup_module_sha1(rr_src_dx)
if rr_sha_dx is not None: gd_entry = self.Gen.CallGenLeafKit.find_func_defaults(func_name, rr_sha_dx)
# Override when df_default_count<=0 too: the caller table may hold a same-name entry with empty
# Defaults (a VpiReader declare), so the real global entry must win or filling never triggers.
if gd_entry is not None and (df_defaults is None or df_default_count <= 0):
df_defaults = gd_entry.Defaults
df_default_count = gd_entry.DefaultCount
df_param_count = gd_entry.ParamCount
# Cross-check: the global table is keyed by bare name, so same-name functions bleed (uhci.init vs
# mm.init). If found_func's real param count differs from ParamCount, drop the table and fall back.
if found_func is not None and df_default_count > 0:
dfx_cnt: int = 0
dfx_p: mlirlite.Param* = mlirlite.function_get_param_head(found_func)
while dfx_p is not None:
dfx_nm: str = mlirlite.param_get_name(dfx_p)
if dfx_nm is None or (string.strcmp(dfx_nm, "%__eh_msg_out__") != 0 and string.strcmp(dfx_nm, "%__eh_code_out__") != 0):
dfx_cnt += 1
dfx_p = mlirlite.param_get_next(dfx_p)
if dfx_cnt > 0 and dfx_cnt != df_param_count:
df_defaults = None
df_default_count = 0
df_param_count = 0
# Fill in the missing arguments.
if df_default_count > 0 and can < df_param_count and (df_defaults is not None or gd_entry is not None):
df_offset: int = df_param_count - df_default_count
for df_di in range(int(df_default_count)):
df_param_idx: int = df_offset + df_di
if df_param_idx >= can:
# Prefer rebuilding from the stdlib snapshot (gd_entry.Defaults' in-pool AST may already be reclaimed).
df_arg_val: mlirlite.Value* = None
if gd_entry is not None: df_arg_val = self.Gen.CallGenLeafKit._build_const_from_snapshot(pool, mod, gd_entry, df_di, trans, builder)
if df_arg_val is None and df_defaults is not None:
df_defaults_list: list[ast.AST*]* = list[ast.AST*]*(df_defaults)
df_default_node: ast.AST* = df_defaults_list.get(df_di)
if df_default_node is not None: df_arg_val = self.Gen.ExprGen.translate_value(builder, pool, mod, df_default_node, None, 0, trans)
if df_arg_val is not None:
df_arg_val.Next = None
if head is None: head = df_arg_val
else: tail.Next = df_arg_val
tail = df_arg_val
# Appended default args must increment can: the downstream coerce/declare steps rebuild
# the chain bounded by can, so not counting them truncates the trailing defaults (json.write OOM root cause).
can += 1
# fail-fast: this callee is known to have defaults, but they could not be rebuilt into
# values (snapshot missing / not a simple constant). Emitting fewer operands than the
# callee declares yields invalid IR that only the MLIR verifier catches
# ("'llvm.call' op incorrect number of operands (N) for callee"); stop here instead.
if can < df_param_count:
self.Gen.TypeGenErr.fatal_error(cl, f"Cross-module default argument cannot be rebuilt into a value (snapshot missing, or the default is not a simple constant): {func_name}; parameter {can + 1} would be missing, which emits an llvm.call with fewer operands than the callee declares", "TypeError")
elif found_func is not None:
# Zero-value fallback when defaults are missing: use found_func's real param count (excluding EH out-params %__eh_msg_out__/%__eh_code_out__) and fill missing args with zeros.
fc_cnt: int = 0
fc_p: mlirlite.Param* = mlirlite.function_get_param_head(found_func)
while fc_p is not None:
fp_nm_z: str = mlirlite.param_get_name(fc_p)
if fp_nm_z is None or (string.strcmp(fp_nm_z, "%__eh_msg_out__") != 0 and string.strcmp(fp_nm_z, "%__eh_code_out__") != 0):
fc_cnt += 1
fc_p = mlirlite.param_get_next(fc_p)
if fc_cnt == 0: fc_cnt = df_param_count if df_param_count > 0 else 0
if can < fc_cnt:
fp_z: mlirlite.Param* = mlirlite.function_get_param_head(found_func)
fpi_z: int = 0
while fp_z is not None and fpi_z < can:
fp_z = mlirlite.param_get_next(fp_z)
fpi_z += 1
while fp_z is not None and fpi_z < fc_cnt:
zero_ty_z: mlirlite.LLVMType* = mlirlite.param_get_ty(fp_z)
zero_val_z: mlirlite.Value* = mlirlite.ConstZero(pool, zero_ty_z)
if zero_val_z is not None:
zero_val_z.Next = None
if head is None: head = zero_val_z
else: tail.Next = zero_val_z
tail = zero_val_z
can += 1 # Zero-value fallback args also count toward the arg count (like defaults).
fp_z = mlirlite.param_get_next(fp_z)
fpi_z += 1
# Use the actual function name from the module (may carry a SHA1 prefix); CExtern/State/CExport
# functions use the bare name. Both declare and define must use found_func.Name (mismatched names cause undefined references).
call_name: str = func_name
# Original function name (recorded for aliased imports, e.g. from X import parse as json_parse -> "parse"),
# used to re-infer the return type after alias handling so _infer_external_func_ret_ty can match.
orig_func_name: str = None
if found_func is not None:
found_name: str = mlirlite.function_get_name(found_func)
if found_name is not None:
call_name = found_name
# CExport bare-name fallback: a cross-module stub declare may carry a SHA1 prefix while the
# definition uses @malloc, causing a link error; extract the sha1 and use the bare name if it is a CExport.
if found_func.IsDeclared != 0:
# Use the single source of truth GenApi.sha1_prefix_len (converged in C10).
if GenApi.sha1_prefix_len(found_name) != 0:
cx_sha1_buf: str = pool.alloc(17)
if cx_sha1_buf is not None:
string.memcpy(cx_sha1_buf, found_name, 16)
cx_sha1_buf[16] = '\0'
if self.Gen.CallGenBase.is_cexport_func(cx_sha1_buf, func_name) != 0: call_name = func_name
else:
# Cross-module call: when found_func is None, check for a module-qualified call (string.strchr -> @{sha1_of_string}.strchr).
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
xmod_at: ast.Attribute* = ast.Attribute*(cl.func)
if xmod_at.value is not None and (xmod_at.value.kind() == ast.ASTKind.Name or xmod_at.value.kind() == ast.ASTKind.Attribute):
# Module path: one-level Name (string.strchr) or nested Attribute (os._win32.exists). The nested
# case must be resolved too, else it suffix-matches this module's own define (infinite recursion).
xmod_path: str = None
if xmod_at.value.kind() == ast.ASTKind.Name:
xmod_nm: ast.Name* = ast.Name*(xmod_at.value)
if xmod_nm.id is not None: xmod_path = xmod_nm.id
else: xmod_path = self.Gen.ExprGenAttr._attr_module_path_str(pool, xmod_at.value)
if xmod_path is not None:
mod_sha1: str = self._lookup_module_sha1(xmod_path)
if mod_sha1 is not None:
# fast-fail: a module-qualified call requires the module to be imported by this file;
# the global SHA1 store contains all scanned includes, so an unimported module would silently resolve.
is_xmod_imported: int = 1
if trans is not None and trans._imported_modules is not None:
is_xmod_imported = self.Gen.ImportsGen.is_module_imported_or_known(trans._imported_modules, xmod_path)
if is_xmod_imported == 0:
err_ni: str = pool.alloc(256)
if err_ni is not None:
viperlib.snprintf(err_ni, 256, "Cross-module call '%s.%s': module '%s' not imported (import %s first)", xmod_path, func_name, xmod_path, xmod_path)
self.Gen.TypeGenErr.fatal_error(xmod_at, err_ni)
self.Gen.TypeGenErr.fatal_error(xmod_at, "Cross-module call: module not imported")
# Check whether it is a re-export function (e.g. ast._lexer_init actually defined in ast.lexer).
reexport_src: str = self.Gen.ImportsGen.lookup_reexport(mod_sha1, func_name)
if reexport_src is None:
# Re-export table miss (includes modules with existing stub/text are not fully translated,
# so the table may be empty): resolve the module's .vpi from-import to locate the defining module.
reexport_src = self._lookup_reexport_from_vpi(pool, mod_sha1, func_name)
rs_found: int = 0
if reexport_src is not None:
# Star-import multiple candidates (space-separated, e.g. fltk/__init__ from _types/_enums/_cfl
# import *): verify each via is_cexport_func/_is_state_func_from_vpi to find where func_name is really defined.
cs_total: sizet = string.strlen(reexport_src)
cs_i: sizet = 0
while cs_i < cs_total:
while cs_i < cs_total and reexport_src[cs_i] == ' ': cs_i += 1
if cs_i >= cs_total: break
cs_start: sizet = cs_i
while cs_i < cs_total and reexport_src[cs_i] != ' ': cs_i += 1
cs_seg: sizet = cs_i - cs_start
cs_buf: str = pool.alloc(cs_seg + 1)
if cs_buf is not None:
string.strncpy(cs_buf, reexport_src + cs_start, cs_seg)
cs_buf[cs_seg] = '\0'
cand_sha1: str = self._lookup_module_sha1(cs_buf)
if cand_sha1 is not None:
if self.Gen.CallGenBase.is_cexport_func(cand_sha1, func_name) != 0 or self._is_state_func_from_vpi(pool, cand_sha1, func_name) != 0:
mod_sha1 = cand_sha1
call_name = func_name
rs_found = 1
break
if rs_found == 0:
# All star-import candidates failed: func_name may be defined in the original module
# itself (e.g. stdlib.malloc, while stdlib's `from stdint import *` adds a false candidate).
if self.Gen.CallGenBase.is_cexport_func(mod_sha1, func_name) != 0 or self._is_state_func_from_vpi(pool, mod_sha1, func_name) != 0:
rs_found = 1
call_name = func_name
else:
# Original module has a plain def (not CExport/State): a vpi hit keeps the original
# module sha (@{mod_sha1}.func) instead of falling back to a star candidate that would hijack resolution.
orig_has_def: int = 0
if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0:
VpiReader._ensure_vpi_ret_annot_loaded(mod_sha1, self.Gen.CallGenBase.TempDir)
if VpiReader._lookup_vpi_ret_annot(mod_sha1, None, func_name) is not None: orig_has_def = 1
if orig_has_def == 0:
# Original module does not define it: fall back to the first candidate (e.g. ast._lexer_init in ast.lexer).
reexport_sha1: str = self._lookup_module_sha1(reexport_src)
if reexport_sha1 is not None: mod_sha1 = reexport_sha1
# CExport functions define with the bare name (@strlen), so the cross-module call must use the bare name too.
if rs_found != 0:
pass # Re-export defining module already verified and bare name set.
elif self.Gen.CallGenBase.is_cexport_func(mod_sha1, func_name) != 0: call_name = func_name
elif self._is_state_func_from_vpi(pool, mod_sha1, func_name) != 0:
# t.State external C function (e.g. CPython PyList_Size): the definition uses the bare
# name @PyList_Size, so the cross-module call must too (a sha1 prefix causes an undefined symbol).
call_name = func_name
else:
mangled_len: sizet = string.strlen(mod_sha1) + string.strlen(func_name) + 2
mangled: str = pool.alloc(mangled_len)
if mangled is not None:
viperlib.snprintf(mangled, mangled_len, "%s.%s", mod_sha1, func_name)
call_name = mangled
else:
# Only real modules reach this path; a missing module segment falls back to a bare name.
if xmod_path is None: call_name = func_name
else:
# Check whether it is an imported module name (e.g. BuildPipeline.run_pipeline): if imported
# but its SHA1 is unregistered the module was not compiled, so error out instead of a misleading from_imports lookup.
is_imported_mod: int = 0
if trans is not None and trans._imported_modules is not None: is_imported_mod = self.Gen.ImportsGen.is_module_imported(trans._imported_modules, xmod_path)
if is_imported_mod != 0:
# Aliased import (import X as Y): Y is in _imported_modules but the exact SHA1 lookup fails
# (mod_arr holds file names); try from_imports (alias -> full name -> suffix SHA1) with allow_star_fallback=0.
alias_sha1: str = None
if trans is not None and trans._from_imports is not None: alias_sha1 = self._lookup_mod_sha1(pool, trans._from_imports, xmod_path, 0)
if alias_sha1 is not None:
if self.Gen.CallGenBase.is_cexport_func(alias_sha1, func_name) != 0: call_name = func_name
elif self._is_state_func_from_vpi(pool, alias_sha1, func_name) != 0:
# t.State external C function: use the bare name (same as the main path).
call_name = func_name
else:
AliasMangledLen: sizet = string.strlen(alias_sha1) + string.strlen(func_name) + 2
alias_mangled: str = pool.alloc(AliasMangledLen)
if alias_mangled is not None:
viperlib.snprintf(alias_mangled, AliasMangledLen, "%s.%s", alias_sha1, func_name)
call_name = alias_mangled
else:
# Imported module but SHA1 unregistered: the module was not compiled, so error out immediately.
err_msg_imp: str = pool.alloc(256)
if err_msg_imp is not None:
viperlib.snprintf(err_msg_imp, 256, "Module '%s' is imported but its SHA1 is not registered (the module was not compiled)", xmod_path)
self.Gen.TypeGenErr.fatal_error(xmod_at, err_msg_imp)
self.Gen.TypeGenErr.fatal_error(xmod_at, "Imported module SHA1 not registered")
else:
# Check whether it is a from-import name (e.g. from .__types import LLVMType): LLVMType is a
# class, not a module, but Ptr is a module-level function of __types; disable star-import fallback.
from_sha1: str = None
if trans is not None and trans._from_imports is not None: from_sha1 = self._lookup_mod_sha1(pool, trans._from_imports, xmod_path, 0)
if from_sha1 is not None:
# CExport functions define with the bare name, so the cross-module call must use the bare name.
if self.Gen.CallGenBase.is_cexport_func(from_sha1, func_name) != 0: call_name = func_name
else:
fmangled_len: sizet = string.strlen(from_sha1) + string.strlen(func_name) + 2
fmangled: str = pool.alloc(fmangled_len)
if fmangled is not None:
viperlib.snprintf(fmangled, fmangled_len, "%s.%s", from_sha1, func_name)
call_name = fmangled
else:
err_msg2: str = pool.alloc(256)
if err_msg2 is not None:
viperlib.snprintf(err_msg2, 256, "Cross-module call '%s.%s': module SHA1 not found", xmod_path, func_name)
self.Gen.TypeGenErr.fatal_error(xmod_at, err_msg2)
self.Gen.TypeGenErr.fatal_error(xmod_at, "Cross-module call: module SHA1 not found")
else:
# Bare-name call (not Attribute): check from_imports. Star-import fallback is disabled for calls
# (star import is for types/constants); a missed function then fails fast at compile time instead of linking wrong.
if trans is not None and trans._from_imports is not None:
bare_sha1: str = self._lookup_mod_sha1(pool, trans._from_imports, func_name, 0)
if bare_sha1 is not None:
# Alias handling: from X import Y as Z -> calling Z must mangle with Y (e.g. from json.__parser
# import parse as json_parse -> SHA1.parse, not SHA1.json_parse).
bare_orig: str = self.Gen.ImportsGen.lookup_from_import_original(trans._from_imports, func_name)
bare_func_name: str = func_name
if bare_orig is not None:
# bare_orig points into the from_imports buffer and is not NUL-terminated, so copy it
# into a separate buffer (truncating at space or NUL).
bo_len: sizet = 0
while bare_orig[bo_len] != ' ' and bare_orig[bo_len] != '\0': bo_len += 1
bo_buf: str = pool.alloc(bo_len + 1)
if bo_buf is not None:
for bo_i in range(int(bo_len)): bo_buf[bo_i] = bare_orig[bo_i]
bo_buf[bo_len] = '\0'
bare_func_name = bo_buf
# Record the original function name for later return-type inference (from json.__parser
# import parse as json_parse -> "parse" matches while "json_parse" would default to i32).
orig_func_name = bare_func_name
# Re-export resolution: a from-imported bare function may actually be defined in a submodule
# (e.g. dump defined in ast.base, ast/__init__ only re-exports), so redirect to the defining module SHA1.
reexport_src2: str = self.Gen.ImportsGen.lookup_reexport(bare_sha1, bare_func_name)
if reexport_src2 is None:
# Re-export table miss: resolve the module's .vpi from-import on demand.
reexport_src2 = self._lookup_reexport_from_vpi(pool, bare_sha1, bare_func_name)
if reexport_src2 is not None:
reexport_sha1_2: str = self._lookup_module_sha1(reexport_src2)
if reexport_sha1_2 is not None: bare_sha1 = reexport_sha1_2
if self.Gen.CallGenBase.is_cexport_func(bare_sha1, bare_func_name) != 0: call_name = bare_func_name
else:
bare_mangled_len: sizet = string.strlen(bare_sha1) + string.strlen(bare_func_name) + 2
bare_mangled: str = pool.alloc(bare_mangled_len)
if bare_mangled is not None:
viperlib.snprintf(bare_mangled, bare_mangled_len, "%s.%s", bare_sha1, bare_func_name)
call_name = bare_mangled
else:
err_msg3: str = pool.alloc(256)
if err_msg3 is not None:
viperlib.snprintf(err_msg3, 256, "Bare-name call '%s' is undefined and not imported", func_name)
self.Gen.TypeGenErr.fatal_error(node, err_msg3)
self.Gen.TypeGenErr.fatal_error(node, "Bare-name call undefined and not imported")
else:
err_msg4: str = pool.alloc(256)
if err_msg4 is not None:
viperlib.snprintf(err_msg4, 256, "Bare-name call '%s' is undefined and not imported", func_name)
self.Gen.TypeGenErr.fatal_error(node, err_msg4)
self.Gen.TypeGenErr.fatal_error(node, "Bare-name call undefined and not imported")
# Direct call. memcpy -> @llvm.memcpy intrinsic (avoids a missing cross-module @memcpy declaration during monomorphization).
if string.strcmp(func_name, "memcpy") == 0 and can >= 3: return self.Gen.CallGenLib._emit_llvm_memcpy_intrinsic(pool, builder, mod, mc_dst2, mc_src2, mc_num2)
# Re-infer the return type for aliased imports: with func_name="json_parse" the whitelist returns i32,
# so use orig_func_name="parse" to match i8* (JsonValue*) and avoid pointer truncation crashes.
if orig_func_name is not None and orig_func_name != func_name:
# Alias attempt only: a temporary inference before the mangled exact resolution (fatal_if_unknown=0);
# a mangled call_name is later overridden by the text.ll/vpi exact resolution.
re_inferred_ty: mlirlite.LLVMType* = self.Gen.CallGenLib._infer_external_func_ret_ty(pool, orig_func_name, node, 0)
if re_inferred_ty is not None: call_ret_ty = re_inferred_ty
# Exact cross-module Viper return-type resolution (fail-fast): the whitelist does not cover pointer-
# returning functions and the default i32 truncates them; the authority is text.ll define, then the vpi annotation.
if self.Gen.CallGenLib._is_sha1_mangled_call(call_name) != 0:
if trans is None or trans._declare_only == 0:
cf_dot: str = string.strchr(call_name, '.')
cf_last: str = string.strrchr(call_name, '.')
if cf_dot is not None and cf_last is not None:
cf_sha1: str = stdlib.malloc(17)
cf_bare_len: sizet = string.strlen(call_name) - (sizet(uint64(cf_last) - uint64(call_name))) - 1
cf_bare: str = stdlib.malloc(cf_bare_len + 1)
if cf_sha1 is not None and cf_bare is not None:
string.strncpy(cf_sha1, call_name, 16)
cf_sha1[16] = '\0'
string.strcpy(cf_bare, cf_last + 1)
# Same-module function (e.g. generic specialization): take the return type from the current
# IR; do not cross-module lookup text.ll (its own is not generated yet in Phase A and would mis-warn).
is_local_mod: int = 0
if trans is not None and trans.ModuleSha1 is not None:
if string.strncmp(cf_sha1, trans.ModuleSha1, 16) == 0: is_local_mod = 1
if is_local_mod != 0:
local_fn: mlirlite.Function* = self.find_func_in_module(mod, call_name)
if local_fn is not None and local_fn.RetTy is not None: call_ret_ty = local_fn.RetTy
else:
cf_vpi: mlirlite.LLVMType* = self._lookup_method_ret_ty_via_vpi(pool, cf_sha1, None, cf_bare)
if cf_vpi is not None: call_ret_ty = cf_vpi
else:
cf_ret: mlirlite.LLVMType* = self._LookupMethodRetTyFromTextLL(pool, cf_sha1, None, cf_bare, 1)
if cf_ret is not None: call_ret_ty = cf_ret
else:
# Dependency text.ll does not exist (module not yet translated): Phase A translates files
# in order (main.vp before its imported mbuddytest.vp), so keep the default type without fail-fast.
if self._dep_text_ll_exists(pool, cf_sha1) != 0: self.Gen.TypeGenErr.fatal_error(node, f"Cannot obtain the cross-module function return type precisely (neither vpi nor text.ll hit); the default i32 will truncate pointers: {call_name}", "TypeError")
stdlib.free(cf_sha1)
stdlib.free(cf_bare)
# Bare C library function (string.memset -> @memset): promote args to the standard signature
# (third arg i32 literal -> i64) before creating the declare, so call/declare/define widths match.
head = self.Gen.CallGenLib._promote_clib_call_args(pool, builder, mod, call_name, head, can)
# Cross-module module-level call ({sha1}.{func}): coerce args to the vpi definition-side param types
# (exact integer widths), so call args / declare / define all agree (B3 generalization).
head = self.Gen.CallGenLib._coerce_args_to_def_params(pool, builder, call_name, head, can, cargs, 0, trans)
# Cross-module call: create the declare to avoid llc's undefined value error.
self._ensure_method_declare(pool, mod, call_name, call_ret_ty, head, int(can))
# Check whether the callee is variadic (*args) and, if so, promote the variadic args to the C ABI
# (cargs is the arg AST list; self_offset=0 for a plain function call).
is_variadic_call: int = self.Gen.CallGenVa._check_and_promote_variadic_call(pool, builder, mod, call_name, head, can, cargs, sizet(0), trans)
# try-except: when the callee takes EH out-params, append the output args and check for exceptions after the call.
return self.Gen.CallGenLeafKit._emit_eh_guarded_call(builder, pool, mod, trans, found_func, call_name, head, can, call_ret_ty, is_variadic_call)