fix(callgenbase): _ensure_method_declare 定义侧参数宽度优先,根治字面量实参固化 i32

declare 此前用首个调用点实参类型建参数,字面量 0=i32 把定义 t.CSizeT(i64) 的参数固化窄(SlotsBugTest _translate_struct_ctor can=i32 vs 定义 i64 → 调用侧 trunc、callee 按 i64 读栈残留垃圾高位 → can=巨值读未写槽位崩溃根因)。

修复:从 call_name 提取 {sha1}.{cls}.{method} 查 vpi 参数注解缓存(SetTempDir 已在 Phase1 设置,按需加载可用),仅当实参与定义都是整数且定义更宽时采用定义宽度(指针/联合保持实参类型,避免字符串路径把 list*/Struct* 降级 i8*)。
This commit is contained in:
TermiNexus committed 2026-08-25 17:01:31 +08:00
1 parent 4d14eeff67
commit d7ed91e3e5
1 file changed
+103 -2
+103 -2
View File
@@ -546,16 +546,80 @@ class CallGenBase:
if func is None:
return
func.IsDeclared = 1
# 定义侧参数类型优先(根治实参字面量固化错误宽度):
# declare 此前用调用点实参类型(param_head)建参数,字面量 0=i32 会把
# 定义 t.CSizeT(i64) 的参数固化窄(SlotsBugTest _translate_struct_ctor
# can=i32 vs 定义 i64 → 调用侧 trunc i64→i32、callee 按 i64 读栈残留
# 垃圾高位 → can=巨值读未写槽位崩溃根因)。从 call_name 提取
# {sha1}.{cls}.{method} 查 vpi 参数注解缓存取定义侧类型:仅当实参与
# 定义都是整数且定义更宽时采用定义宽度(指针/联合保持实参类型,避免
# 字符串路径把 list*/Struct* 降级为 i8*)。
sha1_d: str = None
cls_d: str = None
meth_d: str = None
if call_name is not None:
cl_len_d: t.CSizeT = string.strlen(call_name)
if cl_len_d >= 17 and call_name[16] == '.':
sha1_ok_d: int = 1
si_d: t.CSizeT
for si_d in range(16):
cc_d: int = call_name[si_d]
if not ((cc_d >= 48 and cc_d <= 57) or (cc_d >= 97 and cc_d <= 102)):
sha1_ok_d = 0
break
if sha1_ok_d != 0:
sha1_buf_d: bytes = stdlib.malloc(17)
if sha1_buf_d is not None:
string.strncpy(sha1_buf_d, call_name, 16)
sha1_buf_d[16] = '\0'
sha1_d = sha1_buf_d
rest_d: str = call_name + 17
dot2_d: str = string.strchr(rest_d, '.')
if dot2_d is not None:
cls_len_d: t.CSizeT = t.CUInt64T(dot2_d) - t.CUInt64T(rest_d)
if cls_len_d > 0:
cls_buf_d: bytes = stdlib.malloc(cls_len_d + 1)
if cls_buf_d is not None:
string.strncpy(cls_buf_d, rest_d, cls_len_d)
cls_buf_d[cls_len_d] = '\0'
cls_d = cls_buf_d
meth_d = dot2_d + 1
else:
meth_d = rest_d
prm_annots_d: str = None
if sha1_d is not None and meth_d is not None:
prm_annots_d = self._vpi_method_param_annot(pool, sha1_d, cls_d, meth_d)
i32_ty_d: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool)
i8_ptr_d: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, llvmlite.Int8(pool))
# 添加参数(declare 中参数名无意义,设为 None 只输出类型)
pnode: llvmlite.ParamNode | t.CPtr = param_head
pi: int = 0
while pnode is not None and pi < param_count:
if pnode.Ty is not None:
param: llvmlite.Param | t.CPtr = llvmlite.new_param(pool, pnode.Ty, None)
param_ty_d: llvmlite.LLVMType | t.CPtr = pnode.Ty
if prm_annots_d is not None and prm_annots_d[0] != '\0' and pnode.Ty is not None:
seg_ty_d: llvmlite.LLVMType | t.CPtr = VpiReader._resolve_vpi_param_annot_seg(
pool, sha1_d, cls_d, meth_d, pi, i32_ty_d, i8_ptr_d)
if seg_ty_d is not None:
match pnode.Ty:
case llvmlite.LLVMType.Int(bits_a):
match seg_ty_d:
case llvmlite.LLVMType.Int(bits_b):
if bits_b > bits_a:
param_ty_d = seg_ty_d
case _:
pass
case _:
pass
if param_ty_d is not None:
param: llvmlite.Param | t.CPtr = llvmlite.new_param(pool, param_ty_d, None)
if param is not None:
llvmlite.function_add_param(func, param)
pnode = pnode.Next
pi += 1
if sha1_d is not None:
stdlib.free(sha1_d)
if cls_d is not None:
stdlib.free(cls_d)
llvmlite.module_add_function(mod, func)
# ============================================================
@@ -587,6 +651,43 @@ class CallGenBase:
return None
return VpiReader._resolve_vpi_ret_annot_str(pool, annot, i32_ty, i8_ptr_ty)
# ============================================================
# _vpi_method_param_annot - 从 vpi 参数注解缓存获取方法参数注解串
#
# 跨模块方法调用在 stub 未注入(依赖收集不加载被调模块 vpi,如
# self.Gen.ExprGenCdef.method 经 Generator 属性访问)时,函数表无
# ParamCount/默认参数信息 → 调用点缺参 → 被调函数读栈残留。此函数
# 按需加载并查询 vpi 参数注解缓存('@' 分隔,含首参 self 空串占位),
# 调用方据此补全 declare/call 缺失参数(默认参数零值兜底)。
# 未命中返回 None。
# ============================================================
def _vpi_method_param_annot(self, pool: memhub.MemBuddy | t.CPtr,
cls_sha1: str,
cls_name: str,
method_name: str) -> str:
"""按需加载并查询 vpi 参数注解串,未命中返回 None"""
if pool is None or cls_sha1 is None or cls_name is None or method_name is None:
return None
# 按需加载:缓存未命中时直接读 {temp_dir}/{sha1[0:2]}/{sha1}.vpi
if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0:
VpiReader._ensure_vpi_param_annot_loaded(cls_sha1, self.Gen.CallGenBase.TempDir)
return VpiReader._lookup_vpi_param_annots(cls_sha1, cls_name, method_name)
# ============================================================
# _vpi_resolve_param_annot_ty - 解析 vpi 参数注解段为 LLVMType
#
# 与 _lookup_method_ret_ty_via_vpi 的返回注解解析同源;解析失败返回
# None,调用方以 i8* 兜底(ABI 兼容,参数槽位宽度一致)。
# ============================================================
def _vpi_resolve_param_annot_ty(self, pool: memhub.MemBuddy | t.CPtr,
seg: str) -> llvmlite.LLVMType | t.CPtr:
"""解析单个 vpi 参数注解段为 LLVMType,失败返回 None"""
if pool is None or seg is None:
return None
i32_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool)
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, llvmlite.Int8(pool))
return VpiReader._resolve_vpi_ret_annot_str(pool, seg, i32_ty, i8_ptr_ty)
# ============================================================
# _special_method_ret_ty - 编译器内置特殊方法的固定返回类型
#