fix(phaseb+): includes 按依赖拓扑序编译,修复跨模块 vtable 误杀

astaux.vp 继承 base.vp 的 AST 类时,vtable 槽位需 AST.accept 的
TypeStr 已注册到全局表(完整翻译产物)。Phase B+ 原先按 store 扫描
顺序编译,astaux 先于 base 编译 → 继承方法找不到实现被 fail-fast 误杀。
新增 StubMerger.BuildTopoSortedIndices(Kahn 逆拓扑,被依赖者先),
Phase B+ 循环按拓扑序编译可达 includes。
This commit is contained in:
TermiNexus committed 2026-08-13 03:50:59 +08:00
1 parent ad2b97a140
commit 7cc501cdef
2 files changed
+155 -9

No files matched your search

+22 -9
View File
@@ -728,15 +728,24 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr,
store_rel_arr_mi: bytes | t.CPtr = StubMerger.GetSha1StoreRelArrPtr()
store_incdir_arr_mi: bytes | t.CPtr = StubMerger.GetSha1StoreIncdirArrPtr()
for si_mi in range(store_count_mi):
# 拓扑排序可达 includes:被依赖模块先完整翻译。跨模块 vtable 生成
# 需要依赖模块的函数 TypeStr 已注册到全局表(如 astaux 继承 base 的 AST)
topo_idx: t.CSizeT | t.CPtr = (t.CSizeT | t.CPtr)(stdlib.malloc(8 * (store_count_mi + 1)))
topo_count: int = 0
if topo_idx is None:
VLogger.error("拓扑排序索引分配失败,终止编译", "PhaseB+")
topo_count = 0
else:
topo_count = StubMerger.BuildTopoSortedIndices(
store_count_mi, store_sha1_arr_mi, store_mod_arr_mi,
reachable_set, reachable_count, topo_idx)
for si_mi in range(topo_count):
# 当前条目(拓扑序索引,仅含可达模块)
ti: int = topo_idx[si_mi]
# 获取当前条目的 SHA1 和 rel_path
inc_sha1_mi: str = store_sha1_arr_mi + t.CSizeT(si_mi) * 17
inc_rel_mi: str = store_rel_arr_mi + t.CSizeT(si_mi) * StubMerger.MAX_REL_PATH_LEN
# 按需编译过滤:只处理 reachable_set 中的 includes
# 永不回退:reachable_count > 0 时严格按可达集合过滤
if reachable_count > 0:
if StubMerger._is_in_sha1_set(inc_sha1_mi, reachable_set, reachable_count) == 0:
continue
inc_sha1_mi: str = store_sha1_arr_mi + t.CSizeT(ti) * 17
inc_rel_mi: str = store_rel_arr_mi + t.CSizeT(ti) * StubMerger.MAX_REL_PATH_LEN
# 检查 .obj 是否已存在于 includes.binary(切片子目录)
ibd_len_mi: t.CSizeT = string.strlen(includes_binary_dir)
check_pat_mi: str = StubMerger._sliced_path(includes_binary_dir, ibd_len_mi, inc_sha1_mi, "obj")
@@ -757,7 +766,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr,
# .obj 不存在,需要编译
# 构造源文件路径: {所属 includes 根目录}/{rel_path}(多目录逐条取 IncDir,空串回退 includes_dir)
rel_path_len_mi: t.CSizeT = string.strlen(inc_rel_mi)
incdir_mi: str = store_incdir_arr_mi + t.CSizeT(si_mi) * StubMerger.MAX_INC_DIR_LEN
incdir_mi: str = store_incdir_arr_mi + t.CSizeT(ti) * StubMerger.MAX_INC_DIR_LEN
if incdir_mi is None or incdir_mi[0] == '\0':
incdir_mi = includes_dir
inc_dir_len_mi: t.CSizeT = string.strlen(incdir_mi)
@@ -940,6 +949,10 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr,
stdlib.free(src_fp_mi)
# 释放拓扑排序索引
if topo_idx is not None:
stdlib.free(topo_idx)
fb: str = VLogger.fmt_buf()
if fb is not None:
viperlib.snprintf(fb, 1024, "编译缺失 includes: %d 个", inc_compiled)
+133
View File
@@ -653,6 +653,139 @@ def LookupIncludesDeps(sha1: str) -> str:
return None
# ============================================================
# BuildTopoSortedIndices - 对可达 includes 逆拓扑排序(被依赖者先)
#
# 背景:跨模块 vtable 生成要求被依赖模块先完整翻译(如 astaux.vp 继承
# base.vp 的 AST,vtable 槽位需 AST.accept 的 TypeStr 已注册到全局表)。
# Phase B+ 原先按 store 扫描顺序编译,依赖顺序不保证 → fail-fast 暴露。
#
# 算法:Kahn 逆拓扑。边 m→d 表示 m 依赖 d(d 在 m 的 deps 中)。
# indegree[m] = m 在可达集合内的直接依赖数;
# 入度为 0(不依赖任何可达模块)者先输出,输出后对依赖它的模块
# 减入度,入度归 0 者入队。结果即"被依赖者先编译"。
# reachable_count <= 0 时对全部 store 排序(与既有全量编译行为一致)。
#
# Args:
# store_count / store_sha1_arr / store_mod_arr: store 元数据
# reachable_set / reachable_count: 可达集合(_BuildReachableSha1Set 输出)
# sorted_idx: 输出数组(store_count+1 个 8 字节槽),前 N 个为拓扑序 store 索引
#
# Returns:
# 拓扑序条数;有环时剩余条目按 store 顺序补齐(避免漏编译)
# ============================================================
def BuildTopoSortedIndices(store_count: int, store_sha1_arr: bytes,
store_mod_arr: bytes, reachable_set: bytes,
reachable_count: int,
sorted_idx: t.CSizeT | t.CPtr) -> int:
"""对可达 includes 逆拓扑排序(被依赖者先),store 索引写入 sorted_idx"""
if store_count <= 0 or store_sha1_arr is None or store_mod_arr is None or sorted_idx is None:
return 0
indegree: t.CSizeT | t.CPtr = (t.CSizeT | t.CPtr)(stdlib.malloc(8 * (store_count + 1)))
queue: t.CSizeT | t.CPtr = (t.CSizeT | t.CPtr)(stdlib.malloc(8 * (store_count + 1)))
rel_idx: t.CSizeT | t.CPtr = (t.CSizeT | t.CPtr)(stdlib.malloc(8 * (store_count + 1)))
if indegree is None or queue is None or rel_idx is None:
if indegree is not None:
stdlib.free(indegree)
if queue is not None:
stdlib.free(queue)
if rel_idx is not None:
stdlib.free(rel_idx)
return 0
# 收集可达 store 索引(reachable_count<=0 时全量,对齐既有全量编译行为)
rel_count: int = 0
i_t: int
if reachable_set is None or reachable_count <= 0:
for i_t in range(store_count):
rel_idx[rel_count] = i_t
rel_count += 1
else:
for i_t in range(store_count):
if _is_in_sha1_set(store_sha1_arr + t.CSizeT(i_t) * 17, reachable_set, reachable_count) != 0:
rel_idx[rel_count] = i_t
rel_count += 1
# 计算入度:m 在可达集合内的直接依赖数
i_r: int
for i_r in range(rel_count):
si: int = rel_idx[i_r]
deps_si: str = LookupIncludesDeps(store_sha1_arr + t.CSizeT(si) * 17)
if deps_si is None:
continue
dl_si: t.CSizeT = string.strlen(deps_si)
dpos: t.CSizeT = 0
while dpos < dl_si:
dstart: t.CSizeT = dpos
while dpos < dl_si and deps_si[dpos] != ' ':
dpos += 1
dlen: t.CSizeT = dpos - dstart
if dpos < dl_si:
dpos += 1
if dlen == 0:
continue
dname_buf: bytes = stdlib.malloc(dlen + 1)
if dname_buf is None:
continue
string.strncpy(dname_buf, deps_si + dstart, dlen)
dname_buf[dlen] = '\0'
dsha1: str = _FindSha1ByModName(store_sha1_arr, store_mod_arr, store_count, dname_buf)
stdlib.free(dname_buf)
if dsha1 is not None and _is_in_sha1_set(dsha1, reachable_set, reachable_count) != 0:
indegree[si] = indegree[si] + 1
# Kahn:入度为 0(不依赖任何可达模块)者先出 → 最先编译
q_head: int = 0
q_tail: int = 0
for i_r in range(rel_count):
si2: int = rel_idx[i_r]
if indegree[si2] == 0:
queue[q_tail] = si2
q_tail += 1
out_count: int = 0
while q_head < q_tail:
m: int = queue[q_head]
q_head += 1
sorted_idx[out_count] = m
out_count += 1
# 对依赖 m 的可达模块减入度
mod_m: str = store_mod_arr + t.CSizeT(m) * 64
mod_m_len: t.CSizeT = string.strlen(mod_m)
for i_r in range(rel_count):
si3: int = rel_idx[i_r]
if indegree[si3] == 0:
continue
deps_si3: str = LookupIncludesDeps(store_sha1_arr + t.CSizeT(si3) * 17)
if deps_si3 is None:
continue
dl_si3: t.CSizeT = string.strlen(deps_si3)
dpos3: t.CSizeT = 0
found_dep: int = 0
while dpos3 < dl_si3 and found_dep == 0:
dstart3: t.CSizeT = dpos3
while dpos3 < dl_si3 and deps_si3[dpos3] != ' ':
dpos3 += 1
dlen3: t.CSizeT = dpos3 - dstart3
if dpos3 < dl_si3:
dpos3 += 1
if dlen3 == 0:
continue
if dlen3 == mod_m_len and string.strncmp(deps_si3 + dstart3, mod_m, mod_m_len) == 0:
found_dep = 1
if found_dep == 1:
indegree[si3] = indegree[si3] - 1
if indegree[si3] == 0:
queue[q_tail] = si3
q_tail += 1
# 有环时剩余条目按 store 顺序补齐(避免漏编译)
for i_r in range(rel_count):
si4: int = rel_idx[i_r]
if indegree[si4] > 0:
sorted_idx[out_count] = si4
out_count += 1
stdlib.free(indegree)
stdlib.free(queue)
stdlib.free(rel_idx)
return out_count
def GetSha1StoreArrPtr() -> bytes | t.CPtr:
"""返回 SHA1 数组指针(每个条目 17 字节)"""
return _sha1_store_arr