fix(tpv): 依赖注入期默认参数按定义模块 SHA1 登记,消除跨模块同名函数串味

N17:VpiReader 注入函数/方法 declare 时显式传入 src_sha1(被依赖模块自身
sha1),add_func_to_table 透传至 _register_global_defaults;调用点默认值查找
改用 find_func_entry_in_table_owned 按 "{owner_sha1}." 前缀限定。修复
usb.uhci.init 误命中 mm.init 的 5 形参/2 默认值而凭空补 2 个 i32 0 的 ABI
错位(IR 实证:call @...init(i16 %4, i32 0, i32 0) → (i16 %4))。

同轮并入的其他编译器侧改动:
- N11 Converter.vp:无 -> 但带 @c.CReturn 时合成 tuple 返回注解,与 define 对齐
- N12 VpiReader.vp:union 指针(T | t.CPtr)stub 解析与定义侧收敛到 i8*
- N14 ExprGenFstr.vp:f-string 32 位整型不再写 l 修饰符
- N2 撤回 ExprGenOps.vp:删除在 _node_type_unsigned 热点查/填 vpi 缓存的
  _call_ret_annot_unsigned(有副作用,触发 fat32_diskio.vp match 路径崩溃)
This commit is contained in:
TermiNexus committed 2026-09-11 17:39:21 +08:00
1 parent e66d9e3adb
commit d3690f9143
7 files changed
+124 -92

No files matched your search

