1452 lines
114 KiB
Plaintext
1452 lines
114 KiB
Plaintext
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")
|
||
if func_name is None:
|
||
# BinOp as the Call func: a union-type annotation used as a type cast, (A*)(value).
|
||
if cl.func is not None and cl.func.kind() == ast.ASTKind.BinOp and trans is not None:
|
||
binop_ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, cl.func, trans._imported_modules, trans._from_imports, trans, 0)
|
||
if binop_ty is not None:
|
||
binop_args: list[ast.AST*]* = cl.args
|
||
if binop_args is not None and len(binop_args) == 1:
|
||
binop_arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, binop_args.get(0), funcs_ptr, func_count, trans)
|
||
if binop_arg_val is not None: return self.Gen.CallGenCtor._translate_t_type_cast(pool, builder, binop_arg_val, binop_ty)
|
||
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)
|