From 29667672e4f7f350c9e6b7d8d225f8b8e2f447c7 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 1 Sep 2026 20:15:26 +0800 Subject: [PATCH] =?UTF-8?q?feat(linker):=20TPV=20=E6=94=AF=E6=8C=81=20proj?= =?UTF-8?q?ect.vpj=20=E9=9D=99=E6=80=81=E9=93=BE=E6=8E=A5=20.a/.obj?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Config.vp 新增 LinkerStaticLibs(读取 linker.static_libs 数组并基于 project_dir 解析绝对路径) - BuildPipeline link_obj_to_exe/link_objs_to_exe/run_pipeline 支持 static_libs(位于 .obj 之后、-l 之前) - main.vp/Phase2.RunMultiFileProject 全链传递 linker_static_libs --- App/lib/Phase/BuildPipeline.vp | 52 +++- App/lib/Phase/Phase2/__init__.vp | 480 ++++++++++++++++++++++++++++++- App/lib/Projectrans/Config.vp | 41 ++- App/main.vp | 7 +- 4 files changed, 553 insertions(+), 27 deletions(-) diff --git a/App/lib/Phase/BuildPipeline.vp b/App/lib/Phase/BuildPipeline.vp index 3e83565..3d14767 100644 --- a/App/lib/Phase/BuildPipeline.vp +++ b/App/lib/Phase/BuildPipeline.vp @@ -175,6 +175,9 @@ def TranslateFileGetTrans(mb: memhub.MemBuddy | t.CPtr, file_path: str, # 0xffffffffffffffff 崩溃),docstring 提取与翻译状态无关,提前安全。 if gen is not None: gen.ExprGenCls.extract_module_docstrings(tree, sha1_val) + if tr._deps_pending != 0: + # [PF-DBG] 入口即脏(用后移除):__init__ 未初始化或 mb 池残留 + stdio.printf("[PF-DBG] trans-enter DIRTY: pending=%d file=%s\n", tr._deps_pending, file_path) ret: int = tr.translate(tree) if ret != 0: VLogger.error(f"文件翻译失败: {file_path}", "Translate") @@ -716,7 +719,7 @@ def collect_obj_files(includes_binary_dir: str, out_buf: bytes, out_size: t.CSiz def link_obj_to_exe(output_dir: str, module_name: str, linker_cmd: str, linker_flags: str, linker_output: str, - includes_binary_dir: str) -> int: + includes_binary_dir: str, linker_static_libs: str = None) -> int: """调用 clang++ 链接 .obj 为 .exe Args: @@ -726,6 +729,7 @@ def link_obj_to_exe(output_dir: str, module_name: str, linker_cmd: str, linker_f linker_flags: 链接器参数 linker_output: 输出文件名(如 "test.exe") includes_binary_dir: includes.binary 目录路径(链接时附加预编译 .obj) + linker_static_libs: 显式静态链接文件(.a/.obj,空格分隔绝对路径,可为 None) Returns: 0 成功,非 0 失败 @@ -751,8 +755,9 @@ def link_obj_to_exe(output_dir: str, module_name: str, linker_cmd: str, linker_f else: VLogger.warning(f"includes.binary 无 .obj 文件: {includes_binary_dir}", "link") - # 构造命令: clang++ {obj_path} extra_objs -o {output_dir}/{linker_output} linker_flags + # 构造命令: clang++ {obj_path} extra_objs [static_libs] -o {output_dir}/{linker_output} linker_flags # 注意: .obj 文件必须在 -l 库标志之前,否则链接器无法解析符号依赖 + # linker_static_libs(project.vpj 显式指定的 .a/.obj)同样位于 -l 之前 # ASan 模式:mingw64 clang 的 -fsanitize=address 在 mingw64/lib 下找不到 # asan 运行库(实际在 clang64),改为显式链接 clang64 的 libclang_rt # (DLL 版,运行时需 D:/msys64/clang64/bin 在 PATH) @@ -765,11 +770,23 @@ def link_obj_to_exe(output_dir: str, module_name: str, linker_cmd: str, linker_f viperlib.snprintf(use_flags, fl_len, "%s -L D:/msys64/clang64/lib/clang/22/lib/windows -l:libclang_rt.asan_dynamic-x86_64.dll.a -l:libclang_rt.asan_dynamic_runtime_thunk-x86_64.a", linker_flags) - cmd_len: t.CSizeT = len(linker_cmd) + len(obj_path) + len(use_flags) + len(output_dir) + len(linker_output) + extra_len + 128 + static_libs: str = linker_static_libs + if static_libs is None: + static_libs = "" + sl_len: t.CSizeT = len(static_libs) + cmd_len: t.CSizeT = len(linker_cmd) + len(obj_path) + len(use_flags) + len(output_dir) + len(linker_output) + extra_len + sl_len + 128 cmd: bytes = _mbuddy.alloc(cmd_len) if cmd is None: return 1 - if extra_len > 0: + if sl_len > 0 and extra_len > 0: + viperlib.snprintf(cmd, cmd_len, "%s %s %s %s -o %s/%s %s", + linker_cmd, obj_path, extra_objs, static_libs, + output_dir, linker_output, use_flags) + elif sl_len > 0: + viperlib.snprintf(cmd, cmd_len, "%s %s %s -o %s/%s %s", + linker_cmd, obj_path, static_libs, + output_dir, linker_output, use_flags) + elif extra_len > 0: viperlib.snprintf(cmd, cmd_len, "%s %s %s -o %s/%s %s", linker_cmd, obj_path, extra_objs, output_dir, linker_output, use_flags) @@ -895,7 +912,7 @@ def compile_module_to_obj(ir_buf: bytes, ir_len: t.CSizeT, def link_objs_to_exe(obj_paths: str, obj_paths_len: t.CSizeT, linker_cmd: str, linker_flags: str, linker_output: str, - includes_binary_dir: str) -> int: + includes_binary_dir: str, linker_static_libs: str = None) -> int: """链接多个 .obj 文件为 .exe Args: @@ -905,6 +922,7 @@ def link_objs_to_exe(obj_paths: str, obj_paths_len: t.CSizeT, linker_flags: 链接器参数 linker_output: 输出文件完整路径 includes_binary_dir: includes.binary 目录路径(链接时附加预编译 .obj) + linker_static_libs: 显式静态链接文件(.a/.obj,空格分隔绝对路径,可为 None) Returns: 0 成功,非 0 失败 @@ -987,11 +1005,24 @@ def link_objs_to_exe(obj_paths: str, obj_paths_len: t.CSizeT, viperlib.snprintf(use_flags, fl_len, "%s -L D:/msys64/clang64/lib/clang/22/lib/windows -l:libclang_rt.asan_dynamic-x86_64.dll.a -l:libclang_rt.asan_dynamic_runtime_thunk-x86_64.a", linker_flags) - cmd_len: t.CSizeT = len(linker_cmd) + obj_paths_len + len(use_flags) + len(linker_output) + extra_len + 128 + static_libs2: str = linker_static_libs + if static_libs2 is None: + static_libs2 = "" + sl2_len: t.CSizeT = len(static_libs2) + cmd_len: t.CSizeT = len(linker_cmd) + obj_paths_len + len(use_flags) + len(linker_output) + extra_len + sl2_len + 128 cmd: bytes = _mbuddy.alloc(cmd_len) if cmd is None: return 1 - if extra_len > 0: + # linker.static_libs 显式静态链接文件位于 .obj 之后、-o 之前 + if sl2_len > 0 and extra_len > 0: + viperlib.snprintf(cmd, cmd_len, "%s %s %s %s -o %s %s", + linker_cmd, obj_paths, extra_objs, static_libs2, + linker_output, use_flags) + elif sl2_len > 0: + viperlib.snprintf(cmd, cmd_len, "%s %s %s -o %s %s", + linker_cmd, obj_paths, static_libs2, + linker_output, use_flags) + elif extra_len > 0: viperlib.snprintf(cmd, cmd_len, "%s %s %s -o %s %s", linker_cmd, obj_paths, extra_objs, linker_output, use_flags) @@ -1026,7 +1057,7 @@ def run_pipeline(ir_buf: bytes, ir_len: t.CSizeT, temp_dir: str, output_dir: str, module_name: str, cc_cmd: str, cc_flags: str, linker_cmd: str, linker_flags: str, linker_output: str, - includes_binary_dir: str) -> BuildResult | t.CPtr: + includes_binary_dir: str, linker_static_libs: str = None) -> BuildResult | t.CPtr: """执行完整编译管线 Args: @@ -1041,6 +1072,7 @@ def run_pipeline(ir_buf: bytes, ir_len: t.CSizeT, linker_flags: 链接器参数 linker_output: 输出文件名 includes_binary_dir: includes.binary 目录路径(链接时附加预编译 .obj) + linker_static_libs: 显式静态链接文件(.a/.obj,空格分隔绝对路径,可为 None) Returns: BuildResult 对象 @@ -1074,9 +1106,9 @@ def run_pipeline(ir_buf: bytes, ir_len: t.CSizeT, result.ErrorMsg = "llc 编译失败" return result - # Step 3: clang++ 链接 .obj → .exe(附加 includes.binary 预编译 .obj) + # Step 3: clang++ 链接 .obj → .exe(附加 includes.binary 预编译 .obj 与 static_libs) ret = link_obj_to_exe(output_dir, module_name, linker_cmd, linker_flags, linker_output, - includes_binary_dir) + includes_binary_dir, linker_static_libs) if ret != 0: result.Success = 0 result.ErrorMsg = "链接失败" diff --git a/App/lib/Phase/Phase2/__init__.vp b/App/lib/Phase/Phase2/__init__.vp index a5c1f5d..67f8397 100644 --- a/App/lib/Phase/Phase2/__init__.vp +++ b/App/lib/Phase/Phase2/__init__.vp @@ -341,6 +341,419 @@ def _PreRegisterAppGenericTemplates(mb: memhub.MemBuddy | t.CPtr, return handled +# ============================================================ +# Phase A 收敛失败依赖图诊断 +# +# 多遍收敛失败时,原 ERROR 只报 "无法完成翻译: <路径>",对"直接依赖均已 +# 生成但缺失在传递依赖/MRO/vpi 侧"(如 gfx.vp 经 list 模板 → _list → memhub) +# 无从定位。这里在翻译过程中把每个待译文件的依赖模块名与缺失明细持久化到 +# stdlib 全局表(跨 mb.reset_to(mark2) 存活),收敛失败时按文件输出依赖树, +# 用 [MISS]/[OK] 标出缺 text.ll 的节点,并回显 pending 明细。 +# ============================================================ +PA_DEPS_MAX_STR: t.CSizeT = 1024 +PA_MISS_MAX_STR: t.CSizeT = 768 +PA_GRAPH_DEPTH_MAX: t.CInt = 3 # 缺失链下钻深度(第 1 层开始计数) +PA_GRAPH_OK_PROBE: t.CInt = 2 # 就绪节点仅再探测 1 层(找隐藏缺失) +_pa_diag_max: t.CSizeT = 0 +_pa_diag_count: int = 0 +_pa_diag_sha1: bytes = None +_pa_diag_deps: bytes = None +_pa_diag_miss: bytes = None + + +def _PhaseADiagInit(max_count: t.CSizeT) -> None: + """分配 Phase A 收敛失败诊断持久表(stdlib,跨 reset 存活)""" + global _pa_diag_max, _pa_diag_count, _pa_diag_sha1, _pa_diag_deps, _pa_diag_miss + _pa_diag_max = max_count + _pa_diag_count = 0 + _pa_diag_sha1 = stdlib.malloc(max_count * 17) + _pa_diag_deps = stdlib.malloc(max_count * PA_DEPS_MAX_STR) + _pa_diag_miss = stdlib.malloc(max_count * PA_MISS_MAX_STR) + if _pa_diag_sha1 is not None: + string.memset(_pa_diag_sha1, 0, max_count * 17) + if _pa_diag_deps is not None: + string.memset(_pa_diag_deps, 0, max_count * PA_DEPS_MAX_STR) + if _pa_diag_miss is not None: + string.memset(_pa_diag_miss, 0, max_count * PA_MISS_MAX_STR) + + +def _PhaseADiagFree() -> None: + """释放诊断持久表""" + global _pa_diag_max, _pa_diag_count, _pa_diag_sha1, _pa_diag_deps, _pa_diag_miss + if _pa_diag_sha1 is not None: + stdlib.free(_pa_diag_sha1) + _pa_diag_sha1 = None + if _pa_diag_deps is not None: + stdlib.free(_pa_diag_deps) + _pa_diag_deps = None + if _pa_diag_miss is not None: + stdlib.free(_pa_diag_miss) + _pa_diag_miss = None + _pa_diag_count = 0 + _pa_diag_max = 0 + + +def _PhaseADiagRecord(sha1: str, deps: str, miss: str) -> None: + """记录/更新单文件诊断:sha1 → 依赖串 + 缺失明细(None 表示不更新该字段)""" + global _pa_diag_count + if _pa_diag_sha1 is None or _pa_diag_deps is None or sha1 is None: + return + if string.strlen(sha1) < 16: + return + for i in range(_pa_diag_count): + es: bytes = _pa_diag_sha1 + t.CSizeT(i) * 17 + if string.strcmp(es, sha1) == 0: + if miss is not None and _pa_diag_miss is not None: + maddr: bytes = _pa_diag_miss + t.CSizeT(i) * PA_MISS_MAX_STR + string.strncpy(maddr, miss, PA_MISS_MAX_STR - 1) + maddr[PA_MISS_MAX_STR - 1] = 0 + return + if _pa_diag_count >= _pa_diag_max: + return + saddr: bytes = _pa_diag_sha1 + t.CSizeT(_pa_diag_count) * 17 + string.strncpy(saddr, sha1, 16) + saddr[16] = 0 + daddr: bytes = _pa_diag_deps + t.CSizeT(_pa_diag_count) * PA_DEPS_MAX_STR + if deps is not None: + string.strncpy(daddr, deps, PA_DEPS_MAX_STR - 1) + daddr[PA_DEPS_MAX_STR - 1] = 0 + if miss is not None and _pa_diag_miss is not None: + maddr2: bytes = _pa_diag_miss + t.CSizeT(_pa_diag_count) * PA_MISS_MAX_STR + string.strncpy(maddr2, miss, PA_MISS_MAX_STR - 1) + maddr2[PA_MISS_MAX_STR - 1] = 0 + _pa_diag_count += 1 + + +def _PhaseADiagDepsLookup(sha1: str) -> str: + """按 sha1 查持久化依赖串(stdlib.malloc 副本,调用者 free;未命中 None)""" + if _pa_diag_sha1 is None or _pa_diag_deps is None or sha1 is None: + return None + for i in range(_pa_diag_count): + es: bytes = _pa_diag_sha1 + t.CSizeT(i) * 17 + if string.strcmp(es, sha1) == 0: + src: bytes = _pa_diag_deps + t.CSizeT(i) * PA_DEPS_MAX_STR + ln: t.CSizeT = string.strlen(src) + out: bytes = stdlib.malloc(ln + 1) + if out is None: + return None + string.strcpy(out, src) + return out + return None + + +def _PhaseADiagMissLookup(sha1: str) -> str: + """按 sha1 查持久化缺失明细(stdlib.malloc 副本,调用者 free;未命中/空 None)""" + if _pa_diag_sha1 is None or _pa_diag_miss is None or sha1 is None: + return None + for i in range(_pa_diag_count): + es: bytes = _pa_diag_sha1 + t.CSizeT(i) * 17 + if string.strcmp(es, sha1) == 0: + src: bytes = _pa_diag_miss + t.CSizeT(i) * PA_MISS_MAX_STR + ln: t.CSizeT = string.strlen(src) + if ln == 0: + return None + out: bytes = stdlib.malloc(ln + 1) + if out is None: + return None + string.strcpy(out, src) + return out + return None + + +def _PhaseATextLLExists(temp_dir: str, td_len: t.CSizeT, sha1: str) -> int: + """探测 {temp_dir}/{sha1前2位}/{sha1}.text.ll 是否存在,1=存在""" + if temp_dir is None or td_len == 0 or sha1 is None or string.strlen(sha1) < 2: + return 0 + tpath: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1, "text.ll") + if tpath is None: + return 0 + tf: fileio.File | t.CPtr = fileio.File(tpath, fileio.MODE.R) + exists: int = 0 + if not tf.closed: + tf.close() + exists = 1 + stdlib.free(tpath) + return exists + + +def _PhaseAResolveDepSha1(mod_name: str) -> str: + """依赖模块名 → SHA1(精确 → 后缀兜底),未命中 None""" + if mod_name is None: + return None + s1: str = StubMergerStore.LookupSha1ByModName(mod_name) + if s1 is not None: + return s1 + return StubMergerStore.LookupSha1ByModNameSuffix(mod_name) + + +def _PhaseADumpNode(temp_dir: str, td_len: t.CSizeT, sha1: str, + depth: int, prefix: bytes) -> None: + """递归打印依赖子图;[MISS]=缺 text.ll(下钻整链),[OK]=就绪(仅探测 1 层)""" + deps: bytes = None + if sha1 is not None: + deps = _PhaseADiagDepsLookup(sha1) + if deps is None and sha1 is not None: + # 回退:读取 {temp_dir}/{前缀}/{sha1}.deps.txt(Phase1 已为 includes 写盘) + dpath: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1, "deps.txt") + if dpath is not None: + df: fileio.File | t.CPtr = fileio.File(dpath, fileio.MODE.R) + if not df.closed: + deps = stdlib.malloc(PA_DEPS_MAX_STR) + if deps is not None: + rl: t.CInt64T = df.read_all(deps, PA_DEPS_MAX_STR - 1) + if rl < 0: + rl = 0 + if rl >= PA_DEPS_MAX_STR - 1: + deps[PA_DEPS_MAX_STR - 1] = 0 + else: + deps[rl] = 0 + df.close() + stdlib.free(dpath) + if deps is None or deps[0] == 0: + if deps is not None: + stdlib.free(deps) + return + dl: t.CSizeT = string.strlen(deps) + token_count: int = 0 + pos: t.CSizeT = 0 + while pos < dl: + while pos < dl and deps[pos] == ' ': + pos += 1 + if pos >= dl: + break + token_count += 1 + while pos < dl and deps[pos] != ' ': + pos += 1 + pos = 0 + idx: int = 0 + while pos < dl: + while pos < dl and deps[pos] == ' ': + pos += 1 + if pos >= dl: + break + ts: t.CSizeT = pos + while pos < dl and deps[pos] != ' ': + pos += 1 + tl: t.CSizeT = pos - ts + nm: bytes = stdlib.malloc(tl + 1) + if nm is not None: + string.strncpy(nm, deps + ts, tl) + nm[tl] = 0 + is_last: int = 0 + if idx + 1 >= token_count: + is_last = 1 + builtin: int = 0 + if string.strcmp(nm, "t") == 0 or string.strcmp(nm, "c") == 0: + builtin = 1 + dep_sha1: str = _PhaseAResolveDepSha1(nm) + txt_ok: int = 0 + if dep_sha1 is not None: + txt_ok = _PhaseATextLLExists(temp_dir, td_len, dep_sha1) + # 分支字符内联(不返回字符串字面量:避免 TPV 对字面量返回的 + # 栈临时指针悬垂 → printf %s 崩溃,图输出被吞) + br_1: str = "|-- " + br_2: str = "`-- " + if builtin != 0: + if is_last != 0: + stdio.printf("%s%s%s (builtin)\n", prefix, br_2, nm) + else: + stdio.printf("%s%s%s (builtin)\n", prefix, br_1, nm) + elif dep_sha1 is None: + if is_last != 0: + stdio.printf("%s%s%s (unresolved)\n", prefix, br_2, nm) + else: + stdio.printf("%s%s%s (unresolved)\n", prefix, br_1, nm) + elif txt_ok != 0: + if is_last != 0: + stdio.printf("%s%s%s [OK]\n", prefix, br_2, nm) + else: + stdio.printf("%s%s%s [OK]\n", prefix, br_1, nm) + else: + if is_last != 0: + stdio.printf("%s%s%s [MISS] text.ll=%s\n", prefix, br_2, nm, dep_sha1) + else: + stdio.printf("%s%s%s [MISS] text.ll=%s\n", prefix, br_1, nm, dep_sha1) + # 下钻:MISS 全链(至 PA_GRAPH_DEPTH_MAX),OK 仅探测 1 层(PA_GRAPH_OK_PROBE) + can_expand: int = 0 + if dep_sha1 is not None and builtin == 0: + if txt_ok == 0: + if depth + 1 < PA_GRAPH_DEPTH_MAX: + can_expand = 1 + else: + if depth + 1 < PA_GRAPH_OK_PROBE: + can_expand = 1 + if can_expand != 0: + # +5:strcat 追加 "| "/" "(4 字符)+ NUL;strlen+4 会溢出 1 字节 + # 破坏堆元数据 → 后续 malloc/free 崩溃,图打印中断且 stdout 缓冲丢失 + npl: t.CSizeT = string.strlen(prefix) + 5 + new_prefix: bytes = stdlib.malloc(npl) + if new_prefix is not None: + string.strcpy(new_prefix, prefix) + if is_last != 0: + string.strcat(new_prefix, " ") + else: + string.strcat(new_prefix, "| ") + _PhaseADumpNode(temp_dir, td_len, dep_sha1, depth + 1, new_prefix) + stdlib.free(new_prefix) + stdlib.free(nm) + idx += 1 + stdlib.free(deps) + + +def _PhaseASha1ToModName(sha1: str) -> str: + """sha1 → 模块名(遍历 StubMerger store 反查;未命中 None;返回 store 内部指针)""" + if sha1 is None or StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None: + return None + for i in range(StubMerger._sha1_store_count): + es: bytes = StubMerger._sha1_store_arr + t.CSizeT(i) * 17 + if string.strcmp(es, sha1) == 0: + return StubMerger._sha1_store_mod + t.CSizeT(i) * 64 + return None + + +def _PhaseADumpMroPending(temp_dir: str, td_len: t.CSizeT, sha1: str) -> None: + """输出该调用方文件的 MRO/VPI pending 调用点诊断(读取 _phaseA_pending.log) + + 行格式: {owner_sha1}\t{kind}\t{cls}\t{method}\t{dep_sha1}\t{extra}\n + 与依赖图互补:图显示"缺 text.ll 的模块链",此段显示"具体哪次方法调用、 + 哪个类、哪个方法、返回类型定义方是哪个模块"(MRO/vpi 侧缺失)。 + """ + if temp_dir is None or sha1 is None: + return + path_len: t.CSizeT = td_len + 24 + path_buf: bytes = stdlib.malloc(path_len) + if path_buf is None: + return + viperlib.snprintf(path_buf, path_len, "%s/_phaseA_pending.log", temp_dir) + rf: fileio.File | t.CPtr = fileio.File(path_buf, fileio.MODE.R) + stdlib.free(path_buf) + if rf.closed: + return + buf: bytes = stdlib.malloc(8192) + if buf is None: + rf.close() + return + n: t.CInt64T = rf.read_all(buf, 8191) + rf.close() + if n < 0: + n = 0 + if n < 8191: + buf[n] = 0 + else: + buf[8191] = 0 + nsz: t.CSizeT = t.CSizeT(n) + pos: t.CSizeT = 0 + hits: int = 0 + while pos < nsz: + ls: t.CSizeT = pos + while pos < nsz and buf[pos] != '\n': + pos += 1 + ll: t.CSizeT = pos - ls + if pos < nsz: + pos += 1 + if ll < 8: + continue + # 首字段(tab 前)须与目标 sha1 全等 + tab_pos: t.CSizeT = 0 + while tab_pos < ll and buf[ls + tab_pos] != '\t': + tab_pos += 1 + if tab_pos == 0 or tab_pos >= ll: + continue + if tab_pos != string.strlen(sha1): + continue + if string.strncmp(buf + ls, sha1, tab_pos) != 0: + continue + # 解析 tab 分隔字段:kind / cls / method / dep_sha1 / extra + f2: t.CSizeT = tab_pos + 1 + f2e: t.CSizeT = f2 + while f2e < ll and buf[ls + f2e] != '\t': + f2e += 1 + f3: t.CSizeT = f2e + 1 + f3e: t.CSizeT = f3 + while f3e < ll and buf[ls + f3e] != '\t': + f3e += 1 + f4: t.CSizeT = f3e + 1 + f4e: t.CSizeT = f4 + while f4e < ll and buf[ls + f4e] != '\t': + f4e += 1 + f5: t.CSizeT = f4e + 1 + f5e: t.CSizeT = f5 + while f5e < ll and buf[ls + f5e] != '\t': + f5e += 1 + f6: t.CSizeT = f5e + 1 + if f5e >= ll: + continue + # 临时把 tab 边界置 NUL 分段,打印后复原 + c2: int = buf[ls + f2e] + c3: int = buf[ls + f3e] + c4: int = buf[ls + f4e] + c5: int = buf[ls + f5e] + buf[ls + f2e] = 0 + buf[ls + f3e] = 0 + buf[ls + f4e] = 0 + buf[ls + f5e] = 0 + kind_b: bytes = buf + ls + f2 + cls_b: bytes = buf + ls + f3 + mtd_b: bytes = buf + ls + f4 + dep_b: bytes = buf + ls + f5 + ext_b: bytes = buf + ls + f6 + if hits == 0: + stdio.printf("[PhaseA-Fail] pending call sites (definer module text.ll missing):\n") + if string.strcmp(kind_b, "mro_ret") == 0: + mod_nm: str = _PhaseASha1ToModName(dep_b) + if mod_nm is not None: + stdio.printf(" [mro_ret] %s.%s -> definer %s [%s] %s\n", + cls_b, mtd_b, mod_nm, dep_b, ext_b) + else: + stdio.printf(" [mro_ret] %s.%s -> definer [%s] %s\n", + cls_b, mtd_b, dep_b, ext_b) + elif string.strcmp(kind_b, "func_ret") == 0: + mod_nm2: str = _PhaseASha1ToModName(dep_b) + if mod_nm2 is not None: + stdio.printf(" [func_ret] func %s -> definer %s [%s]\n", mtd_b, mod_nm2, dep_b) + else: + stdio.printf(" [func_ret] func %s -> definer [%s]\n", mtd_b, dep_b) + else: + stdio.printf(" [%s] cls=%s name=%s dep=[%s] extra=%s\n", + kind_b, cls_b, mtd_b, dep_b, ext_b) + hits += 1 + # 复原 tab 边界 + buf[ls + f2e] = c2 + buf[ls + f3e] = c3 + buf[ls + f4e] = c4 + buf[ls + f5e] = c5 + stdlib.free(buf) + + +def _PhaseADumpFailGraph(temp_dir: str, td_len: t.CSizeT, path: str, sha1: str) -> None: + """收敛失败文件诊断:pending 明细 + 依赖树(标出缺 text.ll 节点) + + 每步 printf 后立即 fflush:管道捕获下 stdout 全缓冲,若后续崩溃未刷出 + 则整段诊断丢失("ERROR 可见但依赖图无输出"即此类)。 + """ + stdio.printf("[PhaseA-Fail] ==============================================================\n") + stdio.fflush(None) + stdio.printf("[PhaseA-Fail] FAIL TO TRANSLATE: %s [sha1=%s]\n", path, sha1) + stdio.fflush(None) + miss: bytes = _PhaseADiagMissLookup(sha1) + if miss is not None: + if miss[0] != 0: + stdio.printf("[PhaseA-Fail] pending reason: %s\n", miss) + stdlib.free(miss) + stdio.fflush(None) + stdio.printf("[PhaseA-Fail] dep graph ([MISS]=missing text.ll, [OK]=ready, depth<=%d):\n", + PA_GRAPH_DEPTH_MAX) + stdio.fflush(None) + root_prefix: bytes = stdlib.malloc(1) + if root_prefix is not None: + root_prefix[0] = 0 + _PhaseADumpNode(temp_dir, td_len, sha1, 0, root_prefix) + stdlib.free(root_prefix) + # MRO/VPI pending 调用点明细(与依赖图互补:指出具体哪次方法调用缺返回类型) + _PhaseADumpMroPending(temp_dir, td_len, sha1) + stdio.fflush(None) + + def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, source_dir: str, temp_dir: str, output_dir: str, cc_cmd: str, cc_flags: str, @@ -349,7 +762,8 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, includes_dir: str, do_phase1: int, do_phase2: int, log: VLogger.Logger | t.CPtr, - args: argparse.ParsedArgs | t.CPtr) -> int: + args: argparse.ParsedArgs | t.CPtr, + linker_static_libs: str = None) -> int: """多文件项目编译 Returns: 0 成功,非 0 失败 @@ -410,17 +824,24 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, BuildPipeline.ensure_dir(temp_dir) BuildPipeline.ensure_dir(output_dir) - # === 0.5 扫描 includes 目录填充 Sha1Store(消除 Phase1 依赖)=== + # === 0.5 依赖图按需填充 Sha1Store(消除 Phase1 依赖)=== # Phase2 不依赖 Phase1 的副作用:如果 Sha1Store 为空(Phase1 未运行或提前返回), - # 自行扫描 includes 目录并填充 Sha1Store,确保后续"编译缺失 includes"逻辑能正常工作 + # 自行按依赖图扫描(App 优先 BFS)并填充 Sha1Store,确保后续"编译缺失 includes" + # 逻辑能正常工作。未引用的 includes 根本不扫描(对齐 Phase1 依赖图模型)。 if includes_dir is not None: if StubMergerStore.GetSha1StoreCount() == 0: - inc_scan_result: IncludesScanner.ScanResult | t.CPtr = IncludesScanner.scan_includes(mb, includes_dir) - if inc_scan_result is None: - VLogger.error("includes 扫描失败,无法填充 Sha1Store", "project") - if inc_scan_result is not None: - inc_filled: int = StubMergerStore.PopulateSha1MapStore(inc_scan_result) - VLogger.info(f"Phase2 自主填充 includes Sha1Store: {inc_filled} 个", "project") + rs_05: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES_SHA1 * 17) + if rs_05 is None: + VLogger.error("reachable_set 分配失败,无法填充 Sha1Store", "project") + else: + string.memset(rs_05, 0, StubMerger.MAX_INCLUDES_SHA1 * 17) + rc_05: int = StubMergerBuild._BuildReachableSha1Set( + mb, source_dir, temp_dir, rs_05, None, includes_dir) + if rc_05 < 0: + VLogger.error("依赖图扫描失败,无法填充 Sha1Store", "project") + elif rc_05 > 0: + VLogger.info(f"Phase2 依赖图按需填充 Sha1Store: {rc_05} 个", "project") + stdlib.free(rs_05) # 追加 App 源文件 SHA1 到 _sha1_map.txt(人类可读输出,程序内部不读取此文件) # 格式: {sha1}:{rel_path}\n (保留目录结构,如 lib/core/VLogger.vp) @@ -577,6 +998,18 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, app_deps_len: t.CSizeT = 0 if do_phase1 != 0: log.banner("Phase A: 生成 stub + text") + # Phase A 收敛失败依赖图诊断表初始化(stdlib,跨 mark2 reset 存活) + _PhaseADiagInit(t.CSizeT(file_count) + StubMerger.MAX_INCLUDES) + # 清空跨次构建残留的 MRO/VPI pending 诊断日志(每次 Phase A 全新开始) + if temp_dir is not None: + ppath_t: bytes = stdlib.malloc(string.strlen(temp_dir) + 24) + if ppath_t is not None: + viperlib.snprintf(ppath_t, string.strlen(temp_dir) + 24, + "%s/_phaseA_pending.log", temp_dir) + pf_t: fileio.File | t.CPtr = fileio.File(ppath_t, fileio.MODE.W) + if not pf_t.closed: + pf_t.close() + stdlib.free(ppath_t) # === Phase A-pre-inc: includes 预注册 struct + 依赖填充 === # 必须在 App 源文件预注册之前执行:App 源文件(strict_mode=1)可能继承 @@ -642,6 +1075,8 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, mb, full_path_pre, inc_sha1_pre, pkg_inc_pre, 1) stdlib.free(full_path_pre) if tr_inc_pre is not None: + # 记录本文件依赖(供收敛失败依赖图诊断;stdlib 表跨 reset 存活) + _PhaseADiagRecord(inc_sha1_pre, tr_inc_pre._imported_modules, None) # 预注册成功:清除失败标记 if inc_fail_flags is not None: inc_fail_flags[si_inc] = 0 @@ -751,6 +1186,8 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, # OOM 清理:失败翻译也已产生 AST/tokens 瞬时数据,立即回收 mb.reset_to(mark2) continue + # 记录本文件依赖(供收敛失败依赖图诊断;stdlib 表跨 reset 存活) + _PhaseADiagRecord(ent.Sha1, tr_a._imported_modules, None) # 收集 _imported_modules 到 app_deps_buf(供 Phase B+ 按需编译)。 # 依赖在翻译早期已填充,即使 _deps_pending 也收集。 if tr_a._imported_modules is not None and app_deps_buf is not None: @@ -787,6 +1224,12 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, app_deps_buf[app_deps_len] = '\0' stdlib.free(im_mod_buf) if tr_a._deps_pending != 0: + # [PF-DBG] 出口 pending(用后移除):确认 pending 值与文件归属 + stdio.printf("[PF-DBG] trans-exit pending: val=%d file=%s sha1=%s\n", + tr_a._deps_pending, + ent.Path if ent.Path is not None else "?", + ent.Sha1 if ent.Sha1 is not None else "?") + stdio.fflush(None) # 依赖模块 text.ll 未生成(同项目文件顺序),不写盘,下遍重试 # 诊断增强:遍历本文件直接依赖,列出 text.ll 仍缺失的模块 # (含 SHA1)——精确定位是哪个依赖未完成(通常是其自身依赖链 @@ -871,6 +1314,8 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, war = f"Phase A 依赖未就绪,下遍重试: {ent.Path} | 依赖探测明细: {miss_dp}" else: war = f"Phase A 依赖未就绪,下遍重试: {ent.Path} | 直接依赖 text.ll 均已生成(缺失可能在传递依赖/MRO/vpi 侧)" + # 记录缺失明细(供收敛失败最终 ERROR 输出,不再依赖滚动中的 WARN) + _PhaseADiagRecord(ent.Sha1, None, war) stdlib.free(miss_dp) else: war = f"Phase A 依赖未就绪,下遍重试: {ent.Path}" @@ -952,18 +1397,27 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, if all_done != 0: break if completed_this_pass == 0: - # 无进展:剩余文件无法完成(真实编译错误),终止并列出未完成文件 + # 无进展:剩余文件无法完成(真实编译错误),终止并列出未完成文件。 + # 每个失败文件附带依赖图诊断(_PhaseADumpFailGraph):pending 明细 + + # 依赖树,[MISS] 标出缺 text.ll 的节点,缩小到具体缺失链。 + # 注意:VLogger.error 内部调用 sys.exit(1),必须放在所有图形输出 + # 之后——此前"ERROR 行后调图"导致进程在 ERROR 处即退出,图永远打不出。 + td_len_fail: t.CSizeT = string.strlen(temp_dir) for i_err in range(file_count): if done_flags[i_err] == 0: ea_err: t.CUInt64T = t.CUInt64T(entries) + i_err * entry_size ent_err: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea_err, t.CPtr)) if ent_err is not None and ent_err.Path is not None: - VLogger.error(f"Phase A 无法完成翻译: {ent_err.Path}(请检查依赖模块是否已编译/注册;缺失依赖明细见上方 PhaseA-DBG 警告,若被 --nowarn-register 抑制请去掉该参数重跑)", "PhaseA") + if ent_err.Sha1 is not None: + _PhaseADumpFailGraph(temp_dir, td_len_fail, ent_err.Path, ent_err.Sha1) stdlib.free(done_flags) + _PhaseADiagFree() VLogger.error("Phase A 多遍收敛失败,终止编译", "PhaseA") stdlib.free(entries) return 1 stdlib.free(done_flags) + # Phase A 成功收敛:释放依赖图诊断表(仅失败路径需要,成功后无用途) + _PhaseADiagFree() if do_phase2 == 0: VLogger.info("Phase A 完成(仅 stub 生成)", "project") @@ -1160,7 +1614,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, if reachable_set is not None: string.memset(reachable_set, 0, REACHABLE_SET_SIZE) reachable_count = StubMergerBuild._BuildReachableSha1Set( - mb, source_dir, temp_dir, reachable_set, app_deps_buf) + mb, source_dir, temp_dir, reachable_set, app_deps_buf, includes_dir) VLogger.info(f"可达 includes SHA1: {reachable_count} 个", "PhaseB+") # fast-fail: 可达集合构建失败(依赖未找到),永不回退,直接终止 if reachable_count < 0: @@ -1560,7 +2014,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, lret: int = BuildPipeline.link_objs_to_exe( final_obj_paths, obj_paths_len, linker_cmd, linker_flags, exe_path, - None) + None, linker_static_libs) if lret == 0: VLogger.success(f"输出: {exe_path}", "PhaseC") diff --git a/App/lib/Projectrans/Config.vp b/App/lib/Projectrans/Config.vp index aee0274..0102902 100644 --- a/App/lib/Projectrans/Config.vp +++ b/App/lib/Projectrans/Config.vp @@ -33,6 +33,7 @@ CompilerFlags: str = None # 编译器参数(空格分隔,从 compile LinkerCmd: str = None LinkerFlags: str = None # 链接器参数(空格分隔,从 linker.flags 数组拼接) LinkerOutput: str = None +LinkerStaticLibs: str = None # 显式静态链接文件(空格分隔绝对路径,从 linker.static_libs 数组) TargetTriple: str = None TargetDataLayout: str = None ProjectDir: str = None # project.vpj 所在目录(resolve_paths 设置) @@ -57,7 +58,7 @@ def Load_project_config(path: str) -> int: 0 表示成功,非 0 表示失败 """ global SourceDir, BuildDir, TempDir, OutputDir, ProjectName, ProjectVersion - global CompilerCmd, CompilerFlags, LinkerCmd, LinkerFlags, LinkerOutput + global CompilerCmd, CompilerFlags, LinkerCmd, LinkerFlags, LinkerOutput, LinkerStaticLibs global TargetTriple, TargetDataLayout, Sha1SliceLevel, StrictMode global IncludesDir, IncludesDirs @@ -119,6 +120,10 @@ def Load_project_config(path: str) -> int: # 读取 linker.flags 数组,拼接成空格分隔的字符串 ld_flags_val: JsonValue | t.CPtr = linker["flags"] LinkerFlags = _join_flags_array(ld_flags_val) + # 读取 linker.static_libs 数组:显式静态链接文件(.a/.obj), + # 先拼接原样字符串,resolve_paths 中再基于 project_dir 解析绝对路径 + sl_val: JsonValue | t.CPtr = linker["static_libs"] + LinkerStaticLibs = _join_flags_array(sl_val) # 读取 target 子对象 target: JsonValue | t.CPtr = root["target"] @@ -392,7 +397,7 @@ def resolve_paths(project_dir: str) -> int: 0 表示成功,非 0 表示失败 """ global SourceDir, BuildDir, TempDir, OutputDir, IncludesDir, IncludesDirs - global ProjectDir + global ProjectDir, LinkerStaticLibs if project_dir is None: return 1 if _mbuddy is None: @@ -451,6 +456,38 @@ def resolve_paths(project_dir: str) -> int: IncludesDirs = id_out if IncludesDir is not None: IncludesDir = _join_path(IncludesDir, project_dir) + # linker.static_libs 显式静态链接文件路径基于 project_dir 逐段解析为绝对路径 + if LinkerStaticLibs is not None: + sl_total: t.CSizeT = string.strlen(LinkerStaticLibs) + sl_out: str = _mbuddy.alloc(sl_total * 3 + 64) + if sl_out is not None: + sl_out[0] = '\0' + sl_pos: t.CSizeT = 0 + sl_i: t.CSizeT = 0 + while sl_i < sl_total: + while sl_i < sl_total and LinkerStaticLibs[sl_i] == ' ': + sl_i += 1 + if sl_i >= sl_total: + break + sl_start: t.CSizeT = sl_i + while sl_i < sl_total and LinkerStaticLibs[sl_i] != ' ': + sl_i += 1 + seg_len: t.CSizeT = sl_i - sl_start + if seg_len > 0: + seg_buf: str = _mbuddy.alloc(seg_len + 1) + if seg_buf is not None: + string.strncpy(seg_buf, LinkerStaticLibs + sl_start, seg_len) + seg_buf[seg_len] = '\0' + joined: str = _join_path(seg_buf, project_dir) + if joined is not None: + jl: t.CSizeT = string.strlen(joined) + if sl_pos > 0: + sl_out[sl_pos] = ' ' + sl_pos += 1 + string.strcpy(sl_out + sl_pos, joined) + sl_pos += jl + sl_out[sl_pos] = '\0' + LinkerStaticLibs = sl_out # linker.flags 中 `-T <脚本>` 的链接脚本路径同样基于 project_dir 解析, # 否则 subprocess 按进程 cwd(TransPyC/)找 linker.ld → ld.lld 报 # "cannot find linker script linker.ld" diff --git a/App/main.vp b/App/main.vp index a77ac3a..ca1b604 100644 --- a/App/main.vp +++ b/App/main.vp @@ -368,12 +368,14 @@ Target: %s mf_linker: str = Config.LinkerCmd if Config.LinkerCmd is not None else "clang++" mf_linker_flags: str = Config.LinkerFlags if Config.LinkerFlags is not None else "-lmsvcrt -lucrt -lpthread -lmingwex -lkernel32 -lgcc -Wl,--allow-multiple-definition" mf_linker_out: str = Config.LinkerOutput if Config.LinkerOutput is not None else "app.exe" + mf_static_libs: str = Config.LinkerStaticLibs mf_includes_bin: str = Config.get_includes_binary_dir() mf_ret: int = Phase2.RunMultiFileProject( mb, Config.SourceDir, mf_temp, mf_output, mf_cc, mf_cc_flags, mf_linker, mf_linker_flags, mf_linker_out, - mf_includes_bin, Config.IncludesDirs, do_phase1, do_phase2, log, args) + mf_includes_bin, Config.IncludesDirs, do_phase1, do_phase2, log, args, + mf_static_libs) argparse.release(args) log.success("TransPyV 完成") return mf_ret @@ -492,6 +494,7 @@ Target: %s cc_flags: str = "-filetype=obj -relocation-model=pic" linker_cmd: str = Config.LinkerCmd if Config.LinkerCmd is not None else "clang++" linker_flags: str = "-lmsvcrt -lucrt -lpthread -lmingwex -lkernel32 -lgcc -Wl,--allow-multiple-definition" + linker_static_libs: str = Config.LinkerStaticLibs # --project 模式且未显式指定 --src 时:使用 config 中的 linker_output(如 TransPyV.exe) # 显式指定 --src 时:使用从文件名推导的 output_name(如 test.exe),避免覆盖正在运行的 exe if project is not None and Config.LinkerOutput is not None and args.get_str("src") is None: @@ -563,7 +566,7 @@ Target: %s br: BuildPipeline.BuildResult | t.CPtr = BuildPipeline.run_pipeline( final_ir, final_ir_len, temp_dir, output_dir, module_name, cc_cmd, cc_flags, linker_cmd, linker_flags, linker_output, - includes_binary_dir + includes_binary_dir, linker_static_libs ) if br is not None and br.Success == 1: log.success("编译管线完成")