From 178ea401b7eaffa60d92e90e62ec46a5576f74b6 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 1 Sep 2026 21:06:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(phase1):=20=E5=90=AB=20class=20=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E7=9A=84=20includes=20=E6=A8=A1=E5=9D=97=E5=BC=BA?= =?UTF-8?q?=E5=88=B6=E7=BF=BB=E8=AF=91=EF=BC=8C=E4=BF=AE=E5=A4=8D=E5=A2=9E?= =?UTF-8?q?=E9=87=8F=E7=BC=96=E8=AF=91=E8=B7=A8=E6=A8=A1=E5=9D=97=E7=B1=BB?= =?UTF-8?q?=E6=9E=84=E9=80=A0=20find=5Fstruct=5Fby=5Fmodule=20=E8=90=BD?= =?UTF-8?q?=E7=A9=BA=EF=BC=88fltk.Fl=5FWindow=20=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=20fail-fast=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App/lib/Phase/Phase1/Phase1Main.vp | 644 ++++++++++++++++++----------- 1 file changed, 410 insertions(+), 234 deletions(-) diff --git a/App/lib/Phase/Phase1/Phase1Main.vp b/App/lib/Phase/Phase1/Phase1Main.vp index 39edab5..a66b1c5 100644 --- a/App/lib/Phase/Phase1/Phase1Main.vp +++ b/App/lib/Phase/Phase1/Phase1Main.vp @@ -1,5 +1,6 @@ import t, c from stdint import * +import stdio import string import stdlib import memhub @@ -29,6 +30,299 @@ import lib.Phase.Phase1 as Phase1 import lib.core.Gen.GenApi as GenApi +# ============================================================ +# _InjectedCreatesCycle - 容器模块自动注入前的破环检查 +# +# 注入 _list/_dict 到当前模块的 _imported_modules 前,检查被注入模块自身的 +# 依赖是否已含当前模块。若命中(如 _list 显式 import memhub,而 memhub 因 +# `__provides__: list[str]` 注解要注入 _list)→ memhub↔_list 循环依赖 → +# Phase A 多遍收敛死锁(gfx.vp "无法完成翻译" 根因)。命中则跳过注入:去掉 +# 环边后,_list 已因当前模块被其依赖而处于闭包,list 类仍由 Phase1b 注册。 +# ============================================================ +def _InjectedCreatesCycle(entry: IncludesScanner.FileEntry | t.CPtr, + mod_name: str) -> int: + """注入 mod_name 到当前模块前检查是否成环,返回 1=成环需跳过注入""" + if entry is None or entry.RelPath is None or mod_name is None: + return 0 + inj_sha1: str = StubMergerStore.LookupSha1ByModName(mod_name) + if inj_sha1 is None: + return 0 + inj_deps: str = StubMergerStore.LookupIncludesDeps(inj_sha1) + if inj_deps is None: + return 0 + rp: str = entry.RelPath + # 当前模块名 = RelPath 基名(最后 '/' 或 '\\' 之后)去扩展名 + base: str = string.strrchr(rp, '/') + if base is None: + base = string.strrchr(rp, '\\') + if base is not None: + base = base + 1 + else: + base = rp + dot: str = string.strchr(base, '.') + nm_buf: bytes = stdlib.malloc(128) + if nm_buf is None: + return 0 + if dot is not None: + nm_len: t.CSizeT = t.CSizeT(t.CUInt64T(dot) - t.CUInt64T(base)) + if nm_len > 127: + nm_len = 127 + string.strncpy(nm_buf, base, nm_len) + nm_buf[nm_len] = '\0' + else: + string.strncpy(nm_buf, base, 127) + nm_buf[127] = '\0' + nm_len2: t.CSizeT = string.strlen(nm_buf) + d_len: t.CSizeT = string.strlen(inj_deps) + dp: t.CSizeT = 0 + while dp < d_len: + ts: t.CSizeT = dp + while dp < d_len and inj_deps[dp] != ' ': + dp += 1 + tl: t.CSizeT = dp - ts + if dp < d_len: + dp += 1 + if tl > 0 and tl == nm_len2 and string.strncmp(inj_deps + ts, nm_buf, tl) == 0: + stdlib.free(nm_buf) + return 1 + stdlib.free(nm_buf) + return 0 + + +def _DepsContainsName(deps: str, name: str) -> int: + """deps 字符串(空格分隔模块名)是否含 name token(词边界精确匹配)""" + if deps is None or name is None: + return 0 + nl: t.CSizeT = string.strlen(name) + dl: t.CSizeT = string.strlen(deps) + dp: t.CSizeT = 0 + while dp < dl: + ts: t.CSizeT = dp + while dp < dl and deps[dp] != ' ': + dp += 1 + tl: t.CSizeT = dp - ts + if dp < dl: + dp += 1 + if tl == nl and string.strncmp(deps + ts, name, nl) == 0: + return 1 + return 0 + + +def _DepsRemoveName(deps: str, name: str) -> int: + """从 deps 字符串移除 name token(原地重建,返回 1=有改动)""" + if deps is None or name is None: + return 0 + nl: t.CSizeT = string.strlen(name) + dl: t.CSizeT = string.strlen(deps) + buf: bytes = stdlib.malloc(StubMerger.MAX_DEPS_STR_LEN) + if buf is None: + return 0 + buf[0] = '\0' + bl: t.CSizeT = 0 + dp: t.CSizeT = 0 + while dp < dl: + ts: t.CSizeT = dp + while dp < dl and deps[dp] != ' ': + dp += 1 + tl: t.CSizeT = dp - ts + if dp < dl: + dp += 1 + if tl == 0: + continue + if tl == nl and string.strncmp(deps + ts, name, nl) == 0: + continue + if bl > 0: + buf[bl] = ' ' + bl += 1 + string.strncpy(buf + bl, deps + ts, tl) + bl += tl + buf[bl] = '\0' + string.strcpy(deps, buf) + stdlib.free(buf) + return 1 + + +def _RewriteDepsTxt(temp_dir: str, sha1: str, deps_str: str) -> None: + """重写 {temp_dir}/{sha1前2位}/{sha1}.deps.txt(内存破环后同步磁盘)""" + if temp_dir is None or sha1 is None or deps_str is None: + return + td_len: t.CSizeT = string.strlen(temp_dir) + dpath: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1, "deps.txt") + if dpath is None: + return + df: fileio.File | t.CPtr = fileio.File(dpath, fileio.MODE.W) + if not df.closed: + dl: t.CSizeT = string.strlen(deps_str) + if dl > 0: + df.write(deps_str, dl) + df.close() + stdlib.free(dpath) + + +def _BreakContainerCyclesInDeps(temp_dir: str) -> int: + """扫描后统一清理 _list/_dict 注入环边。 + + Phase1a-pre 全量扫描用 FindFirstFileA(文件系统序),注入 _list/_dict 时 + 被注入模块(_list/_dict)自身的 deps 未必已注册 → _InjectedCreatesCycle + 内联检查可能漏判(如 _list 显式 import memhub,而 memhub 的 list[str] + 注入 _list 未被拦截)→ memhub↔_list 2-环 → Phase A 永不收敛。此处扫描 + 结束后全量检查:凡某模块 M 的 deps 含 _list/_dict,且 _list/_dict 的 deps + 含 M → 从 M 的 deps 移除注入边(list 类仍由 Phase1b 经闭包注册)。 + 内存破环后同步重写 {temp_dir}/deps.txt,保证磁盘与内存一致(Phase2 依赖 + 图诊断回退读 deps.txt)。 + """ + if StubMerger._includes_deps_sha1 is None or StubMerger._includes_deps_str is None: + return 0 + cnt: t.CSizeT = StubMerger._includes_deps_count + # [CYCBREAK] 临时诊断:破环后处理是否执行、命中哪些模块(用后移除) + stdio.printf("[CYCBREAK] deps entries=%lu _list_sha1=%s\n", + t.CUInt64T(cnt), + StubMergerStore.LookupSha1ByModName("_list") if StubMergerStore.LookupSha1ByModName("_list") is not None else "none") + ci: t.CSizeT + for ci in range(cnt): + sha1_m: str = StubMerger._includes_deps_sha1 + t.CSizeT(ci) * 17 + deps_m: str = StubMerger._includes_deps_str + t.CSizeT(ci) * StubMerger.MAX_DEPS_STR_LEN + if sha1_m is None or deps_m is None: + continue + if string.strstr(deps_m, "_list") is None and string.strstr(deps_m, "_dict") is None: + continue + # 该模块名(RelPath 基名去扩展名) + rel_m: str = StubMergerStore.LookupSha1RelPath(sha1_m) + if rel_m is None: + continue + base_m: str = string.strrchr(rel_m, '/') + if base_m is None: + base_m = string.strrchr(rel_m, '\\') + if base_m is not None: + base_m = base_m + 1 + else: + base_m = rel_m + dot_m: str = string.strchr(base_m, '.') + if dot_m is not None: + nm_len_m: t.CSizeT = t.CSizeT(t.CUInt64T(dot_m) - t.CUInt64T(base_m)) + nm_m: bytes = stdlib.malloc(nm_len_m + 1) + if nm_m is None: + continue + string.strncpy(nm_m, base_m, nm_len_m) + nm_m[nm_len_m] = '\0' + else: + nm_m: bytes = stdlib.malloc(129) + if nm_m is None: + continue + string.strncpy(nm_m, base_m, 128) + nm_m[128] = '\0' + # _list ↔ M 环 + if string.strstr(deps_m, "_list") is not None: + lst_sha1: str = StubMergerStore.LookupSha1ByModName("_list") + if lst_sha1 is not None: + lst_deps: str = StubMergerStore.LookupIncludesDeps(lst_sha1) + if lst_deps is not None and _DepsContainsName(lst_deps, nm_m) != 0: + if _DepsRemoveName(deps_m, "_list") != 0: + # 内存破环后同步重写 deps.txt(诊断回退路径读文件) + _RewriteDepsTxt(temp_dir, sha1_m, deps_m) + # [CYCBREAK] 临时诊断:实际移除的环边(用后移除) + stdio.printf("[CYCBREAK] removed _list from %s deps\n", nm_m) + else: + stdio.printf("[CYCBREAK] %s has _list but _list deps no-match (lst_deps=%s)\n", + nm_m, lst_deps if lst_deps is not None else "none") + # _dict ↔ M 环 + if string.strstr(deps_m, "_dict") is not None: + dct_sha1: str = StubMergerStore.LookupSha1ByModName("_dict") + if dct_sha1 is not None: + dct_deps: str = StubMergerStore.LookupIncludesDeps(dct_sha1) + if dct_deps is not None and _DepsContainsName(dct_deps, nm_m) != 0: + if _DepsRemoveName(deps_m, "_dict") != 0: + _RewriteDepsTxt(temp_dir, sha1_m, deps_m) + stdlib.free(nm_m) + return 0 + + +# ============================================================ +# _BuildReachableResult - 从 Sha1Store 构建可达 FileEntry 列表 +# +# 依赖图按需扫描(StubMergerBuild._BuildReachableSha1Set)只登记 +# "可达 includes + App"到全局 Sha1Store;本函数把 incdir 非空条目 +# (includes)重建为 Phase1 1a/1b-pre/1b 需要的 FileEntry 列表。 +# App 条目(incdir 空,仅登记不做可达文件处理)不进入 result—— +# Phase B 已编译其 .obj,加入会导致 Phase B+ 重复编译 → 重复符号。 +# +# Args: +# mb: 内存池 +# includes_dir: includes 根目录(仅用于失败诊断,路径以 store 为准) +# +# Returns: +# ScanResult 指针(stdlib 分配,调用方负责释放);失败返回 None +# ============================================================ +def _BuildReachableResult(mb: memhub.MemBuddy | t.CPtr, + includes_dir: str) -> IncludesScanner.ScanResult | t.CPtr: + """从 Sha1Store(仅可达 includes + App)构建可达 FileEntry 列表""" + if mb is None: + return None + store_count: t.CSizeT = t.CSizeT(StubMerger._sha1_store_count) + if store_count <= 0 or StubMerger._sha1_store_arr is None: + return None + result: IncludesScanner.ScanResult | t.CPtr = IncludesScanner.create_scan_result(mb) + if result is None: + return None + entry_size: t.CSizeT = IncludesScanner.FileEntry.__sizeof__() + i: t.CSizeT + for i in range(store_count): + sidx: t.CSizeT = t.CSizeT(i) * 17 + if StubMerger._sha1_store_arr[sidx] == '\0': + continue + # 仅 includes 条目(incdir 非空);App 条目不进入 result + iidx: t.CSizeT = t.CSizeT(i) * StubMerger.MAX_INC_DIR_LEN + if StubMerger._sha1_store_incdir[iidx] == '\0': + continue + eaddr: t.CUInt64T = t.CUInt64T(result.Entries) + t.CUInt64T(result.Count) * entry_size + entry: IncludesScanner.FileEntry | t.CPtr = (IncludesScanner.FileEntry | t.CPtr)(t.CVoid(eaddr, t.CPtr)) + if entry is None: + continue + # SHA1 深拷贝(store 槽 17 字节) + sha1_buf: bytes = stdlib.malloc(17) + if sha1_buf is None: + continue + string.strcpy(sha1_buf, StubMerger._sha1_store_arr + sidx) + entry.Sha1 = sha1_buf + # RelPath / IncDir 深拷贝 + ridx: t.CSizeT = t.CSizeT(i) * StubMerger.MAX_REL_PATH_LEN + rel_len: t.CSizeT = string.strlen(StubMerger._sha1_store_rel + ridx) + inc_len: t.CSizeT = string.strlen(StubMerger._sha1_store_incdir + iidx) + rel_buf: bytes = stdlib.malloc(rel_len + 1) + inc_buf: bytes = stdlib.malloc(inc_len + 1) + if rel_buf is None or inc_buf is None: + if rel_buf is not None: + stdlib.free(rel_buf) + if inc_buf is not None: + stdlib.free(inc_buf) + stdlib.free(sha1_buf) + continue + string.strcpy(rel_buf, StubMerger._sha1_store_rel + ridx) + string.strcpy(inc_buf, StubMerger._sha1_store_incdir + iidx) + entry.RelPath = rel_buf + entry.IncDir = inc_buf + # 绝对路径 = inc_dir + '/' + rel_path(与 _TryResolveModuleFile 拼接一致) + abs_buf: bytes = stdlib.malloc(inc_len + 1 + rel_len + 1) + if abs_buf is None: + stdlib.free(sha1_buf) + stdlib.free(rel_buf) + stdlib.free(inc_buf) + continue + string.strcpy(abs_buf, inc_buf) + abs_buf[inc_len] = '/' + string.strcpy(abs_buf + inc_len + 1, rel_buf) + entry.Path = abs_buf + # 模块名(store 登记时保存的绝对模块名) + midx: t.CSizeT = t.CSizeT(i) * 64 + mod_len: t.CSizeT = string.strlen(StubMerger._sha1_store_mod + midx) + if mod_len > 0: + mod_buf: bytes = stdlib.malloc(mod_len + 1) + if mod_buf is not None: + string.strcpy(mod_buf, StubMerger._sha1_store_mod + midx) + entry.ModuleName = mod_buf + result.Count += 1 + return result + # ============================================================ # Phase1Main - Phase1 拆分(阶段 E):Phase1Main # @@ -71,212 +365,19 @@ def RunPhase1(mb: memhub.MemBuddy | t.CPtr, includes_dir: str, temp_dir: str, log.banner("Phase1: 扫描 includes(按需翻译)") - # 扫描 includes 目录(含每文件 SHA1 计算计时) - t_scan0: t.CUInt64T = w32.win32base.GetTickCount64() - result: IncludesScanner.ScanResult | t.CPtr = IncludesScanner.scan_includes(mb, includes_dir) - t_scan1: t.CUInt64T = w32.win32base.GetTickCount64() - VLogger.info(f"扫描 includes 总耗时: {t_scan1 - t_scan0} ms ({result.Count if result is not None else 0} 文件, 含 SHA1 计算)", "Phase1") - if result is None: - VLogger.error("扫描失败", "Phase1") - return 1 - - # 从扫描结果直接构建 SHA1 集合(不依赖 _sha1_map.txt 文件) - # Phase1 作为编译器自身,应从内存中的扫描结果获取 SHA1,避免外部文件依赖 - sha1_set: str = stdlib.malloc(StubMerger.MAX_INCLUDES_SHA1 * 17) - if sha1_set is None: - VLogger.error("sha1_set 分配失败", "Phase1") - return 1 - string.memset(sha1_set, 0, StubMerger.MAX_INCLUDES_SHA1 * 17) - set_count: int = 0 - p1_entry_size_init: t.CSizeT = IncludesScanner.FileEntry.__sizeof__() - for se_i in range(result.Count): - if set_count >= StubMerger.MAX_INCLUDES_SHA1: - break - se_addr: t.CUInt64T = t.CUInt64T(result.Entries) + se_i * p1_entry_size_init - se_ent: IncludesScanner.FileEntry | t.CPtr = (IncludesScanner.FileEntry | t.CPtr)(t.CVoid(se_addr, t.CPtr)) - if se_ent is None or se_ent.Sha1 is None: - continue - string.strcpy(sha1_set + set_count * 17, se_ent.Sha1) - set_count += 1 - # 先填充全局 SHA1 映射存储器(即使 set_count==0 也填充,确保 Phase2 能获取 includes 条目) - # 注意:PopulateSha1MapStore 会先 free 旧存储器再重新分配 - StubMergerStore.PopulateSha1MapStore(result) - # 写入 _sha1_map.txt(人类可读输出,程序内部不读取;机器分析使用 PopulateSha1MapStore 内存存储器) - StubMergerCollect.WriteIncludesSha1Map(mb, temp_dir, result, None, 0) - if set_count <= 0: - VLogger.warning("扫描结果无有效 SHA1,跳过 Phase1(项目可能无 includes 依赖)", "Phase1") - stdlib.free(sha1_set) - return 0 - # 模块 SHA1 映射已由 PopulateSha1MapStore 填充到 StubMerger 全局存储器, - # HandlesExprCall._lookup_module_sha1 直接查全局存储器,不再需要独立副本。 - - # 初始化 AST 表(只需一次) + # 初始化 AST 表(只需一次):必须在依赖图 BFS 扫描之前调用。 + # _init_tables 懒分配关键字/操作符表(全局 _kw_head 等),若首次触发 + # 发生在 BFS 的某个文件级 mark 之后,reset_to 会回收表节点 → 全局 + # 悬空 → 后续 tokenize 的 _kw_lookup strcmp 访问 0x4 崩溃(1.log ASan)。 ast._init_tables(mb) - # FileEntry 结构体大小(Phase 1a 和 1b 共用) - entry_size: t.CSizeT = IncludesScanner.FileEntry.__sizeof__() - - # ============================================================ - # Phase 1a-pre: 扫描所有 includes 文件的 import 依赖 - # - # 只处理 Import/ImportFrom 语句,跳过 ClassDef/FunctionDef 等, - # 不调用 resolve_annotation_type,避免 list[...] 等不支持的语法触发 crash。 - # 生成 .deps.txt 供依赖图按需翻译使用。 - # ============================================================ - p1a_registered: int = 0 - p1a_skipped: int = 0 - p1a_failed: int = 0 - # 1a-pre 阶段总耗时起点(GetTickCount64,ms;对齐 Phase2 计时插桩) - t_pre0: t.CUInt64T = w32.win32base.GetTickCount64() - - for i in range(result.Count): - # 文件级 mark:import 依赖收集完(deps 写盘 + PopulateIncludesDeps - # 内部 stdlib 深拷贝)后立即回收本文件 AST——98 includes 全量 parse - # 的 AST 若驻留到 mark1 → 1GB 主池耗尽 OOM 根因之一 - scan_mark_a: t.CSizeT = mb.mark() - entry_addr_a: t.CUInt64T = t.CUInt64T(result.Entries) + i * entry_size - entry_a: IncludesScanner.FileEntry | t.CPtr = (IncludesScanner.FileEntry | t.CPtr)(t.CVoid(entry_addr_a, t.CPtr)) - if entry_a is None: - p1a_failed += 1 - continue - - sha1_a: str = entry_a.Sha1 - if sha1_a is None: - p1a_failed += 1 - continue - - if StubMergerCollect._is_in_sha1_set(sha1_a, sha1_set, set_count) == 0: - p1a_skipped += 1 - continue - - # 读取文件内容 - file_path_a: str = entry_a.Path - f_a: fileio.File | t.CPtr = fileio.File(file_path_a, fileio.MODE.R) - if f_a.closed: - p1a_failed += 1 - continue - - src_buf_a: bytes = stdlib.malloc(Phase1.SRC_BUF_SIZE) - if src_buf_a is None: - f_a.close() - p1a_failed += 1 - continue - - bytes_read_a: LONG = f_a.read_all(src_buf_a, Phase1.SRC_BUF_SIZE) - f_a.close() - if bytes_read_a <= 0: - stdlib.free(src_buf_a) - p1a_failed += 1 - continue - if bytes_read_a < Phase1.SRC_BUF_SIZE: - src_buf_a[bytes_read_a] = 0 - else: - src_buf_a[Phase1.SRC_BUF_SIZE - 1] = 0 - - # 解析 AST - lx_a: ast.Lexer | t.CPtr = ast.new_lexer(mb) - if lx_a is None: - stdlib.free(src_buf_a) - p1a_failed += 1 - continue - ast._lexer_init(lx_a, src_buf_a, mb) - tokens_a: ast.Token | t.CPtr = ast.tokenize(lx_a) - tree_a: ast.AST | t.CPtr = ast.parse_tokens(mb, tokens_a) - if tree_a is None: - stdlib.free(src_buf_a) - p1a_failed += 1 - continue - - # 创建 Translator,设置 _declare_only=2(只扫描 import,跳过 ClassDef) - tr_a: HandlesTranslator.Translator | t.CPtr = HandlesTranslator.Translator() - if tr_a is None: - stdlib.free(src_buf_a) - p1a_failed += 1 - continue - tr_a.ModuleSha1 = sha1_a - tr_a._declare_only = 2 - tr_a.CurrentPackage = GenApi.compute_package_from_relpath(mb, entry_a.RelPath) - GenApi.set_current_file_type(file_path_a) - GenApi.set_current_file_imports(file_path_a) - GenApi.set_current_module_sha1(sha1_a) - GenApi.clear_cdefine_constants() - GenApi.clear_ctypedefs() - GenApi.reset_visible_structs(mb, 0) - ret_a: int = tr_a.translate(tree_a) - if ret_a != 0: - p1a_failed += 1 - else: - p1a_registered += 1 - - # 容器模板按需纳入依赖:includes 使用 list/dict 泛型/构造/字面量 - # (如 base.vp 的 list[AST | CPtr])时追加 _list/_dict 到 - # _imported_modules → 进 deps → _BuildReachableSha1Set 的可达集合 - # 自动包含(Phase1 与 Phase2 都受益),否则泛型模板未注册 → - # "泛型类 'list' 未找到模板"。对齐 App 层 _inject_auto_container_visibility。 - cl_box: t.CPtr = stdlib.malloc(4) - cd_box: t.CPtr = stdlib.malloc(4) - if cl_box is not None and cd_box is not None: - cl_box[0] = 0 - cd_box[0] = 0 - Phase1Scan._ScanTreeForContainers(tree_a, cl_box, cd_box) - if cl_box[0] != 0: - if tr_a._imported_modules is None or \ - GenApi.is_module_imported(tr_a._imported_modules, "_list") == 0: - tr_a._imported_modules = GenApi.add_imported_module( - mb, tr_a._imported_modules, "_list") - if cd_box[0] != 0: - if tr_a._imported_modules is None or \ - GenApi.is_module_imported(tr_a._imported_modules, "_dict") == 0: - tr_a._imported_modules = GenApi.add_imported_module( - mb, tr_a._imported_modules, "_dict") - if cl_box is not None: - stdlib.free(cl_box) - if cd_box is not None: - stdlib.free(cd_box) - - # 生成 .deps.txt(记录依赖模块名,供依赖图按需翻译使用) - # 同时填充内存依赖存储器(PopulateIncludesDeps),供 _BuildReachableSha1Set - # 直接从内存读取,不依赖 .deps.txt 过程文件 - td_len_a: t.CSizeT = string.strlen(temp_dir) - deps_path_a: str = StubMergerCollect._sliced_path(temp_dir, td_len_a, sha1_a, "deps.txt") - if deps_path_a is not None: - df_a: fileio.File | t.CPtr = fileio.File(deps_path_a, fileio.MODE.W) - if not df_a.closed: - if tr_a._imported_modules is not None: - dl_a: t.CSizeT = string.strlen(tr_a._imported_modules) - df_a.write(tr_a._imported_modules, dl_a) - df_a.close() - stdlib.free(deps_path_a) - # 填充内存依赖存储器(供 _BuildReachableSha1Set 使用,不读 .deps.txt) - if tr_a._imported_modules is not None: - StubMergerStore.PopulateIncludesDeps(sha1_a, tr_a._imported_modules) - - # 释放 Translator 的 C malloc 资源 - if tr_a._global_names is not None: - stdlib.free(tr_a._global_names) - if tr_a._nonlocal_names is not None: - stdlib.free(tr_a._nonlocal_names) - - stdlib.free(src_buf_a) - # 回收本文件 AST/lexer/token(deps 已写盘、依赖存储器已深拷贝) - mb.reset_to(scan_mark_a) - - # 1a-pre 阶段总耗时(全部 includes 文件读+parse+import 依赖扫描) - t_pre1: t.CUInt64T = w32.win32base.GetTickCount64() - VLogger.info(f"1a-pre 总耗时: {t_pre1 - t_pre0} ms ({result.Count} 文件扫描 import 依赖)", "Phase1") - - if p1a_failed > 0: - VLogger.error(f"Phase 1a-pre 有 {p1a_failed} 个文件扫描失败", "Phase1") - return 1 - - # ============================================================ - # 依赖图按需翻译:构建可达 SHA1 集合 - # - # 从 Config.SourceDir 的源文件开始,解析 import 语句,递归收集 - # 可达的 includes 文件 SHA1。Phase 1b 只翻译可达集合中的文件, - # 避免翻译不需要的 includes(如 Test 不依赖 llvmlite,则不翻译)。 - # 如果 Config.SourceDir 不可用(仅运行 Phase1),回退到 sha1_set。 - # ============================================================ + # 依赖图按需扫描(App 优先模型): + # 1) 先扫 App 源文件(Config.SourceDir)的 import 依赖 → 初始队列 + # 2) BFS 沿队列按文件系统解析 includes 文件,只对可达 includes + # 读文件+算 SHA1+解析 import,补充依赖图并登记 Sha1Store/ + # deps 存储器/写 deps.txt + # 3) 未引用的 includes 从不读取(根本不扫描) + t_scan0: t.CUInt64T = w32.win32base.GetTickCount64() reachable_set: str = None reachable_count: int = 0 use_reachable: int = 0 @@ -284,22 +385,52 @@ def RunPhase1(mb: memhub.MemBuddy | t.CPtr, includes_dir: str, temp_dir: str, reachable_set = stdlib.malloc(StubMerger.MAX_INCLUDES_SHA1 * 17) if reachable_set is not None: string.memset(reachable_set, 0, StubMerger.MAX_INCLUDES_SHA1 * 17) - reachable_count = StubMergerBuild._BuildReachableSha1Set(mb, Config.SourceDir, temp_dir, reachable_set) + reachable_count = StubMergerBuild._BuildReachableSha1Set( + mb, Config.SourceDir, temp_dir, reachable_set, None, includes_dir) if reachable_count < 0: - VLogger.error("可达集合构建失败,终止", "Phase1") + VLogger.error("可达集合构建失败(依赖图扫描失败),终止编译", "Phase1") return 1 - if reachable_count > 0: - use_reachable = 1 - # 用可达集合重新生成 _sha1_map.txt(按图求索的最终产物) - # Phase B+ 只遍历这些条目,避免编译不需要的 includes(如 asm.vp) - StubMergerCollect.WriteIncludesSha1Map(mb, temp_dir, result, reachable_set, reachable_count) - # 重新加载 sha1_set,使后续 Phase 1a-pre/1a/1b 的过滤也使用可达集合 - string.memset(sha1_set, 0, StubMerger.MAX_INCLUDES_SHA1 * 17) - set_count = StubMergerCollect._load_includes_sha1_set(mb, temp_dir, sha1_set) - else: - VLogger.warning("可达集合为空,回退全量扫描", "Phase1") + if reachable_count <= 0: + VLogger.warning("App 无可达 includes 依赖,跳过 Phase1", "Phase1") stdlib.free(reachable_set) reachable_set = None + return 0 + use_reachable = 1 + else: + VLogger.error("reachable_set 分配失败,终止编译", "Phase1") + return 1 + else: + VLogger.warning("SourceDir 不可用,无法构建依赖图,跳过 Phase1", "Phase1") + return 0 + t_scan1: t.CUInt64T = w32.win32base.GetTickCount64() + VLogger.info(f"依赖图扫描总耗时: {t_scan1 - t_scan0} ms(可达 {reachable_count} 个 includes)", "Phase1") + + # 从 Sha1Store(仅可达 includes + App)构造可达 FileEntry 列表: + # 后续 1a/1b-pre/1b 直接遍历 result(incdir 非空条目=includes), + # use_reachable 过滤幂等保留(result 即可达子集) + result: IncludesScanner.ScanResult | t.CPtr = _BuildReachableResult(mb, includes_dir) + if result is None: + VLogger.error("可达 FileEntry 列表构建失败,终止编译", "Phase1") + return 1 + VLogger.info(f"可达 includes 条目: {result.Count}", "Phase1") + + # 扫描后统一清理 _list/_dict 注入环边(_list↔memhub 等 2-环)。 + # 依赖图按需扫描顺序不可靠,内联检查可能漏判,此处全量清理保证 + # 依赖图无注入环,Phase A 多遍可收敛。破环后同步重写 deps.txt。 + _BreakContainerCyclesInDeps(temp_dir) + + # 写 _sha1_map.txt(仅可达 includes,人类可读输出;机器分析使用内存存储器) + StubMergerCollect.WriteIncludesSha1Map(mb, temp_dir, result, reachable_set, reachable_count) + + # 兼容旧引用:sha1_set/set_count 对齐可达集合(use_reachable=1 时不被使用) + sha1_set: str = reachable_set + set_count: int = reachable_count + # 模块 SHA1 映射已由依赖图扫描登记到 StubMerger 全局存储器(仅可达 includes + App), + # HandlesExprCall._lookup_module_sha1 直接查全局存储器。 + + # FileEntry 结构体大小(Phase 1a 和 1b 共用) + entry_size: t.CSizeT = IncludesScanner.FileEntry.__sizeof__() + # ============================================================ # Phase 1a: 注册可达 includes 文件的 struct/enum/union @@ -703,26 +834,71 @@ def RunPhase1(mb: memhub.MemBuddy | t.CPtr, includes_dir: str, temp_dir: str, continue # 检查 stub 是否已存在(按需翻译:跳过已存在的) - sf: fileio.File | t.CPtr = fileio.File(stub_path, fileio.MODE.R) - if not sf.closed: - sf.close() - # 检查 text.ll 是否也存在(虚表扫描需要 text.ll) - text_path: str = StubMergerCollect._sliced_path(temp_dir, dir_len, sha1, "text.ll") - if text_path is not None: - tf: fileio.File | t.CPtr = fileio.File(text_path, fileio.MODE.R) - if not tf.closed: - tf.close() - stdlib.free(text_path) - stdlib.free(stub_path) - continue - stdlib.free(text_path) - # text.ll 不存在,需要重新翻译 + # 例外:含 class 定义的模块必须翻译——类注册(register_struct) + # 只在翻译时发生;增量编译时 stub/text 已存在被跳过 → 跨模块类构造 + # find_struct_by_module 落空 → 返回类型 fail-fast + # (fltk.Fl_Window(...) → "4049e14d73c7e091.Fl_Window 无法精确获取", + # 仅 --clean 全量翻译时可用)。扫描 1b-pre 生成的 vpi 是否有 + # "class " 行(vpi 为解析后存根,精确无注释/字符串误判)。 + force_translate: int = 0 + vpi_path_1b: str = StubMergerCollect._sliced_path(temp_dir, dir_len, sha1, "vpi") + if vpi_path_1b is not None: + vf_1b: fileio.File | t.CPtr = fileio.File(vpi_path_1b, fileio.MODE.R) + if not vf_1b.closed: + vbuf_1b: bytes = stdlib.malloc(1048576) + if vbuf_1b is not None: + vrd_1b: LONG = vf_1b.read_all(vbuf_1b, 1048576) + vf_1b.close() + if vrd_1b > 0: + if vrd_1b < 1048576: + vbuf_1b[vrd_1b] = 0 + else: + vbuf_1b[1048576 - 1] = 0 + vtotal_1b: t.CSizeT = string.strlen(vbuf_1b) + vpos_1b: t.CSizeT = 0 + while vpos_1b < vtotal_1b: + vls_1b: t.CSizeT = vpos_1b + while vpos_1b < vtotal_1b and vbuf_1b[vpos_1b] != '\n': + vpos_1b += 1 + vll_1b: t.CSizeT = vpos_1b - vls_1b + if vpos_1b < vtotal_1b: + vpos_1b += 1 + vi2_1b: t.CSizeT = vls_1b + while vi2_1b < vls_1b + vll_1b and vbuf_1b[vi2_1b] == ' ': + vi2_1b += 1 + if vll_1b >= 6 and string.strncmp(vbuf_1b + vi2_1b, "class ", 6) == 0: + force_translate = 1 + break + stdlib.free(vbuf_1b) + else: + vf_1b.close() + stdlib.free(vpi_path_1b) + if force_translate == 1: + # 含 class:强制翻译(覆盖 stub/text,重新注册类) rp: str = entry.RelPath - VLogger.info(f"text.ll 不存在,重新翻译: {rp}(sha1={sha1})", "Phase1") + VLogger.info(f"含 class 定义,强制翻译: {rp}(sha1={sha1})", "Phase1") else: - # stub 不存在,需要翻译 - rp: str = entry.RelPath - VLogger.info(f"翻译: {rp}(sha1={sha1})", "Phase1") + # 检查 stub 是否已存在(按需翻译:跳过已存在的) + sf: fileio.File | t.CPtr = fileio.File(stub_path, fileio.MODE.R) + if not sf.closed: + sf.close() + # 检查 text.ll 是否也存在(虚表扫描需要 text.ll) + text_path: str = StubMergerCollect._sliced_path(temp_dir, dir_len, sha1, "text.ll") + if text_path is not None: + tf: fileio.File | t.CPtr = fileio.File(text_path, fileio.MODE.R) + if not tf.closed: + tf.close() + stdlib.free(text_path) + stdlib.free(stub_path) + continue + stdlib.free(text_path) + # text.ll 不存在,需要重新翻译 + rp: str = entry.RelPath + VLogger.info(f"text.ll 不存在,重新翻译: {rp}(sha1={sha1})", "Phase1") + else: + # stub 不存在,需要翻译 + rp: str = entry.RelPath + VLogger.info(f"翻译: {rp}(sha1={sha1})", "Phase1") stdlib.free(stub_path)