diff --git a/App/lib/Stub/StubGen/VpiReader.vp b/App/lib/Stub/StubGen/VpiReader.vp index dfbe4b6..2510a88 100644 --- a/App/lib/Stub/StubGen/VpiReader.vp +++ b/App/lib/Stub/StubGen/VpiReader.vp @@ -40,6 +40,29 @@ _g_vpi_ret_strs: bytes = None _g_vpi_ret_count: int = 0 _g_vpi_ret_cap: int = 0 +# ============================================================ +# 方法参数注解缓存(stdlib 持久,仿返回注解缓存) +# +# 背景:vpi tree 在"解析后立即"有效,但遍历处理到后续类的方法时, +# 深层 AST 节点(含函数参数列表)可能被覆盖损坏 → _inject_method_declare +# /_inject_function_declare 读 args 列表被截断(带默认值的尾部参数丢失, +# 同一 vpi 不同模块注入时忽对忽错)→ declare 参数数不足 → 调用点按 +# declare 缺参(fill 无法超 declare 补)→ 被调函数按 x64 ABI 读残留 +# 寄存器/栈槽垃圾(如"跨模块 CDefine 查找递归过深 depth=160":depth +# 与 imported_modules 均为垃圾,导致 imported_modules 指针失控递归)。 +# 与返回注解同源:从 vpi_buf 原始文本(ParseVpiIntoModule 末尾才释放, +# 始终有效)按行提取每个函数/方法的参数注解串缓存,注入 declare 时 +# 若 tree 参数数不足,用缓存补全,保证 declare 与 define 参数数一致。 +# ============================================================ +VPI_PARAM_MAX: t.CDefine = 4096 +VPI_PARAM_KEY_LEN: t.CDefine = 224 +VPI_PARAM_LEN: t.CDefine = 1024 + +_g_vpi_param_keys: bytes = None +_g_vpi_param_strs: bytes = None +_g_vpi_param_count: int = 0 +_g_vpi_param_cap: int = 0 + # ============================================================ # _cache_vpi_return_annots_text - 从 vpi_buf 文本直接提取方法返回注解 @@ -188,6 +211,367 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int: return 1 +# ============================================================ +# _cache_vpi_param_annots_text - 从 vpi_buf 文本提取函数/方法参数注解串 +# +# 与 _cache_vpi_return_annots_text 同构:vpi tree 深层节点在遍历中可能被 +# 覆盖损坏(参数列表截断),故从原始文本按行提取每个函数/方法的参数注解, +# 以 '@' 分隔存储(含首参 self → 空串占位,保证"参数位置 == 段索引"; +# 注解文法不含 '@',安全作分隔符)。 +# 存储格式:key="sha1.ClassName.method"(类内)/"sha1.method"(模块级), +# val=各参数注解串 '@' 分隔(无注解的参数为空串,如 self)。 +# 调用方:ParseVpiIntoModule(tree 解析后立即调用,文本始终有效)。 +# ============================================================ +def _cache_vpi_param_annots_text(vpi_buf: bytes, src_sha1: str) -> int: + """从 vpi_buf 文本提取函数/方法参数注解字符串(stdlib 持久)""" + global _g_vpi_param_keys, _g_vpi_param_strs, _g_vpi_param_count, _g_vpi_param_cap + if vpi_buf is None or src_sha1 is None: + return 0 + if _g_vpi_param_keys is None: + _g_vpi_param_keys = stdlib.malloc(VPI_PARAM_MAX * VPI_PARAM_KEY_LEN) + _g_vpi_param_strs = stdlib.malloc(VPI_PARAM_MAX * VPI_PARAM_LEN) + if _g_vpi_param_keys is None or _g_vpi_param_strs is None: + return 0 + string.memset(_g_vpi_param_keys, 0, VPI_PARAM_MAX * VPI_PARAM_KEY_LEN) + string.memset(_g_vpi_param_strs, 0, VPI_PARAM_MAX * VPI_PARAM_LEN) + _g_vpi_param_cap = VPI_PARAM_MAX + sha1_len: t.CSizeT = string.strlen(src_sha1) + buf_len: t.CSizeT = string.strlen(vpi_buf) + cur_cls: bytes = None + pos: t.CSizeT = 0 + while pos < buf_len: + ls: t.CSizeT = pos + le: t.CSizeT = pos + while le < buf_len and vpi_buf[le] != '\n': + le += 1 + line_len: t.CSizeT = le - ls + i: t.CSizeT = ls + while i < le and vpi_buf[i] == ' ': + i += 1 + if i < le: + if line_len >= 6 and string.strncmp(vpi_buf + i, "class ", 6) == 0: + cstart: t.CSizeT = i + 6 + cend: t.CSizeT = cstart + while cend < le and vpi_buf[cend] != ':' and vpi_buf[cend] != ' ' and vpi_buf[cend] != '(': + cend += 1 + if cend > cstart: + if cur_cls is not None: + stdlib.free(cur_cls) + cur_cls = stdlib.malloc(cend - cstart + 1) + if cur_cls is not None: + string.strncpy(cur_cls, vpi_buf + cstart, cend - cstart) + cur_cls[cend - cstart] = '\0' + elif line_len >= 4 and string.strncmp(vpi_buf + i, "def ", 4) == 0: + # 模块级函数(行首无缩进):清空类名上下文 + if i == ls and cur_cls is not None: + stdlib.free(cur_cls) + cur_cls = None + dstart: t.CSizeT = i + 4 + dend: t.CSizeT = dstart + while dend < le and vpi_buf[dend] != '(': + dend += 1 + if dend >= le: + pos = le + 1 + continue + # 找匹配的 ')'(深度感知:注解可含 t.CArray[...] / tuple[...] 括号) + p: t.CSizeT = dend + 1 + pdepth: int = 0 + pend: t.CSizeT = dend + while p < le: + ch_p: t.CChar = vpi_buf[p] + if ch_p == '(' or ch_p == '[' or ch_p == '{': + pdepth += 1 + elif ch_p == ')' or ch_p == ']' or ch_p == '}': + pdepth -= 1 + if pdepth < 0: + pend = p + break + p += 1 + # 逐段解析参数(顶层逗号分隔;跳过 *args/**kwargs) + plist: bytes = stdlib.malloc(VPI_PARAM_LEN) + if plist is None: + pos = le + 1 + continue + plist[0] = '\0' + plist_pos: t.CSizeT = 0 + seg_written: int = 0 + seg_cur: t.CSizeT = dend + 1 + while seg_cur < pend: + while seg_cur < pend and (vpi_buf[seg_cur] == ' ' or vpi_buf[seg_cur] == ','): + seg_cur += 1 + if seg_cur >= pend: + break + s_start: t.CSizeT = seg_cur + sd: int = 0 + while seg_cur < pend: + ch_s: t.CChar = vpi_buf[seg_cur] + if ch_s == '(' or ch_s == '[' or ch_s == '{': + sd += 1 + elif ch_s == ')' or ch_s == ']' or ch_s == '}': + sd -= 1 + elif ch_s == ',' and sd == 0: + break + seg_cur += 1 + s_end: t.CSizeT = seg_cur + # 跳过 * / **(vararg/kwarg 不入 args 列表) + st: t.CSizeT = s_start + while st < s_end and vpi_buf[st] == ' ': + st += 1 + if st < s_end and vpi_buf[st] == '*': + if seg_cur < pend and vpi_buf[seg_cur] == ',': + seg_cur += 1 + continue + # 截断默认值(顶层 '=',如 ": str = None") + e_pos: t.CSizeT = st + ed: int = 0 + while e_pos < s_end: + ch_e: t.CChar = vpi_buf[e_pos] + if ch_e == '(' or ch_e == '[' or ch_e == '{': + ed += 1 + elif ch_e == ')' or ch_e == ']' or ch_e == '}': + ed -= 1 + elif ch_e == '=' and ed == 0: + break + e_pos += 1 + s_end = e_pos + # 提取注解:顶层 ':' 之后(无 ':' → 空串,如 self) + c_pos: t.CSizeT = st + cd2: int = 0 + annot_start: t.CSizeT = s_end + while c_pos < s_end: + ch_c: t.CChar = vpi_buf[c_pos] + if ch_c == '(' or ch_c == '[' or ch_c == '{': + cd2 += 1 + elif ch_c == ')' or ch_c == ']' or ch_c == '}': + cd2 -= 1 + elif ch_c == ':' and cd2 == 0: + annot_start = c_pos + 1 + break + c_pos += 1 + a_start: t.CSizeT = annot_start + while a_start < s_end and vpi_buf[a_start] == ' ': + a_start += 1 + a_end: t.CSizeT = s_end + while a_end > a_start and vpi_buf[a_end - 1] == ' ': + a_end -= 1 + if plist_pos + (a_end - a_start) + 1 < VPI_PARAM_LEN: + if seg_written > 0: + plist[plist_pos] = '@' + plist_pos += 1 + ai2: t.CSizeT + for ai2 in range(a_end - a_start): + plist[plist_pos] = vpi_buf[a_start + ai2] + plist_pos += 1 + plist[plist_pos] = '\0' + seg_written += 1 + if seg_cur < pend and vpi_buf[seg_cur] == ',': + seg_cur += 1 + # 存入缓存(key = sha1.class.method / sha1.method,去重) + if plist_pos > 0 and plist[0] != '\0': + mname_len: t.CSizeT = dend - dstart + cls_len2: t.CSizeT = 0 + if cur_cls is not None: + cls_len2 = string.strlen(cur_cls) + key_len: t.CSizeT = sha1_len + 1 + cls_len2 + 1 + mname_len + if key_len < VPI_PARAM_KEY_LEN: + mname_buf: bytes = stdlib.malloc(mname_len + 1) + if mname_buf is not None: + string.strncpy(mname_buf, vpi_buf + dstart, mname_len) + mname_buf[mname_len] = '\0' + key_buf: bytes = stdlib.malloc(key_len + 1) + if key_buf is not None: + if cur_cls is not None: + viperlib.snprintf(key_buf, key_len + 1, "%s.%s.%s", src_sha1, cur_cls, mname_buf) + else: + viperlib.snprintf(key_buf, key_len + 1, "%s.%s", src_sha1, mname_buf) + dup_key: int = 0 + dki: int + for dki in range(_g_vpi_param_count): + if string.strcmp(_g_vpi_param_keys + dki * VPI_PARAM_KEY_LEN, key_buf) == 0: + dup_key = 1 + break + grow_ok: int = 1 + if dup_key == 0 and _g_vpi_param_count >= _g_vpi_param_cap: + grow_ok = _grow_vpi_param_cache() + if dup_key == 0 and grow_ok != 0: + string.strcpy(_g_vpi_param_keys + _g_vpi_param_count * VPI_PARAM_KEY_LEN, key_buf) + string.strncpy(_g_vpi_param_strs + _g_vpi_param_count * VPI_PARAM_LEN, + plist, plist_pos) + (_g_vpi_param_strs + _g_vpi_param_count * VPI_PARAM_LEN)[plist_pos] = '\0' + _g_vpi_param_count += 1 + stdlib.free(key_buf) + stdlib.free(mname_buf) + stdlib.free(plist) + pos = le + 1 + if cur_cls is not None: + stdlib.free(cur_cls) + return 1 + + +# ============================================================ +# _grow_vpi_param_cache - 扩容参数注解缓存(容量翻倍) +# ============================================================ +def _grow_vpi_param_cache() -> int: + """扩容参数注解缓存(容量翻倍),返回 1=成功 / 0=失败""" + global _g_vpi_param_keys, _g_vpi_param_strs, _g_vpi_param_cap + if _g_vpi_param_keys is None or _g_vpi_param_cap <= 0: + return 0 + new_cap: int = _g_vpi_param_cap * 2 + if new_cap > 1048576: + return 0 + new_keys: bytes = stdlib.realloc(_g_vpi_param_keys, new_cap * VPI_PARAM_KEY_LEN) + if new_keys is None: + return 0 + new_strs: bytes = stdlib.realloc(_g_vpi_param_strs, new_cap * VPI_PARAM_LEN) + if new_strs is None: + return 0 + _g_vpi_param_keys = new_keys + _g_vpi_param_strs = new_strs + _g_vpi_param_cap = new_cap + return 1 + + +# ============================================================ +# _lookup_vpi_param_annots - 查参数注解缓存 +# ============================================================ +def _lookup_vpi_param_annots(src_sha1: str, cls_name: str, method_name: str) -> str: + """查参数注解缓存;返回注解串('@' 分隔,含首参空串占位)或 None""" + if src_sha1 is None or method_name is None or _g_vpi_param_keys is None: + return None + sha1_len: t.CSizeT = string.strlen(src_sha1) + mlen: t.CSizeT = string.strlen(method_name) + clen: t.CSizeT = 0 + if cls_name is not None: + clen = string.strlen(cls_name) + key_len: t.CSizeT = sha1_len + clen + mlen + 2 + if key_len >= VPI_PARAM_KEY_LEN: + return None + key_buf: bytes = stdlib.malloc(key_len + 1) + if key_buf is None: + return None + if cls_name is not None: + viperlib.snprintf(key_buf, key_len + 1, "%s.%s.%s", src_sha1, cls_name, method_name) + else: + viperlib.snprintf(key_buf, key_len + 1, "%s.%s", src_sha1, method_name) + r: str = None + ki: int + for ki in range(_g_vpi_param_count): + if string.strcmp(_g_vpi_param_keys + ki * VPI_PARAM_KEY_LEN, key_buf) == 0: + r = _g_vpi_param_strs + ki * VPI_PARAM_LEN + break + stdlib.free(key_buf) + return r + + +# ============================================================ +# _repair_declare_params_from_text - 用文本缓存补全 declare 参数 +# +# vpi tree 参数列表被覆盖损坏时(param_count 偏小,尾部默认参数丢失), +# 用 _cache_vpi_param_annots_text 的文本缓存补全 declare:参数位置 p +# 对应缓存段 p(含首参空串占位)。补全后 declare 参数数与 define 一致, +# 调用点默认参数填充才能生效,避免被调函数读栈垃圾。 +# ============================================================ +def _repair_declare_params_from_text(pool: memhub.MemBuddy | t.CPtr, + func: llvmlite.Function | t.CPtr, + src_sha1: str, + cls_name: str, + method_name: str, + param_count: int, + i32_ty: llvmlite.LLVMType | t.CPtr, + i8_ptr_ty: llvmlite.LLVMType | t.CPtr) -> int: + """用 vpi 文本缓存的参数注解补全 declare 参数;返回补全后参数数""" + if func is None or src_sha1 is None or method_name is None: + return param_count + prm_annots: str = _lookup_vpi_param_annots(src_sha1, cls_name, method_name) + if prm_annots is None or prm_annots[0] == '\0': + return param_count + # 统计文本参数数('@' 分隔;段数 = 分隔符数 + 1,含首参) + pa_count: int = 1 + pi2: t.CSizeT = 0 + while prm_annots[pi2] != '\0': + if prm_annots[pi2] == '@': + pa_count += 1 + pi2 += 1 + if pa_count <= param_count: + return param_count + # 补全缺失参数:段索引 >= param_count 的段即缺失参数位置 + seg_idx: int = 0 + seg_start2: t.CSizeT = 0 + cur2: t.CSizeT = 0 + while prm_annots[cur2] != '\0': + while prm_annots[cur2] != '\0' and prm_annots[cur2] != '@': + cur2 += 1 + seg_len2: t.CSizeT = cur2 - seg_start2 + if prm_annots[cur2] == '@': + cur2 += 1 + if seg_idx >= param_count: + param_ty_r: llvmlite.LLVMType | t.CPtr = i8_ptr_ty + if seg_len2 > 0: + seg_buf_r: bytes = stdlib.malloc(seg_len2 + 1) + if seg_buf_r is not None: + string.strncpy(seg_buf_r, prm_annots + seg_start2, seg_len2) + seg_buf_r[seg_len2] = '\0' + rt: llvmlite.LLVMType | t.CPtr = _resolve_vpi_ret_annot_str( + pool, seg_buf_r, i32_ty, i8_ptr_ty) + if rt is not None: + param_ty_r = rt + stdlib.free(seg_buf_r) + llvmlite.add_param(pool, func, param_ty_r, "arg") + seg_start2 = cur2 + seg_idx += 1 + return pa_count + + +# ============================================================ +# _resolve_vpi_param_annot_seg - 按段索引解析 vpi 参数注解为 LLVMType +# +# 返回类型已用字符串注解缓存解析(绕开 vpi tree 深层节点 kind() 损坏,见 +# _inject_method_declare L1673-1675 注释);参数此前仍走 AST +# resolve_annotation_type,同样会得垃圾类型(如 t.CSizeT → i32),跨模块 +# declare 参数类型错 → 调用侧 trunc i64→i32 传参、callee 按 i64 读栈残留 +# 垃圾高位 → can=巨值读未写槽位崩溃(SlotsBugTest 根因)。 +# 此处与返回类型同源:从 @-joined 注解串取第 seg_idx 段(0=self 空段), +# 用 _resolve_vpi_ret_annot_str 字符串解析。空段/未命中返回 None(走 AST 兜底)。 +# ============================================================ +def _resolve_vpi_param_annot_seg(pool: memhub.MemBuddy | t.CPtr, + src_sha1: str, + cls_name: str, + method_name: str, + seg_idx: int, + i32_ty: llvmlite.LLVMType | t.CPtr, + i8_ptr_ty: llvmlite.LLVMType | t.CPtr) -> llvmlite.LLVMType | t.CPtr: + """按段索引解析 vpi 参数注解段为 LLVMType,空段/失败返回 None""" + if pool is None or src_sha1 is None or method_name is None: + return None + prm_annots_p: str = _lookup_vpi_param_annots(src_sha1, cls_name, method_name) + if prm_annots_p is None or prm_annots_p[0] == '\0': + return None + cur_p: t.CSizeT = 0 + cur_seg_p: int = 0 + while cur_seg_p < seg_idx and prm_annots_p[cur_p] != '\0': + while prm_annots_p[cur_p] != '\0' and prm_annots_p[cur_p] != '@': + cur_p += 1 + if prm_annots_p[cur_p] == '@': + cur_p += 1 + cur_seg_p += 1 + seg_start_p: t.CSizeT = cur_p + seg_len_p: t.CSizeT = 0 + while prm_annots_p[seg_start_p + seg_len_p] != '\0' \ + and prm_annots_p[seg_start_p + seg_len_p] != '@': + seg_len_p += 1 + if seg_len_p == 0: + return None + seg_buf_p: bytes = stdlib.malloc(seg_len_p + 1) + if seg_buf_p is None: + return None + string.strncpy(seg_buf_p, prm_annots_p + seg_start_p, seg_len_p) + seg_buf_p[seg_len_p] = '\0' + seg_ty_p: llvmlite.LLVMType | t.CPtr = _resolve_vpi_ret_annot_str( + pool, seg_buf_p, i32_ty, i8_ptr_ty) + stdlib.free(seg_buf_p) + return seg_ty_p + + # ============================================================ # _grow_vpi_ret_cache - 扩容方法返回注解缓存(容量翻倍) # @@ -277,6 +661,66 @@ def _ensure_vpi_ret_annot_loaded(src_sha1: str, temp_dir: str) -> int: return 1 +# ============================================================ +# _ensure_vpi_param_annot_loaded - 按需加载模块的 vpi stub 参数注解缓存 +# +# 与 _ensure_vpi_ret_annot_loaded 同构:_cache_vpi_param_annots_text 仅在 +# stub 注入(LoadDepVpiIntoModule,即"当前模块 import 了该模块")时填充。 +# 但跨模块方法调用可能绕过 stub 注入——如 ExprGenAttr 经 Generator 属性 +# (self.Gen.ExprGenCdef.method)调用依赖模块方法,依赖收集不加载其 vpi +# → 函数表无 ParamCount/默认参数 → 调用点缺参 → 被调函数读栈残留 +# (IterTest 跨模块 CDefine 查找 depth=160 假递归根因)。 +# 此函数在参数缓存未命中时直接读 vpi 文件填充,幂等跳过。 +# ============================================================ +def _ensure_vpi_param_annot_loaded(src_sha1: str, temp_dir: str) -> int: + """按需加载模块 vpi stub 的参数注解缓存,返回 1=已就绪 / 0=失败""" + if src_sha1 is None or temp_dir is None: + return 0 + sha1_len: t.CSizeT = string.strlen(src_sha1) + if sha1_len < 2: + return 0 + # 该模块已有参数缓存条目 → 跳过(幂等) + if _g_vpi_param_keys is not None and _g_vpi_param_count > 0: + pre_i: int + for pre_i in range(_g_vpi_param_count): + pre_key: bytes = _g_vpi_param_keys + pre_i * VPI_PARAM_KEY_LEN + if string.strncmp(pre_key, src_sha1, sha1_len) == 0 and pre_key[sha1_len] == '.': + return 1 + # 构造路径 {temp_dir}/{sha1[0:2]}/{sha1}.vpi(逐字符拼接,避免 %c 格式化问题) + td_len: t.CSizeT = string.strlen(temp_dir) + path_len: t.CSizeT = td_len + sha1_len + 12 + vpi_path: bytes = stdlib.malloc(path_len) + if vpi_path is None: + return 0 + string.strcpy(vpi_path, temp_dir) + vpi_path[td_len] = '/' + vpi_path[td_len + 1] = src_sha1[0] + vpi_path[td_len + 2] = src_sha1[1] + vpi_path[td_len + 3] = '/' + string.strcpy(vpi_path + td_len + 4, src_sha1) + string.strcpy(vpi_path + td_len + 4 + sha1_len, ".vpi") + f: fileio.File | t.CPtr = fileio.File(vpi_path, fileio.MODE.R) + if f.closed: + stdlib.free(vpi_path) + return 0 + vpi_buf: bytes = stdlib.malloc(VPI_READ_BUF_SIZE) + if vpi_buf is None: + f.close() + stdlib.free(vpi_path) + return 0 + bytes_read: LONG = f.read_all(vpi_buf, VPI_READ_BUF_SIZE) + f.close() + if bytes_read > 0: + if bytes_read < VPI_READ_BUF_SIZE: + vpi_buf[bytes_read] = 0 + else: + vpi_buf[VPI_READ_BUF_SIZE - 1] = 0 + _cache_vpi_param_annots_text(vpi_buf, src_sha1) + stdlib.free(vpi_buf) + stdlib.free(vpi_path) + return 1 + + # ============================================================ # _subst_template_arg - 替换注解字符串中独立的模板参数 token # @@ -811,6 +1255,9 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy | t.CPtr, # (不依赖 AST tree:tree 深层节点在后续遍历处理时可能被覆盖损坏, # 但 vpi_buf 原始文本在 ParseVpiIntoModule 末尾才释放,始终有效) _cache_vpi_return_annots_text(vpi_buf, src_sha1) + # 同源:提取函数/方法参数注解字符串缓存(tree 参数列表同样可能被 + # 覆盖截断,_inject_*_declare 用此缓存补全 declare 参数数) + _cache_vpi_param_annots_text(vpi_buf, src_sha1) # 3. 遍历 AST 顶层节点 ch: list[ast.AST | t.CPtr] | t.CPtr = tree.children @@ -818,7 +1265,7 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy | t.CPtr, if src_sha1 is not None: stdlib.free(src_sha1) return 1 - n: t.CSizeT = ch.__len__() + n: t.CSizeT = len(ch) # 收集 stub 内被读模块自己的 import 语句,构建独立的注解解析上下文。 # .vpi stub 顶部含被读模块的 import(Converter 已写入,如 string.vp 的 @@ -835,7 +1282,7 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy | t.CPtr, if sk == ast.ASTKind.Import: simp: ast.Import | t.CPtr = (ast.Import | t.CPtr)(schild) if simp.names is not None: - sn_n: t.CSizeT = simp.names.__len__() + sn_n: t.CSizeT = len(simp.names) sni: t.CSizeT for sni in range(sn_n): sal: ast.Alias | t.CPtr = (ast.Alias | t.CPtr)(simp.names.get(sni)) @@ -852,7 +1299,7 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy | t.CPtr, stub_imported_modules = _stub_add_imported( pool, stub_imported_modules, sifr_mod) if sifr.names is not None: - sifr_nn: t.CSizeT = sifr.names.__len__() + sifr_nn: t.CSizeT = len(sifr.names) sifri: t.CSizeT for sifri in range(sifr_nn): sial: ast.Alias | t.CPtr = (ast.Alias | t.CPtr)(sifr.names.get(sifri)) @@ -959,11 +1406,11 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy | t.CPtr, # 具体特化的方法 define 已在 Phase1 生成,跨模块调用按特化类名 # (ndarray[t.CDouble].__add__)查找命中,无需 stub 模板 declare。 tp_stub: list[str] | t.CPtr = class_cd.type_params - if tp_stub is not None and tp_stub.__len__() > 0: + if tp_stub is not None and len(tp_stub) > 0: continue cls_children: list[ast.AST | t.CPtr] | t.CPtr = class_cd.children if cls_children is not None: - cn2: t.CSizeT = cls_children.__len__() + cn2: t.CSizeT = len(cls_children) for ci2 in range(cn2): m_child: ast.AST | t.CPtr = cls_children.get(ci2) if m_child is None: @@ -1142,11 +1589,13 @@ def _inject_function_declare(pool: memhub.MemBuddy | t.CPtr, # 提取参数并添加到 declare param_count: int = 0 + fd_defaults: t.CVoid | t.CPtr = None + fd_default_count: int = 0 if fd.args is not None: args_node: ast.Arguments | t.CPtr = (ast.Arguments | t.CPtr)(fd.args) if args_node.args is not None: args_list: list[ast.AST | t.CPtr] | t.CPtr = args_node.args - param_count = args_list.__len__() + param_count = len(args_list) # 参数数上限哨兵(fail-fast):vpi tree 深层节点在遍历处理过程中 # 可能被覆盖损坏(见本文件顶部注释),fd.args 悬空时 __len__() # 返回垃圾大数 → for 循环 add_param 海量次 → Params 链超长, @@ -1166,7 +1615,22 @@ def _inject_function_declare(pool: memhub.MemBuddy | t.CPtr, pname = "arg" # 解析参数类型 param_ty: llvmlite.LLVMType | t.CPtr = i8_ptr_ty - if ag.annotation is not None: + # 优先用 vpi 文本注解缓存解析参数类型(与返回类型同源——vpi + # tree 深层节点 kind() 可能损坏,AST resolve 得垃圾类型如 + # t.CSizeT→i32,跨模块 declare 参数宽错 → 调用侧 trunc 传参、 + # callee 按宽类型读栈残留垃圾高位 → 参数巨值崩溃(SlotsBugTest + # 根因)。段索引 = pi(模块级函数无 self,0=首参)。 + pseg_ty: llvmlite.LLVMType | t.CPtr = _resolve_vpi_param_annot_seg( + pool, src_sha1, None, fd.name, pi, i32_ty, i8_ptr_ty) + if pseg_ty is not None: + match pseg_ty: + case llvmlite.LLVMType.Struct(_, _, _): + param_ty = llvmlite.Int64(pool) + case llvmlite.LLVMType.Array(aelem_ty, _): + param_ty = llvmlite.Ptr(pool, aelem_ty) + case _: + param_ty = pseg_ty + elif ag.annotation is not None: resolved_ty: llvmlite.LLVMType | t.CPtr = GenApi.resolve_annotation_type( pool, ag.annotation, imported_modules, from_imports, trans) if resolved_ty is not None: @@ -1183,18 +1647,46 @@ def _inject_function_declare(pool: memhub.MemBuddy | t.CPtr, case _: param_ty = resolved_ty llvmlite.add_param(pool, func, param_ty, pname) + # 文本缓存校验/修复:vpi tree args 可能被覆盖损坏(尾部默认参数丢失) + # → declare 参数数不足 → 调用点缺参 → 被调函数读栈垃圾。用 vpi 文本 + # 缓存的参数注解补全 declare 参数(与 define 参数数一致)。 + param_count = _repair_declare_params_from_text( + pool, func, src_sha1, None, fd.name, param_count, i32_ty, i8_ptr_ty) # *args 变长参数:标记 IsVarArg=1,使调用侧走 C ABI 参数提升 # (i8/i16 → i32,float → double),避免 %d/%x 等格式因参数按 i64 # 传递而读错(第二个 %x 读到第一个 i64 槽的高 4 字节)。 if args_node.vararg is not None: func.IsVarArg = 1 + # 默认参数:vpi 签名含 '= '(Converter 已输出),提取 defaults + # 列表注册默认参数表——跨模块缺参调用(如 resolve_annotation_type 的 + # depth=0)才能按表填充,否则被调函数读到栈垃圾(假报递归过深/崩溃)。 + if args_node.defaults is not None: + fd_dlist: list[ast.AST | t.CPtr] | t.CPtr = args_node.defaults + fd_default_count = len(fd_dlist) + if fd_default_count < 0 or fd_default_count > 128: + # vpi tree 深层节点(args.defaults)可能被覆盖损坏,len 返回 + # 垃圾大数 → _register_global_defaults 按垃圾大数 alloc + 数组 + # 越界写 perm_pool(覆盖当前模块 AST/表,SlotsBugTest 崩溃根因 + # 候选)。与 param_count 哨兵(L1493-1497)对齐:垃圾值放弃 + # 注册默认值(跨模块缺参调用走零值兜底),阻断越界写。 + stdio.printf( + "[VPIS] 默认参数数异常: 函数名=%s default_count=%d(tree 可能被覆盖,跳过默认值注册)\n", + fd.name, fd_default_count) + fd_defaults = None + fd_default_count = 0 + else: + fd_defaults = fd_dlist # 注册到函数表(用裸名 fd.name,不是 mangled_name) 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, - None, 0, param_count) == 0: + fd_defaults, fd_default_count, param_count) == 0: trans._func_count = cur_count + 1 + # 补全全局函数表 TypeStr(参数已全部添加,对齐 FuncGenDef L211 行为): + # vpi 注入路径若不补全,跨模块 vtable/调用解析 find_func_entry_global() + # 时 TypeStr 为空 → "vtable 方法找不到函数实现"(依赖模块晚于本模块编译时必现) + ExprCallTable.update_global_func_typestr(pool, fd.name, func) # ============================================================ @@ -1269,11 +1761,13 @@ def _inject_method_declare(pool: memhub.MemBuddy | t.CPtr, # 提取参数并添加到 declare param_count: int = 0 + md_defaults: t.CVoid | t.CPtr = None + md_default_count: int = 0 if m_fd.args is not None: args_node: ast.Arguments | t.CPtr = (ast.Arguments | t.CPtr)(m_fd.args) if args_node.args is not None: args_list: list[ast.AST | t.CPtr] | t.CPtr = args_node.args - param_count = args_list.__len__() + param_count = len(args_list) # 参数数上限哨兵(fail-fast):与 _inject_function_declare 同因 # (vpi tree 深层节点被覆盖损坏时 __len__() 返回垃圾大数)。 if param_count < 0 or param_count > 128: @@ -1301,7 +1795,22 @@ def _inject_method_declare(pool: memhub.MemBuddy | t.CPtr, llvmlite.add_param(pool, func, llvmlite.Ptr(pool, cls_ty_sd), pname) continue param_ty: llvmlite.LLVMType | t.CPtr = i8_ptr_ty - if ag.annotation is not None: + # 优先用 vpi 文本注解缓存解析参数类型(与返回类型同源——vpi + # tree 深层节点 kind() 可能损坏,AST resolve 得垃圾类型如 + # t.CSizeT→i32,declare 参数宽错 → 调用侧 trunc i64→i32 传参、 + # callee 按 i64 读栈残留垃圾高位 → can=巨值读未写槽位崩溃, + # SlotsBugTest 根因)。段索引 = pi(0=self 空段)。 + pseg_ty: llvmlite.LLVMType | t.CPtr = _resolve_vpi_param_annot_seg( + pool, src_sha1, cls_name, m_fd.name, pi, i32_ty, i8_ptr_ty) + if pseg_ty is not None: + match pseg_ty: + case llvmlite.LLVMType.Struct(_, _, _): + param_ty = llvmlite.Int64(pool) + case llvmlite.LLVMType.Array(aelem_ty, _): + param_ty = llvmlite.Ptr(pool, aelem_ty) + case _: + param_ty = pseg_ty + elif ag.annotation is not None: resolved_ty: llvmlite.LLVMType | t.CPtr = GenApi.resolve_annotation_type( pool, ag.annotation, imported_modules, from_imports, trans) if resolved_ty is not None: @@ -1319,6 +1828,13 @@ def _inject_method_declare(pool: memhub.MemBuddy | t.CPtr, param_ty = resolved_ty llvmlite.add_param(pool, func, param_ty, pname) + # 文本缓存校验/修复:vpi tree args 可能被覆盖损坏(尾部默认参数丢失) + # → declare 参数数不足 → 调用点缺参 → 被调函数读栈垃圾(跨模块 CDefine + # 查找 depth=160 假递归根因)。用 vpi 文本缓存的参数注解补全 declare + # 参数(与 define 参数数一致,调用点默认参数填充才能生效)。 + param_count = _repair_declare_params_from_text( + pool, func, src_sha1, cls_name, m_fd.name, param_count, i32_ty, i8_ptr_ty) + # OOP iter 协议:__next__ 方法 stub 声明带隐藏 __stop_iter_flag__(i1*), # 与 _translate_method 的 define 签名一致(对齐 TPC HandlesFunctions.py # L703-705)。for x in obj 循环会以 2 个实参调用 __next__(self, flag)。 @@ -1328,10 +1844,28 @@ def _inject_method_declare(pool: memhub.MemBuddy | t.CPtr, i1_ptr_ty_si: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i1_ty_si) if i1_ptr_ty_si is not None: llvmlite.add_param(pool, func, i1_ptr_ty_si, "__stop_iter_flag__") + # 默认参数:vpi 签名含 '= '(Converter 已输出),提取 defaults + # 列表注册默认参数表——跨模块缺参调用(如 resolve_annotation_type 的 + # depth=0)才能按表填充,否则被调函数读到栈垃圾(假报递归过深/崩溃)。 + if m_fd.args is not None: + md_args_node: ast.Arguments | t.CPtr = (ast.Arguments | t.CPtr)(m_fd.args) + if md_args_node.defaults is not None: + md_dlist: list[ast.AST | t.CPtr] | t.CPtr = md_args_node.defaults + md_default_count = len(md_dlist) + if md_default_count < 0 or md_default_count > 128: + # 同 _inject_function_declare:vpi tree 深层节点被覆盖时 + # defaults len 可能为垃圾大数 → 越界写 perm_pool。放弃注册。 + stdio.printf( + "[VPIS] 默认参数数异常: 方法名=%s.%s default_count=%d(tree 可能被覆盖,跳过默认值注册)\n", + cls_name, m_fd.name, md_default_count) + md_defaults = None + md_default_count = 0 + else: + md_defaults = md_dlist # 注册到函数表(用裸名 ClassName.method,与调用方 lookup_name 一致) # ParamCount 不含 self(方法首参 self/cls 不计入,与 _translate_method 的 - # md_param_count = args.__len__() - 1 保持一致)——默认参数填充按非 self + # md_param_count = len(args) - 1 保持一致)——默认参数填充按非 self # 参数计数,若含 self 会导致 mdf_param_count 虚高、填充逻辑错位/越界。 if funcs_ptr is not None: cur_count: int = trans._func_count @@ -1339,8 +1873,13 @@ 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, - None, 0, param_count_no_self) == 0: + md_defaults, md_default_count, param_count_no_self) == 0: trans._func_count = cur_count + 1 + # 补全全局函数表 TypeStr(参数已全部添加,对齐 ClassGenVTable L832 行为): + # vpi 注入的方法声明若不补全 TypeStr,跨模块继承 vtable 生成时 + # find_func_entry_global().TypeStr 为空 → "vtable 方法找不到函数实现" + # (astaux.vp 先于 ast.base.vp 编译时必现:AST.accept 仅有 vpi 注入条目) + ExprCallTable.update_global_func_typestr(pool, lookup_name, func) # ============================================================