fix(vpireader): declare 参数注解改字符串解析优先,修复 t.CSizeT→i32

SlotsBugTest 根因:跨模块 declare 参数类型经 AST resolve_annotation_type 解析,vpi tree 深层节点 kind() 损坏时得垃圾类型(t.CSizeT→i32),而定义侧是 i64——调用侧 trunc i64→i32 传参、callee 按 i64 读栈残留垃圾高位 → can=巨值读未写槽位(0xCDCD)崩溃(ASAN node=0xCDCD,rcx 垃圾高 32 位实锤)。

修复:_inject_function_declare/_inject_method_declare 参数解析与返回类型同源——优先用 vpi 文本注解缓存(_resolve_vpi_param_annot_seg 按段索引解析),AST 仅兜底。map_t_type('CSizeT')=i64 字符串路径正确,can declare 恢复 i64,调用侧不再 trunc。
This commit is contained in:
TermiNexus committed 2026-08-25 16:43:43 +08:00
1 parent 936aa14041
commit 4d14eeff67
1 file changed
+551 -12
+551 -12
View File
@@ -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 签名含 '= <default>'(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 签名含 '= <default>'(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)
# ============================================================