fix(gen): 模块限定构造按模块名解析 SHA1,修复 ctor 归属内核同名类

gfx.Scene3D(...) 的模块限定构造路径(Name 模块基)误用 trans.ModuleSha1
(当前文件 sha1,类在 gfx 模块)→ find_struct_by_module 落空 → 裸名回退
find_struct("Scene3D") 命中跨模块同名类中先注册的 entry(VKernel gfx 与
App gfx 都定义 Scene3D,0ae 先经 includes 预注册)→ ctor 生成
@0ae.Scene3D.__before_init__ → 链接 undefined symbol(Scene3D 根因)。

改为按模块名 _lookup_module_sha1("gfx") 精确解析 SHA1 后
find_struct_by_module 命中 App 版(9aab),与注解解析路径一致。
This commit is contained in:
TermiNexus committed 2026-08-26 17:29:48 +08:00
1 parent 9b56d1d75c
commit 0a28df0864
1 file changed
+68 -17
+68 -17
View File
@@ -85,14 +85,6 @@ class CallGenMixin(CallGenBase.CallGenBase):
can: t.CSizeT = 0
if cargs is not None:
can = len(cargs)
# [DET] len() 结果核对(SlotsBugTest/StringIterTest can 垃圾高位定位:
# can=0x229_0000_0000=[cargs高32][count低32],怀疑 len() 生成代码 4 字节
# 错位。门控:真实 count 远小于 2^32,can>=2^32 即异常,打印定位)。
if (t.CUInt64T(can) & (t.CUInt64T(1) << 32)) != 0:
lh_l: t.CUInt64T | t.CPtr = (t.CUInt64T | t.CPtr)(t.CVoid(cargs, t.CPtr))
stdio.printf("[DET] len结果异常: can=%llu cargs=%llu data=%llu count=%llu cap=%llu\n",
t.CUInt64T(can), t.CUInt64T(cargs), lh_l[0], lh_l[1], lh_l[2])
# super()/ctraits 检测:void 方法(如 __init__)命中后 build_call 返回
# None 属预期(void call 无 SSA 值),必须用 hit_box 区分"命中已处理"
# 与"未命中",否则 None 被误判继续主路径 → 内层 Call(super) 当裸名
@@ -248,8 +240,18 @@ class CallGenMixin(CallGenBase.CallGenBase):
mod_nm_qc: ast.Name | t.CPtr = (ast.Name | t.CPtr)(at_qc.value)
imp_ok: int = self.Gen.ImportsGen.is_module_imported(trans._imported_modules, mod_nm_qc.id)
if imp_ok != 0:
if trans is not None:
gen_ctor_entry = self.Gen.StructGen.find_struct_by_module(func_name, trans.ModuleSha1)
# 模块限定构造 Module.Class(...):按模块名解析 SHA1 精确命中类。
# 不能用 trans.ModuleSha1(那是当前文件 sha1,类在 gfx 模块)——
# find_struct_by_module 落空后裸名回退 find_struct 会命中跨模块
# 同名类中先注册的 entry(VKernel gfx 与 App gfx 都定义 Scene3D,
# 0ae 先注册 → ctor 生成 @0ae.Scene3D.__before_init__ → 链接
# undefined symbol,Scene3D 根因)。
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:
@@ -347,12 +349,6 @@ class CallGenMixin(CallGenBase.CallGenBase):
return self.Gen.CallGenCtor._translate_struct_ctor_kw(
pool, builder, mod, struct_ty, func_name, cargs, can,
keywords, kw_count, trans)
# 仅位置参数:按字段顺序构建
# [DET] 调用前 can 核对(与 len结果/callee 收到的 can 三级对照,
# 定位 4 字节错位发生在 len 计算/调用传参/callee 读取哪一级)
if (t.CUInt64T(can) & (t.CUInt64T(1) << 32)) != 0:
stdio.printf("[DET] 调用前 can=%llu cargs=%llu\n",
t.CUInt64T(can), t.CUInt64T(cargs))
return self.Gen.CallGenCtor._translate_struct_ctor(
pool, builder, mod, struct_ty, func_name, cargs, can, trans)
@@ -607,6 +603,16 @@ class CallGenMixin(CallGenBase.CallGenBase):
if self.Gen.ImportsGen.is_module_imported(
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(如 import fat32_diskio as diskio):
# 精确查不到别名,必须按别名还原后再查(精确→后缀→
# LookupModNameByAlias 三级)。否则本校验被跳过 →
# find_func_in_module 后缀匹配误命中本模块同名函数
# (diskio.read 命中 fat32 自身 read)→ call 到错误
# 符号 + 参数错位 + define 被 4 参调用污染(VKernel
# llc "instruction expected to be numbered" 根因)。
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 = llvmlite.function_get_name(found_func)
if ff_nm2 is not None:
@@ -652,12 +658,19 @@ class CallGenMixin(CallGenBase.CallGenBase):
if bi_sha1 is not None:
bi_ret: llvmlite.LLVMType | t.CPtr = None
bi_p1: llvmlite.LLVMType | t.CPtr = None
bi_p2: llvmlite.LLVMType | t.CPtr = None
if string.strcmp(func_name, "ord") == 0:
bi_ret = llvmlite.Int32(pool)
bi_p1 = llvmlite.Int8(pool)
elif string.strcmp(func_name, "chr") == 0:
bi_ret = llvmlite.Int8(pool)
bi_p1 = llvmlite.Int32(pool)
elif string.strcmp(func_name, "min") == 0 or string.strcmp(func_name, "max") == 0:
# min/max 数值比较(builtins.vp 定义 double 参数/返回,
# int/float 实参经 coerce 提升;两参数)
bi_ret = llvmlite.Double(pool)
bi_p1 = llvmlite.Double(pool)
bi_p2 = llvmlite.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:
@@ -670,6 +683,8 @@ class CallGenMixin(CallGenBase.CallGenBase):
pool, mod, bi_sym, bi_ret)
if bi_decl is not None:
llvmlite.add_param(pool, bi_decl, bi_p1, "v")
if bi_p2 is not None:
llvmlite.add_param(pool, bi_decl, bi_p2, "w")
found_func = bi_decl
# 使用访问器绕过 stub 类型限制
found_ret_ty: llvmlite.LLVMType | t.CPtr = None
@@ -720,7 +735,25 @@ class CallGenMixin(CallGenBase.CallGenBase):
if found_func is None:
fp_alloca: llvmlite.Value | t.CPtr = None
if cl.func is None or cl.func.kind() == ast.ASTKind.Name:
fp_alloca = self.Gen.VarGen.lookup_var(trans.SymTab, func_name)
# 闭包内调用捕获的闭包变量(如 _cmd_cd 调 _build_path):
# 从 env 读闭包对象指针,不能 lookup_var 跨函数命中父函数
# 闭包 alloca(SSA 错乱 → llc "instruction expected to be
# numbered",Terminal _cmd_cd 根因)。
if trans is not None and HT.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 期望 i8** 存储(load 一次得闭包
# 对象指针);load_nonlocal_var 已返回对象指针 → 临时
# alloca 暂存
i8_ty_nl: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
i8_ptr_ty_nl: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty_nl)
tmp_alloca_nl: llvmlite.Value | t.CPtr = llvmlite.build_alloca(
builder, i8_ptr_ty_nl)
if tmp_alloca_nl is not None:
llvmlite.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:
# 闭包变量(嵌套函数提升,已注册到闭包名集合):提取 fn_ptr/env_ptr 间接调用
if self.Gen.CallGenLeafKit.is_closure_name(func_name) != 0:
@@ -962,6 +995,24 @@ class CallGenMixin(CallGenBase.CallGenBase):
found_name: str = llvmlite.function_get_name(found_func)
if found_name is not None:
call_name = found_name
# CExport 裸名兜底:跨模块 stub declare 可能带 SHA1 前缀
# (@"1fdad1c76fb116bc.malloc"),但定义端 CExport 函数
# (mm.vp malloc/free/malloc_direct,返回注解含 t.CExport)
# 用裸名 @malloc → 链接 undefined symbol: 1fdad1c76fb116bc.malloc。
# found_func 非 None 时下方 is_cexport_func 裸名分支不执行
# (该分支仅 found_func None 的模块限定路径),这里对带前缀
# 的 declare 提取 sha1 再查 CExport 注册表,命中则改用裸名。
if found_func.IsDeclared != 0:
dot_pos_cx: str = string.strchr(found_name, 46)
if dot_pos_cx is not None:
cx_pre_len: t.CSizeT = string.strlen(found_name) - string.strlen(dot_pos_cx)
if cx_pre_len == 16:
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:
# 跨模块函数调用:found_func 为 None 时,检查是否是模块限定调用
# 如 string.strchr(...) → @{sha1_of_string}.strchr