From d7ed91e3e5ebe63e84a850ebf5c45d74da3b48e5 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 25 Aug 2026 17:01:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(callgenbase):=20=5Fensure=5Fmethod=5Fdeclar?= =?UTF-8?q?e=20=E5=AE=9A=E4=B9=89=E4=BE=A7=E5=8F=82=E6=95=B0=E5=AE=BD?= =?UTF-8?q?=E5=BA=A6=E4=BC=98=E5=85=88=EF=BC=8C=E6=A0=B9=E6=B2=BB=E5=AD=97?= =?UTF-8?q?=E9=9D=A2=E9=87=8F=E5=AE=9E=E5=8F=82=E5=9B=BA=E5=8C=96=20i32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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*)。 --- App/lib/core/Gen/CallGenBase.vp | 105 +++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 2 deletions(-) diff --git a/App/lib/core/Gen/CallGenBase.vp b/App/lib/core/Gen/CallGenBase.vp index be007ca..694696a 100644 --- a/App/lib/core/Gen/CallGenBase.vp +++ b/App/lib/core/Gen/CallGenBase.vp @@ -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 - 编译器内置特殊方法的固定返回类型 #