diff --git a/App/lib/core/Phase2.vp b/App/lib/core/Phase2.vp index 8b51a40..9da66af 100644 --- a/App/lib/core/Phase2.vp +++ b/App/lib/core/Phase2.vp @@ -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) diff --git a/App/lib/core/StubMerger.vp b/App/lib/core/StubMerger.vp index 65cd29c..dd7fa11 100644 --- a/App/lib/core/StubMerger.vp +++ b/App/lib/core/StubMerger.vp @@ -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