+45
View File
@@ -424,6 +424,37 @@ def _PyiDefaultStr(buf: bytes, buf_size: t.CSizeT, pos: t.CSizeT,
return pos
# ============================================================
# _PyiCreturnArgs - 从装饰器列表提取 @c.CReturn(...) 的实参列表
#
# N11:`@c.CReturn(a, b, c)` 在定义侧被 FuncGenBase._resolve_creturn_ret_ty
# 展开为聚合返回类型 `{a, b, c}`,但 vpi 里该函数只有装饰器行、没有 `->` 注解
# → stub declare 退化成 i32(mm.GetMemoryStats 定义 `{i64,i64,i64}` vs
# `declare i32`)。生成 vpi 时据此合成 `-> tuple[a, b, c]`
# (_resolve_vpi_ret_annot_str 的既有 tuple 分支同样打包成 Struct),
# 使 stub declare 与 define 的返回类型一致。
# 返回 None = 装饰器列表里没有 CReturn。
# ============================================================
def _PyiCreturnArgs(decorators: list[ast.AST | t.CPtr] | t.CPtr) -> list[ast.AST | t.CPtr] | t.CPtr:
"""在装饰器列表里找 @c.CReturn(...),返回其实参列表;未找到返回 None"""
if decorators is None:
return None
dn: t.CSizeT = len(decorators)
di: t.CSizeT = 0
while di < dn:
dnode: ast.AST | t.CPtr = decorators.get(di)
di += 1
if dnode is None or dnode.kind() != ast.ASTKind.Call:
continue
dcall: ast.Call | t.CPtr = (ast.Call | t.CPtr)(dnode)
if dcall.func is None or dcall.func.kind() != ast.ASTKind.Attribute:
continue
dat: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(dcall.func)
if dat.attr is not None and string.strcmp(dat.attr, "CReturn") == 0:
return dcall.args
return None
# ============================================================
# _PyiFunction - 生成函数签名
#
@@ -518,6 +549,20 @@ def _PyiFunction(buf: bytes, buf_size: t.CSizeT, pos: t.CSizeT,
if fn.returns is not None:
pos = _PyiEmit(buf, buf_size, pos, " -> ")
pos = _PyiTypeStr(buf, buf_size, pos, fn.returns)
else:
# 无 `->` 注解但带 @c.CReturn(...):合成 " -> tuple[a, b, c]"(N11)
cre_args: list[ast.AST | t.CPtr] | t.CPtr = _PyiCreturnArgs(fn.decorator_list)
if cre_args is not None:
cre_n: t.CSizeT = len(cre_args)
if cre_n > 0:
pos = _PyiEmit(buf, buf_size, pos, " -> tuple[")
cre_i: t.CSizeT = 0
while cre_i < cre_n:
if cre_i > 0:
pos = _PyiEmit(buf, buf_size, pos, ", ")
pos = _PyiTypeStr(buf, buf_size, pos, cre_args.get(cre_i))
cre_i += 1
pos = _PyiEmit(buf, buf_size, pos, "]")
pos = _PyiEmit(buf, buf_size, pos, ": pass\n")
return pos
+30 -3
View File
@@ -1021,6 +1021,11 @@ def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy | t.CPtr,
has_cptr: int = 0
has_cvoid: int = 0
first_ty: llvmlite.LLVMType | t.CPtr = None
# 产生 first_ty 那一段文本的属性(供末尾 has_cptr 分支复刻定义侧规则,N12):
# first_qual=1 段是限定名(含 '.',如 t.CUInt64T / mod.Class)
# first_wl =1 段是白名单裸标量名(int/char/str/bytes/bool/float/double)
first_qual: int = 0
first_wl: int = 0
buf_len: t.CSizeT = string.strlen(ret_annot)
pos: t.CSizeT = 0
@@ -1050,6 +1055,20 @@ def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy | t.CPtr,
seg_ty: llvmlite.LLVMType | t.CPtr = _resolve_single_vpi_type_str(pool, seg_buf)
if seg_ty is not None and first_ty is None:
first_ty = seg_ty
# 记录该段属性(N12):定义侧 TypeGenResolve.resolve_annotation_type
# 对**裸名** pointee 仅在它解析为已注册结构体、泛型参数名或白名单
# 裸标量时才保留 pointee,否则联合坍缩为 i8*;stub 侧此前无条件
# Ptr(first_ty) → `uint64 | t.CPtr` 两侧不一致(定义 i8* vs declare i64*)。
if string.strchr(seg_buf, 46) is not None:
first_qual = 1
if string.strcmp(seg_buf, "int") == 0 or \
string.strcmp(seg_buf, "char") == 0 or \
string.strcmp(seg_buf, "str") == 0 or \
string.strcmp(seg_buf, "bytes") == 0 or \
string.strcmp(seg_buf, "bool") == 0 or \
string.strcmp(seg_buf, "float") == 0 or \
string.strcmp(seg_buf, "double") == 0:
first_wl = 1
stdlib.free(seg_buf)
pos += 1 # 跳过 '|'
@@ -1057,7 +1076,15 @@ def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy | t.CPtr,
if has_cvoid != 0:
return i8_ptr_ty
if first_ty is not None:
return llvmlite.Ptr(pool, first_ty)
# 复刻定义侧规则(N12):限定名 / 白名单裸标量 / 已注册结构体 → 保留
# pointee;其余**裸名**(如 uint64/uint32 等 typedef 别名、裸枚举名)
# → 联合坍缩为 i8*,与 resolve_annotation_type 一致,避免
# `save_buf: uint64 | t.CPtr` 定义侧 i8* 而 stub 侧 i64* 跨模块不收敛。
if first_qual != 0 or first_wl != 0:
return llvmlite.Ptr(pool, first_ty)
if first_ty.kind() == llvmlite.LLVMType.Struct:
return llvmlite.Ptr(pool, first_ty)
return i8_ptr_ty
return i8_ptr_ty
if has_cvoid != 0:
# t.CVoid | t.State:State 是装饰器标记(编译侧忽略),t.CVoid → void。
@@ -2320,7 +2347,7 @@ def _inject_function_declare(pool: memhub.MemBuddy | t.CPtr,
if funcs_ptr is not None:
cur_count: int = trans._func_count
if GenApi.add_func_to_table(funcs_ptr, cur_count, fd.name, func, max_funcs,
fd_defaults, fd_default_count, param_count) == 0:
fd_defaults, fd_default_count, param_count, None, src_sha1) == 0:
trans._func_count = cur_count + 1
# 补全全局函数表 TypeStr(参数已全部添加,对齐 FuncGenDef L211 行为):
# vpi 注入路径若不补全,跨模块 vtable/调用解析 find_func_entry_global()
@@ -2512,7 +2539,7 @@ def _inject_method_declare(pool: memhub.MemBuddy | t.CPtr,
if param_count_no_self > 0:
param_count_no_self -= 1
if GenApi.add_func_to_table(funcs_ptr, cur_count, lookup_name, func, max_funcs,
md_defaults, md_default_count, param_count_no_self) == 0:
md_defaults, md_default_count, param_count_no_self, None, src_sha1) == 0:
trans._func_count = cur_count + 1
# 补全全局函数表 TypeStr(参数已全部添加,对齐 ClassGenVTable L832 行为):
# vpi 注入的方法声明若不补全 TypeStr,跨模块继承 vtable 生成时
+18 -8
View File
@@ -994,7 +994,8 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
defaults: t.CVoid | t.CPtr = None,
default_count: int = 0,
param_count: int = 0,
pool: memhub.MemBuddy | t.CPtr = None) -> int:
pool: memhub.MemBuddy | t.CPtr = None,
def_sha1: str = None) -> int:
"""添加函数到函数表,返回 0 成功
Args:
@@ -1019,7 +1020,7 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
self._register_global_func(name, func, pool)
# 同时注册到全局默认参数表(供跨模块调用时填充默认值)
if default_count > 0:
self._register_global_defaults(name, defaults, default_count, param_count)
self._register_global_defaults(name, defaults, default_count, param_count, def_sha1)
return 0
@@ -1172,21 +1173,30 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
def _register_global_defaults(self, name: str,
defaults: t.CVoid | t.CPtr,
default_count: int,
param_count: int) -> int:
param_count: int,
def_sha1: str = None) -> int:
"""注册函数默认参数到全局表,返回 0 成功
注册时提取每个默认值 Constant 的常量快照到 stdlib(ConstKinds/ConstInts/ConstStrs),
跨模块调用时优先用快照重建常量 IR,避免读取被 reset 回收的 pool 内 AST。
def_sha1:**定义该函数的模块 SHA1**。依赖注入路径(VpiReader)必须显式传入,
否则会退回 CurrentModuleSha1 —— 注入期它是**导入方**,会把被依赖模块函数的
默认值挂到导入方名下(如 mm.init 的 2 个默认值挂到 usb)→ usb 的
`uhci.init(io_base)` 经 find_func_defaults("init", usb_sha) 命中该错账条目
而被补 2 个 i32 0(N17 真因;模块函数表的"按定义模块限定查找"救不了这条
全局表路径,因为该处 found_func 仍为 None、交叉校验被整体跳过)。
"""
if name is None or defaults is None:
return 1
if self._init_global_defaults_table() == 0:
return 1
# 当前模块 SHA1 作为限定键:同一函数名在不同模块各存一条,
# 查询时按「名 + 定义模块」精确匹配,消除跨模块同名串味
# (usb.uhci.init 1 参误命中 mm.init 5 参/2 默认值根因)。
cur_sha: str = None
if self.Gen is not None and self.Gen.TypeGenBase is not None:
# 限定键 = **定义该函数的模块 SHA1**:同一函数名在不同模块各存一条,查询时
# 按「名 + 定义模块」精确匹配,消除跨模块同名串味。def_sha1 由依赖注入
# 路径显式传入被依赖模块自身 sha1;非注入路径(本模块真实定义)取
# CurrentModuleSha1(此时它就是定义模块)。
cur_sha: str = def_sha1
if cur_sha is None and self.Gen is not None and self.Gen.TypeGenBase is not None:
cur_sha = self.Gen.TypeGenBase.CurrentModuleSha1
# 同模块同名已存在则跳过
if self.find_func_defaults(name, cur_sha) is not None:
+21 -18
View File
@@ -1088,13 +1088,14 @@ class CallGenMixin(CallGenBase.CallGenBase):
# 默认参数填充:当实参数量 < 形参数量时,用默认值填充缺失参数
# defaults 数组右对齐到 args:defaults[0] 对应 args[offset],offset = param_count - default_count
if can > 0 or (can == 0 and cargs is not None):
df_defaults: t.CVoid | t.CPtr = None
df_default_count: int = 0
df_param_count: int = 0
df_defaults: t.CVoid | t.CPtr = None
df_default_count: int = 0
df_param_count: int = 0
# 计算被调模块 SHA1 作限定键(消除跨模块同名函数串味):
# module.func(...) → 该 module 的 SHA1;裸名调用 → 当前模块 SHA1。
# 全局默认参数表按「名 + 定义模块 SHA1」精确匹配,避免 usb.uhci.init(1 参)
# 误命中 mm.init(5 参/2 默认值) 而补出多余实参。
# 模块表与全局默认参数表都按「名 + 定义模块 SHA1」精确匹配,避免
# usb.uhci.init(1 参) 误命中 mm.init(5 参/2 默认值) 而补出多余实参。
# 必须先算(模块表查找也要用它限定定义模块 → 见下)。
dfx_callee_sha: str = None
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
dfx_at: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(cl.func)
@@ -1104,10 +1105,15 @@ class CallGenMixin(CallGenBase.CallGenBase):
dfx_callee_sha = self._lookup_module_sha1(dfx_mn.id)
if dfx_callee_sha is None and self.Gen.TypeGenBase is not None:
dfx_callee_sha = self.Gen.TypeGenBase.CurrentModuleSha1
# 先从当前模块函数表查找 FuncEntry —— 必须按**定义模块 SHA1 限定**:
# 表内含 LoadDepVpiIntoModule 以裸名注入的依赖 stub(如 mm.init,
# 槽位在前),裸名首命中会让 usb 的 `uhci.init(io_base)`(1 形参)
# 拿到 mm.init 的 5 形参/2 默认值 → 调用点被补 2 个 i32 0(N17)。
# 从当前模块函数表查找 FuncEntry —— 必须按**定义模块 SHA1 限定**:
# 表内含 LoadDepVpiIntoModule 以裸名注入的依赖 stub(槽位在前),
# 裸名首命中会让 usb 的 `uhci.init(io_base)`(1 形参)拿到 mm.init 的
# 5 形参/2 默认值 → 调用点被补 2 个 i32 0(N17)。
# 注:仅做「精确到定义模块」这一项改动;下方交叉校验守卫(dfx_cnt)
# 保持原样(`dfx_cnt > 0 and ...`)——收紧它会让 dfx_cnt==0 的场景
# 落入 `elif found_func is not None` 零值兜底分支,改动面更大,
# 且本项改动已足以修掉 N17(受限查找命中本模块/同定义模块条目,
# Defaults 为 None 或本模块池内 AST)。
if trans is not None and trans._funcs is not None:
df_entry: GenTypes.FuncEntry | t.CPtr = self.Gen.CallGenLeafKit.find_func_entry_in_table_owned(
trans._funcs, trans._func_count, func_name, dfx_callee_sha)
@@ -1125,14 +1131,11 @@ class CallGenMixin(CallGenBase.CallGenBase):
df_defaults = gd_entry.Defaults
df_default_count = gd_entry.DefaultCount
df_param_count = gd_entry.ParamCount
# 交叉校验:默认参数表(模块表裸名 / 全局表按导入方 sha1 登记)仍可能
# 跨模块同名串味(如 usb.uhci.init 只有 1 形参,却拿到 mm.init 的
# 5 形参/2 默认值记录 → 被补 2 个 i32 0 → call 3 实参 vs declare 1 形参
# → ABI 错位,N17)。若被调声明(found_func)的实际形参数与表记录
# ParamCount 不符,则丢弃该表,交由下方 `elif found_func is not None`
# 分支按真实形参数零值兜底。
# 注意:必须覆盖 dfx_cnt == 0(found_func 被后缀匹配成同模块无参同名
# 函数时原有 `dfx_cnt > 0` 守卫失效 → usb.uhci.init(1 参) 仍被补参)。
# 交叉校验:全局默认参数表按「裸函数名」登记/查询,跨模块同名函数会串味
# (如 usb.uhci.init 只有 1 形参,却命中 mm.init 的 5 形参/2 默认值记录
# → 被补 2 个 i32 0 → call 3 实参 vs declare 1 形参 → ABI 错位)。
# 若被调声明(found_func)的实际形参数与表记录 ParamCount 不符,则丢弃该表,
# 交由下方 `elif found_func is not None` 分支按真实形参数零值兜底。
if found_func is not None and df_default_count > 0:
dfx_cnt: int = 0
dfx_p: llvmlite.Param | t.CPtr = llvmlite.function_get_param_head(found_func)
@@ -1142,7 +1145,7 @@ class CallGenMixin(CallGenBase.CallGenBase):
and string.strcmp(dfx_nm, "%__eh_code_out__") != 0):
dfx_cnt += 1
dfx_p = llvmlite.param_get_next(dfx_p)
if dfx_cnt != df_param_count:
if dfx_cnt > 0 and dfx_cnt != df_param_count:
df_defaults = None
df_default_count = 0
df_param_count = 0
+4 -2
View File
@@ -338,10 +338,12 @@ class ExprGenFstr(ExprGenBase.ExprGenBase):
return None
len_mod[0] = '\0'
ibits: int = self.Gen.ExprGenBase.get_llvm_type_bits(val.Ty)
# 32 位**不写**长度修饰符(N14):`{x:08X}` 的实参是 i32,此前推导出
# `l` → 格式串 `%08lX`(long)与实参宽度不符(LP64 下 printf 按 64 位
# 读参 → 变参错位)。与同文件无 spec 路径 `_fstr_infer_c_fmt`(32 位用
# `%u/%d`)以及 TPC 侧 HandlesExprFormat.py 保持一致。
if ibits == 64:
string.strcpy(len_mod, "ll")
elif ibits == 32:
string.strcpy(len_mod, "l")
elif ibits == 16:
string.strcpy(len_mod, "h")
elif ibits == 8:
-58
View File
@@ -235,12 +235,6 @@ class ExprGenOps(ExprGenBase.ExprGenBase):
and at.attr is not None:
if self._type_name_unsigned(at.attr) != 0:
return 1
# 用户函数/方法调用:查被调方 vpi 返回类型注解(N2)。
# `read(LAPIC_ID) >> 24`(apic.read 返回 uint32)此前落到 return 0
# → translate_binop 生成 ashr(应 lshr)。查询失败一律返回 0(本函数
# 契约:只读、失败返回 0、绝不 fatal)。
if self._call_ret_annot_unsigned(cl, trans) != 0:
return 1
return 0
if k == ast.ASTKind.Name:
nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(node)
@@ -322,58 +316,6 @@ class ExprGenOps(ExprGenBase.ExprGenBase):
fi_u += 1
return 0
return 0
def _call_ret_annot_unsigned(self, cl: ast.Call | t.CPtr,
trans: HT.Translator | t.CPtr) -> int:
"""用户函数/方法调用的返回类型是否为无符号整型(查被调方 vpi 返回注解)
1=无符号,0=否/未知。N2 根因:apic.`read(offset: uint32) -> uint32` 的
调用结果参与 `>> 24` 时,Call 分支只认 `t.CXXXT(...)` 强转 → 返回 0 →
translate_binop 选 ashr(应 lshr)。
严格遵循 _node_type_unsigned 的契约「只读、失败返回 0」:
- 不做任何 fatal_error / 断言;
- vpi 未生成、TempDir 为空、模块名不可解、注解缺失等一律返回 0
(退回既有有符号语义,不引入新语义)。
返回注解缓存为幂等(已加载即跳过),故热路径仅一次哈希查表。
"""
if cl is None or cl.func is None or trans is None:
return 0
callee_sha: str = None
callee_meth: str = None
fk: int = cl.func.kind()
if fk == ast.ASTKind.Name:
# 裸名调用(同模块函数或 from-import):按当前模块 sha1 限定
nm_f: ast.Name | t.CPtr = (ast.Name | t.CPtr)(cl.func)
if nm_f.id is None:
return 0
callee_meth = nm_f.id
callee_sha = trans.ModuleSha1
elif fk == ast.ASTKind.Attribute:
# module.func(...) / 模块限定调用:按模块名反查定义模块 sha1
at_r: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(cl.func)
if at_r.attr is None or at_r.value is None:
return 0
if at_r.value.kind() != ast.ASTKind.Name:
return 0
mod_r: ast.Name | t.CPtr = (ast.Name | t.CPtr)(at_r.value)
if mod_r.id is None or string.strcmp(mod_r.id, "t") == 0:
return 0
callee_meth = at_r.attr
callee_sha = self.Gen.CallGenBase._lookup_module_sha1(mod_r.id)
else:
return 0
if callee_sha is None or callee_meth is None:
return 0
# 函数内懒 import:与 CallGenLib._coerce_args_to_def_params 同源,规避循环依赖
import lib.Stub.StubGen.VpiReader as VpiReader
td_r: str = self.Gen.CallGenBase.TempDir
if td_r is not None and self.Gen.CallGenBase.TempDirLen > 0:
VpiReader._ensure_vpi_ret_annot_loaded(callee_sha, td_r)
ret_annot_r: str = VpiReader._lookup_vpi_ret_annot(callee_sha, None, callee_meth)
if ret_annot_r is None:
return 0
return self._annot_str_unsigned(ret_annot_r)
def _type_name_unsigned(self, type_name: str) -> int:
"""类型名是否为无符号整数类型(精确匹配,避免子串误伤如 nUIntCount)"""
if type_name is None:
+6 -3
View File
@@ -594,12 +594,15 @@ def find_func_in_module(mod: llvmlite.LLVMModule | t.CPtr, name: str) -> llvmlit
# ---------------- add_func_to_table(委托 CallGenLeafKit.add_func_to_table) ----------------
def add_func_to_table(funcs: GenTypes.FuncEntry | t.CPtr, func_count: int, name: str, func: llvmlite.Function | t.CPtr, max_count: int, defaults: t.CVoid | t.CPtr = None, default_count: int = 0, param_count: int = 0, pool: memhub.MemBuddy | t.CPtr = None) -> int:
"""委托到 gen.add_func_to_table(原 Handles 壳)"""
def add_func_to_table(funcs: GenTypes.FuncEntry | t.CPtr, func_count: int, name: str, func: llvmlite.Function | t.CPtr, max_count: int, defaults: t.CVoid | t.CPtr = None, default_count: int = 0, param_count: int = 0, pool: memhub.MemBuddy | t.CPtr = None, def_sha1: str = None) -> int:
"""委托到 gen.add_func_to_table(原 Handles 壳)
def_sha1:定义该函数的模块 SHA1(依赖注入路径必须传,避免默认值被挂到导入方名下)
"""
import lib.core.Gen.Generator as Generator
g: Generator.Generator | t.CPtr = (Generator.Generator | t.CPtr)(t.CVoid(_g_gen, t.CPtr))
if g is not None:
return g.CallGenLeafKit.add_func_to_table(funcs, func_count, name, func, max_count, defaults, default_count, param_count, pool)
return g.CallGenLeafKit.add_func_to_table(funcs, func_count, name, func, max_count, defaults, default_count, param_count, pool, def_sha1)
return 0