From 0f4aeeceff06b7b91e200f89143c13827e2c6c22 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 5 Oct 2026 13:38:22 +0800 Subject: [PATCH] =?UTF-8?q?docs(tpv):=20R11=20=E6=B3=A8=E9=87=8A=E4=B8=8E?= =?UTF-8?q?=20docstring=20=E8=8B=B1=E6=96=87=E5=8C=96=E5=B9=B6=E5=8E=8B?= =?UTF-8?q?=E7=BC=A9=E5=88=B0=202=20=E8=A1=8C=E5=86=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按 wiki/新规范:注释只讲作用、不掺杂历史原因、避免超长、尽可能英文(含三引号 __doc__)。全文 79 个 .vp:# 注释 16269 行(中文 13808)与 docstring(中文 1381 处)全部改英文;超 2 行的注释块(2093)与 docstring(397)全部压到 2 行。运行时可见文本未动(fatal_error/VLogger 的中文报错文案、stdio.printf 的横幅格式串)。校验:屏蔽注释与三引号后逐行比对各文件代码骨架,0 漂移。验证:自举重建 153.48s 通过(跨模块内联完成);TPV 顺序回归 77/77 全通过。 --- App/lib/Phase/BuildPipeline.vp | 704 ++----- App/lib/Phase/Phase1/Phase1Main.vp | 359 ++-- App/lib/Phase/Phase1/Phase1Scan.vp | 160 +- App/lib/Phase/Phase1/Phase1Topo.vp | 102 +- App/lib/Phase/Phase1/__init__.vp | 11 +- App/lib/Phase/Phase2/__init__.vp | 497 +++-- App/lib/Projectrans/Config.vp | 268 +-- App/lib/Projectrans/Utils.vp | 98 +- App/lib/Stub/StubGen/Converter.vp | 363 ++-- App/lib/Stub/StubGen/VpiReader.vp | 1698 ++++++---------- App/lib/Stub/StubMerger/StubMergerBuild.vp | 192 +- App/lib/Stub/StubMerger/StubMergerCollect.vp | 74 +- App/lib/Stub/StubMerger/StubMergerIR.vp | 116 +- App/lib/Stub/StubMerger/StubMergerStore.vp | 306 +-- App/lib/Stub/StubMerger/StubMergerTopo.vp | 41 +- App/lib/Stub/StubMerger/__init__.vp | 54 +- App/lib/__init__.vp | 8 +- App/lib/core/CompilerFactory.vp | 16 +- App/lib/core/Gen/AssignGenMixin.vp | 1413 ++++---------- App/lib/core/Gen/AsyncGenMixin.vp | 457 ++--- App/lib/core/Gen/BaseGenMixin.vp | 22 +- App/lib/core/Gen/Call/CallGenBase.vp | 448 ++--- App/lib/core/Gen/Call/CallGenCBuiltin.vp | 511 ++--- App/lib/core/Gen/Call/CallGenCall.vp | 848 +++----- App/lib/core/Gen/Call/CallGenCtor.vp | 614 ++---- App/lib/core/Gen/Call/CallGenDispatch.vp | 881 +++------ App/lib/core/Gen/Call/CallGenFd.vp | 101 +- App/lib/core/Gen/Call/CallGenLeafKit.vp | 458 ++--- App/lib/core/Gen/Call/CallGenLib.vp | 268 +-- App/lib/core/Gen/Call/CallGenMixin.vp | 810 +++----- App/lib/core/Gen/Call/CallGenProp.vp | 199 +- App/lib/core/Gen/Call/CallGenVa.vp | 148 +- App/lib/core/Gen/Call/CallGenVt.vp | 43 +- App/lib/core/Gen/Class/ClassGenBase.vp | 399 ++-- App/lib/core/Gen/Class/ClassGenDef.vp | 430 ++--- App/lib/core/Gen/Class/ClassGenEnum.vp | 530 ++--- App/lib/core/Gen/Class/ClassGenGeneric.vp | 523 ++--- App/lib/core/Gen/Class/ClassGenMixin.vp | 6 +- App/lib/core/Gen/Class/ClassGenVTable.vp | 519 ++--- App/lib/core/Gen/DebugInfoGen.vp | 122 +- App/lib/core/Gen/Expr/ExprGenAttr.vp | 697 +++---- App/lib/core/Gen/Expr/ExprGenBase.vp | 609 ++---- App/lib/core/Gen/Expr/ExprGenCdef.vp | 418 ++-- App/lib/core/Gen/Expr/ExprGenCls.vp | 155 +- App/lib/core/Gen/Expr/ExprGenConst.vp | 132 +- App/lib/core/Gen/Expr/ExprGenFstr.vp | 127 +- App/lib/core/Gen/Expr/ExprGenMixin.vp | 57 +- App/lib/core/Gen/Expr/ExprGenName.vp | 585 ++---- App/lib/core/Gen/Expr/ExprGenOps.vp | 659 +++---- App/lib/core/Gen/Func/FuncGenBase.vp | 159 +- App/lib/core/Gen/Func/FuncGenDef.vp | 574 ++---- App/lib/core/Gen/Func/FuncGenMixin.vp | 369 ++-- App/lib/core/Gen/GenApi.vp | 398 ++-- App/lib/core/Gen/GenTypes.vp | 451 ++--- App/lib/core/Gen/Generator.vp | 16 +- App/lib/core/Gen/ImportsGenMixin.vp | 484 ++--- App/lib/core/Gen/MatchGenMixin.vp | 630 +++--- App/lib/core/Gen/Stmt/StmtGenCifMixin.vp | 875 +++------ App/lib/core/Gen/Stmt/StmtGenDeclMixin.vp | 168 +- App/lib/core/Gen/Stmt/StmtGenMixin.vp | 195 +- App/lib/core/Gen/StructGenMixin.vp | 989 +++------- App/lib/core/Gen/Translator.vp | 550 ++---- App/lib/core/Gen/TranslatorGenMixin.vp | 654 +++---- App/lib/core/Gen/Type/TypeGenBase.vp | 138 +- App/lib/core/Gen/Type/TypeGenCDef.vp | 204 +- App/lib/core/Gen/Type/TypeGenCType.vp | 302 +-- App/lib/core/Gen/Type/TypeGenErr.vp | 161 +- App/lib/core/Gen/Type/TypeGenMixin.vp | 18 +- App/lib/core/Gen/Type/TypeGenResolve.vp | 1817 +++++------------- App/lib/core/Gen/Type/TypeSpec.vp | 552 ++---- App/lib/core/Gen/VarGenMixin.vp | 544 ++---- App/lib/core/Gen/__init__.vp | 6 +- App/lib/core/IncludesScanner.vp | 193 +- App/lib/core/Repl.vp | 1202 ++++-------- App/lib/core/VLogger.vp | 103 +- App/lib/core/__init__.vp | 4 +- App/lib/core/builtin/ViperBuild.vp | 289 +-- App/lib/core/builtin/__init__.vp | 4 +- App/main.vp | 137 +- 79 files changed, 10090 insertions(+), 20352 deletions(-) diff --git a/App/lib/Phase/BuildPipeline.vp b/App/lib/Phase/BuildPipeline.vp index f79a0f9..cec0044 100644 --- a/App/lib/Phase/BuildPipeline.vp +++ b/App/lib/Phase/BuildPipeline.vp @@ -25,48 +25,34 @@ import lib.core.Gen.Generator as GenGenerator import lib.core.Gen.Expr.ExprGenCls as ExprGenCls import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect -# ============================================================ -# BuildPipeline - 编译管线 -# -# 负责将 LLVM IR 编译为可执行文件: -# 1. 写 .ll 文件到 temp 目录 -# 2. 调用 llc 编译 .ll → .obj -# 3. 调用 clang++ 链接 .obj → .exe -# ============================================================ +# BuildPipeline - compile pipeline: write .ll to temp, run llc (.ll -> .obj), +# then link with clang++ (.obj -> .exe). -# 全局 mbuddy 指针 +# global mbuddy pointer _mbuddy: memhub.MemBuddy* -# ASan 模式(--asan):对 .ll 先 opt -passes=asan 插桩;ASan 运行库由 -# project.vpj 的 linker.static_libs / dynamic_libs 显式声明(TPV 不注入) +# ASan mode (--asan): instrument .ll via opt -passes=asan; the ASan runtime is +# declared explicitly in project.vpj's linker.static_libs / dynamic_libs (TPV injects nothing). _asan_mode: bool = 0 -# Traceback 模式(--traceback):强制编入 includes/traceback.vp 并注入启动期 -# Install() 构造器;llc 保留帧指针(-frame-pointer=all);链接 dbghelp; -# 链接成功后导出 .stackmap 地址→源行旁文件。 +# Traceback mode (--traceback): force-build includes/traceback.vp, inject the startup +# Install() ctor, keep frame pointers, link dbghelp, then export .stackmap. _traceback_mode: bool = 0 -# --phase2command xxx.bat:不执行 llc/链接命令,只把 Phase B/B+/C 的 -# llc(含 ASan opt)与 clang 链接命令写入指定 .bat 路径(由用户手动执行)。 -# None = 正常执行(默认)。 +# --phase2command xxx.bat: do not run llc/link; write the llc (incl. ASan opt) and clang +# link commands to the given .bat path for manual execution. None = run normally (default). _phase2_cmd_path: str = None -# bat 文件是否已写入 header(@echo off),首次写入时置 1 +# whether the bat header (@echo off) has been written; set to 1 on first write _phase2_bat_init: int = 0 -# MLIR 降级步骤(单端固定,无后端开关):mlirlite 发射的是 MLIR 文本, -# 而 opt/llc 只接受 LLVM IR,故在两者之前插入: -# mlir-opt -o .mlir.opt (语法/语义校验) -# mlir-translate --mlir-to-llvmir .mlir.opt -o .ll.local (降级回 LLVM IR) -# 降级产物 <...>.ll.local 与输入 .ll 同目录,不占用 .ll 缓存键。 +# MLIR lowering step (fixed, no backend switch): mlirlite emits MLIR but opt/llc accept +# only LLVM IR, so run mlir-opt (validate) then mlir-translate --mlir-to-llvmir first. MLIR_OPT_CMD: str = "mlir-opt" MLIR_TRANSLATE_CMD: str = "mlir-translate" def _ir_sibling_path(ir_path: str, suffix: str) -> str: - """由 <...>.ll 派生同目录邻居路径:去掉末尾 ".ll" 后拼 suffix(如 ".mlir.opt")。 - - 从 _mbuddy 分配(与 build_sliced_path 同池,随 pool reset 回收,无需手动 free)。 - """ + """Derive a sibling path from <...>.ll: strip the .ll suffix and append suffix; mbuddy-allocated.""" if ir_path is None or suffix is None: return None n: sizet = string.strlen(ir_path) if n < 3: return None @@ -78,34 +64,22 @@ def _ir_sibling_path(ir_path: str, suffix: str) -> str: string.strcat(buf, suffix) return buf -# 源代码缓冲区大小(1MB) +# source buffer size (1MB) define SRC_BUF_SIZE = 1048576 # 2^20 = 1MB -# .vpi 存根缓冲区大小(256KB,与 Phase1 一致) -# 编译期常量用 define:模块级同名全局(Phase1 已有 PYI_BUF_SIZE)会各发一份 -# 强定义符号,llvm-link 报 symbol multiply defined。 +# .vpi stub buffer size (256KB, same as Phase1). Use a compile-time define: a module-level +# global with this name would emit a second strong symbol and llvm-link reports multiply defined. define PYI_BUF_SIZE = 262144 -# ============================================================ -# _llc_frame_ptr_flag - llc extra flag for --traceback -# -# Returns " -frame-pointer=all" (leading space so it concatenates into the llc -# flag sequence) while --traceback is on; "" otherwise, keeping the llc command -# byte-identical to the pre-switch behaviour. -# ============================================================ +# _llc_frame_ptr_flag - llc extra flag for --traceback: " -frame-pointer=all" while +# enabled (leading space), "" otherwise so the llc command stays byte-identical. def _llc_frame_ptr_flag() -> str: if _traceback_mode != 0: return " -frame-pointer=all" return "" -# ============================================================ -# _append_traceback_dbghelp - append -ldbghelp for --traceback -# -# traceback.vp's main walk path uses StackWalk64 / SymInitialize / -# SymFunctionTableAccess64 / SymGetModuleBase64 (all in dbghelp); without this -# flag the link fails with undefined symbols. Libraries must follow the objects, -# so this is appended to linker_flags (not prepended). -# ============================================================ +# _append_traceback_dbghelp - append -ldbghelp for --traceback (StackWalk64 etc. live in +# dbghelp); appended so libraries follow the objects, else the link has undefined symbols. def _append_traceback_dbghelp(linker_flags: str) -> str: if _traceback_mode == 0 or linker_flags is None: return linker_flags need: sizet = string.strlen(linker_flags) + 16 @@ -117,21 +91,15 @@ def _append_traceback_dbghelp(linker_flags: str) -> str: return buf -# ============================================================ -# --traceback 调用点旁文件(.vpcall) -# -# 编译器(mlirlite 调试调用点记录 + DebugInfoGen 转达)在翻译每个模块时累积 -# 「F/C」记录文本;Phase1/Phase2 在模块翻译完成、dump 之后调用 WriteCallSidecar -# 写盘并登记路径。WriteStackMap 链接后按登记表读取,结合 llvm-objdump 归一地址。 -# 仅 --traceback 时产生;关闭时不写任何旁文件、不登记。 -# ============================================================ +# --traceback call-site sidecar (.vpcall): the compiler accumulates F/C records per +# module; WriteCallSidecar writes and registers them, WriteStackMap reads them after link. define TB_VPCALL_MAX = 8192 -_tb_vpcall_buf: bytes = None # TB_VPCALL_MAX * 8:每槽一个 char*(stdlib 分配,进程内一次性) +_tb_vpcall_buf: bytes = None # TB_VPCALL_MAX * 8: one char* per slot (stdlib-allocated, one-shot per process) _tb_vpcall_n: int = 0 def _tb_register_vpcall(path: str) -> int: - """把已写出的 .vpcall 路径登记进全局表(供 WriteStackMap 稍后读取)。""" + """Register a written .vpcall path in the global table for WriteStackMap to read.""" global _tb_vpcall_buf, _tb_vpcall_n if path is None: return 0 if _tb_vpcall_buf is None: @@ -150,11 +118,7 @@ def _tb_register_vpcall(path: str) -> int: def WriteCallSidecar(temp_dir: str, sha1: str, text: str) -> int: - """--traceback: 把本模块的调试调用点文本写入 //.vpcall。 - - text 为 None 时跳过(该模块无 define/无调用)。路径随后登记到全局表,交由 - WriteStackMap 读取;本函数不释放路径(进程生命周期内一直有效)。 - """ + """--traceback: write this module's call-site text to //.vpcall; None text skips.""" if temp_dir is None or sha1 is None or text is None: return 0 vpath: str = StubMergerCollect._sliced_path(temp_dir, string.strlen(temp_dir), sha1, "vpcall") if vpath is None: @@ -170,9 +134,7 @@ def WriteCallSidecar(temp_dir: str, sha1: str, text: str) -> int: return _tb_register_vpcall(vpath) -# ============================================================ -# _tb_basename_is_traceback - is this source file includes/traceback.vp -# ============================================================ +# _tb_basename_is_traceback - whether this source file is includes/traceback.vp def _tb_basename_is_traceback(file_path: str) -> int: if file_path is None: return 0 flen: sizet = string.strlen(file_path) @@ -182,29 +144,15 @@ def _tb_basename_is_traceback(file_path: str) -> int: if prev != '/' and prev != '\\': return 0 return 1 if string.strcmp(file_path + (flen - 12), "traceback.vp") == 0 else 0 -# ============================================================ -# TranslateFileGetTrans - 翻译单个文件,返回 Translator 对象(供调用者 dump_ir) -# -# 读取文件 → AST 解析 → .vpi 生成 → LLVM IR 翻译。命名空间隔离:includes 文件宽松模式(全部可见),用户文件严格模式(仅本地+import)。 -# -# Args: mb 内存池;file_path 源文件路径;sha1_val 文件内容的 SHA1 前16字符(用于函数名混淆);current_package 当前文件所属包名(用于解析相对导入),None 表示顶级模块;declare_only 0=全量翻译(默认),1=仅注册 struct/enum/union(不翻译方法体);temp_dir temp 目录路径(非 None 时在翻译前生成 .vpi 存根文件) -# Returns: Translator 对象(None 失败) -# ============================================================ +# TranslateFileGetTrans - translate one file and return a Translator (caller dumps IR). +# File -> AST -> .vpi -> LLVM IR; includes use loose scoping, user files strict (local + imports). def TranslateFileGetTrans(mb: memhub.MemBuddy*, file_path: str, sha1_val: str, current_package: str = None, declare_only: int = 0, temp_dir: str = None, pre_allow_dep_inject: int = 0) -> rawptr[HandlesTranslator.Translator]: - """翻译文件,返回 Translator 对象(调用者负责 dump_ir),None 失败 - - current_package: 当前文件所属包名(用于解析相对导入),None 表示顶级模块 - declare_only: 0=全量翻译(默认),1=仅注册 struct/enum/union(不翻译方法体) - temp_dir: temp 目录路径(非 None 时在翻译前生成 .vpi 存根文件) - pre_allow_dep_inject: 仅 **App 预注册**(Phase A-pre)传 1:声明期也处理导入语句并 - 注入依赖 .vpi,使类字段注解与阶段 A 同上下文(详见 Translator._pre_allow_dep_inject)。 - includes 预注册(Phase A-pre-inc)必须保持 0。 - """ + """Translate one file and return a Translator (caller dumps IR); None on failure.""" if file_path is None: return None f: rawptr[fileio.File] = fileio.File(file_path, fileio.MODE.R) if f.closed: return None - # read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被**静默截断**; - # 这里先按文件大小拒绝, 否则拿截断后的源码去 parse 只会得到莫名其妙的语法错误 + # read_all's capacity includes the terminator (reads cap-1 bytes max) and silently + # truncates longer files; reject by size first, else parsing gets confusing syntax errors. if f.size() >= SRC_BUF_SIZE: VLogger.error(f"源文件超过 {SRC_BUF_SIZE} 字节上限, 拒绝读取: {file_path}", "fileio") f.close() @@ -223,31 +171,27 @@ def TranslateFileGetTrans(mb: memhub.MemBuddy*, file_path: str, sha1_val: str, c ast._lexer_init(lx, src_buf, mb) tokens: ast.Token* = ast.tokenize(lx) tree: ast.AST* = ast.parse_tokens(mb, tokens) - # parse 完成后 AST 树已独立,释放源代码缓冲区(避免 1MB/文件泄漏) + # AST is independent after parse; free the source buffer (avoid a 1MB/file leak) stdlib.free(src_buf) if tree is None: - # fail-fast 定位:以 Python 风格输出解析错误 traceback(仿 fatal_error) - # Traceback (most recent call last): - # File "", line N - # - # SyntaxError: + # fail-fast location: print a Python-style parse-error traceback (like fatal_error): + # Traceback (most recent call last) / File "", line N / SyntaxError: GenApi.dump_parse_traceback(file_path) err_line: int = ast.get_parse_err_line() err_col: int = ast.get_parse_err_col() VLogger.error(f"解析失败: {file_path} (line {err_line} col {err_col})", "PARSE") return None - # 生成 .vpi 存根文件(从 AST 直接生成,在 tr.translate 之前,与 Phase1 一致) - # .vpi 供其他模块跨模块查询函数签名和 CTypedef 定义 + # Generate the .vpi stub from the AST before tr.translate (same as Phase1); it lets + # other modules query function signatures and CTypedef definitions. if temp_dir is not None: pyi_buf: bytes = stdlib.malloc(PYI_BUF_SIZE) if pyi_buf is not None: pyi_buf[0] = 0 - # rel_path 传 file_path(本文件绝对路径):vpi 生成期间 fatal_error - # 的报错归因需指向本源文件(Phase A 预注册/翻译时 file_path 已可用), - # 且绝对路径可被 fatal_error 的 _read_source_line 直接打开打印源码行。 + # Pass file_path as rel_path: vpi-time fatal_error must blame this source file, + # and the absolute path lets _read_source_line open and print the source line. pyi_pos: sizet = StubConverter._GeneratePyiFromAst(mb, tree, file_path, pyi_buf, PYI_BUF_SIZE) if pyi_pos > 0: - # 写入 {sha1}.vpi 到 temp_dir(切片路径) + # write {sha1}.vpi into temp_dir (sliced path) td_len_vpi: sizet = len(temp_dir) pyi_path: str = StubMergerCollect._sliced_path(temp_dir, td_len_vpi, sha1_val, "vpi") if pyi_path is not None: @@ -263,12 +207,11 @@ def TranslateFileGetTrans(mb: memhub.MemBuddy*, file_path: str, sha1_val: str, c tr.ModuleSha1 = sha1_val tr.CurrentPackage = current_package tr._declare_only = declare_only - # App 预注册(Phase A-pre)专用开关:置 1 时声明期也做导入处理 + 依赖 vpi 注入 - # (详见 Translator._pre_allow_dep_inject)。includes 预注册(Phase A-pre-inc) - # 必须传 0,否则依赖环自锁(见 Translator 内 _declare_only==0 门处注释)。 + # App pre-registration (Phase A-pre) switch: when 1, import handling + dependency vpi + # injection also run in the declare phase. includes pre-registration must pass 0. tr._pre_allow_dep_inject = pre_allow_dep_inject - # P2: 复用全局 gen 单例(App/main.vp 创建;HandlesType 等旧模块函数经 - # 延迟 import 委托到 gen 方法)。单例未注册时兜底创建(防御直接调用路径)。 + # P2: reuse the global gen singleton (created by App/main.vp); create it as a fallback + # when unregistered (defensive for direct-call paths). gen: rawptr[GenGenerator.Generator] = GenGenerator._g_gen if gen is None: gen = mb.alloc(GenGenerator.Generator.__sizeof__()) @@ -278,39 +221,36 @@ def TranslateFileGetTrans(mb: memhub.MemBuddy*, file_path: str, sha1_val: str, c gen.__before_init__() gen.__init__(mb) GenGenerator._g_gen = gen - # P2-P5: 注入各 Handles 模块级单例(委托函数经本地 _g_gen 访问, - # 避免函数体 import 包模块与类/模块名歧义) + # P2-P5: inject the Handles module-level singletons (delegated functions use local + # _g_gen to avoid package/class name ambiguity from in-body imports). GenApi._g_gen = gen - # P6: 注入语句/赋值/变量/导入/翻译驱动模块的单例 - # 设置当前文件名(供报错使用) + # P6: inject singletons for statement/assign/var/import/translate-driver modules + # set the current file name (for error reporting) GenApi.set_current_file_type(file_path) - # --traceback: mirror the switch (BuildPipeline._traceback_mode) into the - # Gen-layer DWARF emitter. 0 => every DI entry point is a no-op and the IR - # stays byte-identical to the pre-DWARF output. + # --traceback: mirror the switch into the Gen-layer DWARF emitter. 0 => every DI entry + # point is a no-op and the IR stays byte-identical to the pre-DWARF output. DebugInfoGen.SetEnabled(_traceback_mode) - # 同步到 HandlesImports(import 时 SHA1 检查报错需要当前文件名,循环依赖无法直接读 HandlesType) + # also set it for HandlesImports (import SHA1 checks need it; a circular dep blocks reading HandlesType) GenApi.set_current_file_imports(file_path) - # 清空调用栈(每个文件翻译开始前重置) + # clear the call stack (reset before each file translation) GenApi.clear_trace() - # 模块切换:清空 CDefine 常量表,确保每个模块的 CDefine 常量正确隔离 + # module switch: clear the CDefine constant table to isolate each module's constants GenApi.clear_cdefine_constants() - # 模块切换:清空 CTypedef 表,确保每个模块的 CTypedef 正确隔离 + # module switch: clear the CTypedef table to isolate each module's typedefs GenApi.clear_ctypedefs() - # 命名空间隔离:includes 文件宽松模式(全部可见),用户文件严格模式(仅本地+import) + # namespace isolation: includes loose (all visible), user files strict (local + imports only) strict_mode: int = 1 if string.strstr(file_path, "includes") is not None: strict_mode = 0 GenApi.reset_visible_structs(mb, strict_mode) - # 提取 docstring(预注册阶段所有 App/includes 文件都被翻译,保证跨模块 __doc__ 在正式翻译前已登记;查重幂等) - # 必须在 tr.translate 之前扫描:translate 期间 mb 池大量分配 IR 节点,AST 节点所在池块可能被复用覆盖(ASan 下遍历 tree 会崩溃)。 + # Extract docstrings before tr.translate (pre-registration registers cross-module __doc__; + # must run before translate recycles the AST pool blocks, which crashes under ASan). if gen is not None: gen.ExprGenCls.extract_module_docstrings(tree, sha1_val) ret: int = tr.translate(tree) if ret != 0: VLogger.error(f"文件翻译失败: {file_path}", "Translate") return None - # --traceback: when this file is includes/traceback.vp, inject the startup - # Install() ctor into its IR (before the caller dumps stub/text.ll). The - # injector lives in the Gen layer, which already depends on mlirlite, so - # BuildPipeline gains no cross-module mlirlite symbol dependency. + # --traceback: for includes/traceback.vp, inject the startup Install() ctor into its IR. + # The injector lives in the Gen layer, so no new cross-module mlirlite dependency. if _traceback_mode != 0 and _tb_basename_is_traceback(file_path) != 0: HandlesTranslator.InjectTracebackInstallCtor(tr) return tr @@ -330,32 +270,24 @@ class BuildResult: def ensure_dir(path: str) -> int: - """递归创建目录(类似 mkdir -p),目录已存在视为成功 - - Args: - path: 目录路径(支持 / 或 \\ 分隔符) - - Returns: - 0 成功(包括目录已存在),非 0 失败 - """ + """Recursively create a directory (like mkdir -p); an existing directory counts as success.""" if path is None: return 1 path_len: sizet = len(path) if path_len == 0: return 0 - # 热点快速路径(PROF:ensure_dir 占 4.5%,22256 次调用每次逐级 CreateDirectoryA): - # 末级目录已存在则直接成功返回,省去 1 次分配 + N+1 次 CreateDirectoryA 系统调用。 - # 语义不变:原实现把"目录已存在"(CreateDirectoryA 失败)也视为成功。 + # Hot path (PROF: ensure_dir was 4.5%): if the final dir exists, return success early, + # saving an alloc + N+1 CreateDirectoryA calls. Semantics unchanged (exists == success). attr: ULONG = w32.win32file.GetFileAttributesA(path) if attr != 0xFFFFFFFF and (attr & w32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0: return 0 - # 复制路径到可写缓冲区(逐级截断用) + # copy the path into a writable buffer (for stepwise truncation) buf: bytes = _mbuddy.alloc(path_len + 1) if buf is None: return 1 string.strcpy(buf, path) - # 遇到分隔符时临时截断,创建每一层目录 - # CreateDirectoryA 在目录已存在时返回 0(失败),忽略即可 + # Temporarily truncate at each separator and create every directory level. + # CreateDirectoryA returns 0 when the dir exists (failure); ignore it. plen: int = path_len for i in range(plen): ch: int = buf[i] @@ -365,21 +297,15 @@ def ensure_dir(path: str) -> int: w32.win32file.CreateDirectoryA(buf, None) buf[i] = saved - # 创建最终目录 + # create the final directory w32.win32file.CreateDirectoryA(buf, None) return 0 -# ============================================================ -# build_sliced_path - 构建切片路径并确保目录存在(自动调用 ensure_dir) -# -# 按 Config.Sha1SliceLevel 分散到 SHA1 前缀子目录:level=0 → {base_dir}/{sha1}.{ext};level=1 → {base_dir}/b7/{sha1}.{ext};level=2 → {base_dir}/b7/90/{sha1}.{ext} -# -# Args: base_dir 基础目录(如 temp_dir);sha1 SHA1 字符串;ext 文件扩展名(如 "stub.ll") -# Returns: 完整文件路径(mbuddy 分配),None 失败 -# ============================================================ +# build_sliced_path - build a sliced path and ensure its directory exists. Fans out into +# SHA1-prefix subdirs per Config.Sha1SliceLevel; returns the full path (mbuddy) or None. def build_sliced_path(base_dir: str, sha1: str, ext: str) -> str: - """构建切片路径并确保目录存在""" + """Build a sliced path and ensure its directory exists.""" if base_dir is None or sha1 is None or ext is None: return None subdir: str = Config.slice_subdir(sha1, Config.Sha1SliceLevel) @@ -394,14 +320,14 @@ def build_sliced_path(base_dir: str, sha1: str, ext: str) -> str: if dir_path is not None: viperlib.snprintf(dir_path, base_len + sub_len + 2, "%s/%s", base_dir, subdir) ensure_dir(dir_path) - # 文件路径: {base_dir}/{subdir}/{sha1}.{ext} + # file path: {base_dir}/{subdir}/{sha1}.{ext} path_len: sizet = base_len + sub_len + sha1_len + ext_len + 4 path: str = _mbuddy.alloc(path_len) if path is None: return None viperlib.snprintf(path, path_len, "%s/%s/%s.%s", base_dir, subdir, sha1, ext) return path else: - # 无切片: {base_dir}/{sha1}.{ext} + # no slicing: {base_dir}/{sha1}.{ext} path_len = base_len + sha1_len + ext_len + 3 path = _mbuddy.alloc(path_len) if path is None: return None @@ -410,24 +336,14 @@ def build_sliced_path(base_dir: str, sha1: str, ext: str) -> str: def write_ir_to_file(ir_buf: bytes, ir_len: sizet, output_dir: str, module_name: str) -> int: - """将 IR 缓冲区写入 .ll 文件 - - Args: - ir_buf: IR 文本缓冲区 - ir_len: IR 文本长度 - output_dir: 输出目录(temp 或 output) - module_name: 模块名(SHA1) - - Returns: - 0 成功,非 0 失败 - """ + """Write the IR buffer to a .ll file; 0 on success, non-zero on failure.""" if ir_buf is None or output_dir is None or module_name is None: return 1 - # 构造切片路径: output_dir/{sha1前缀}/{module_name}.ll + # build sliced path: output_dir/{sha1 prefix}/{module_name}.ll path: str = build_sliced_path(output_dir, module_name, "ll") if path is None: return 1 - # 打开文件写入 + # open the file for writing f: rawptr[fileio.File] = fileio.File(path, fileio.MODE.W) if f.closed: return 1 @@ -438,31 +354,19 @@ def write_ir_to_file(ir_buf: bytes, ir_len: sizet, output_dir: str, module_name: def compile_ll_to_obj(ir_path: str, output_dir: str, module_name: str, cc_cmd: str, cc_flags: str) -> int: - """调用 llc 将 .ll 编译为 .obj - - Args: - ir_path: .ll 文件路径 - output_dir: 输出目录 - module_name: 模块名(SHA1,用于生成 .obj 文件名) - cc_cmd: 编译器命令(如 "llc") - cc_flags: 编译器参数(如 "-filetype=obj -relocation-model=pic") - - Returns: - 0 成功,非 0 失败 - """ + """Compile a .ll file to .obj via llc; 0 on success, non-zero on failure.""" if ir_path is None or cc_cmd is None or output_dir is None or module_name is None: return 1 - # 构造切片 .obj 路径: output_dir/{sha1前缀}/{module_name}.obj + # build sliced .obj path: output_dir/{sha1 prefix}/{module_name}.obj obj_path: str = build_sliced_path(output_dir, module_name, "obj") if obj_path is None: return 1 - # ASan 模式(_asan_mode=1):先 opt -passes=asan 插桩再 llc。 - # -asan-globals=false 关闭全局插桩:TPV 对全局变量(如各模块 _g_gen) - # 用直接地址寻址,ASan 全局 redzone 重定位后引用错位会读到垃圾指针。 + # ASan mode: run opt -passes=asan before llc. -asan-globals=false disables global + # instrumentation: TPV addresses globals directly and ASan redzones would shift them. compile_input: str = ir_path - # ---- MLIR 降级(单端固定,无开关):mlir-opt(校验)→ mlir-translate ---- - # 必须在 ASan opt 与 llc 之前,并把 compile_input 替换为 <...>.ll.local。 + # ---- MLIR lowering (fixed, no switch): mlir-opt (validate) -> mlir-translate ---- + # Must run before ASan opt and llc, replacing compile_input with <...>.ll.local. ll_local_path: str = _ir_sibling_path(ir_path, ".ll.local") mlir_opt_out: str = _ir_sibling_path(ir_path, ".mlir.opt") if ll_local_path is None or mlir_opt_out is None: return 1 @@ -475,7 +379,7 @@ def compile_ll_to_obj(ir_path: str, output_dir: str, module_name: str, cc_cmd: s if cmd_mt is None: return 1 viperlib.snprintf(cmd_mt, mt_len, "%s --mlir-to-llvmir %s -o %s", MLIR_TRANSLATE_CMD, mlir_opt_out, ll_local_path) if _phase2_cmd_path is not None: - # 只写命令(须排在下面的 ASan/llc 命令之前) + # write the command only (must precede the ASan/llc commands below) _phase2_write_cmd(cmd_mo) _phase2_write_cmd(cmd_mt) else: @@ -489,7 +393,7 @@ def compile_ll_to_obj(ir_path: str, output_dir: str, module_name: str, cc_cmd: s return 1 compile_input = ll_local_path - # --phase2command 模式:不执行 opt/llc,把 opt(ASan)+ llc 命令写入 bat + # --phase2command mode: do not run opt/llc; write the opt (ASan) + llc commands to the bat if _phase2_cmd_path is not None: p2_opt_cmd: str = None if _asan_mode != 0: @@ -522,15 +426,15 @@ def compile_ll_to_obj(ir_path: str, output_dir: str, module_name: str, cc_cmd: s return 1 compile_input = asan_path - # 构造命令: llc -filetype=obj -o {obj_path} ir_path + # command: llc -filetype=obj -o {obj_path} ir_path llc_fp2: str = _llc_frame_ptr_flag() cmd_len: sizet = len(cc_cmd) + len(cc_flags) + len(llc_fp2) + len(obj_path) + len(compile_input) + 64 cmd: bytes = _mbuddy.alloc(cmd_len) if cmd is None: return 1 viperlib.snprintf(cmd, cmd_len, "%s %s%s -o %s %s", cc_cmd, cc_flags, llc_fp2, obj_path, compile_input) - # 用 CreateProcessA 直启 llc(避开 cmd/c 在 asan 下挂起),子进程继承父控制台: - # stdout|stderr 直接显示;失败时打印模块 sha1 + 退出码 + 完整命令(退出码同步返回) + # Launch llc directly via CreateProcessA (avoids cmd/c hanging under asan); the child + # inherits the console. On failure, log module sha1 + exit code + full command. sys_ret: int = _run_cmd_wait(cmd) if sys_ret != 0: VLogger.error(f"编译失败 (module={module_name}, exit={sys_ret}, cmd={cmd})", "LLC") @@ -538,22 +442,12 @@ def compile_ll_to_obj(ir_path: str, output_dir: str, module_name: str, cc_cmd: s return 0 -# ============================================================ -# llc 并行编译(线程异步,Phase B 多模块同时跑 llc) -# -# 线程安全约束: -# - LlcTask 及命令串一律 stdlib.malloc:主池 mb 会被 reset_to(mark2) 回收,线程运行期间字符串悬空 → 概率崩溃 -# - worker 只调 _run_cmd_wait(内部仅 stdlib.malloc/CreateProcessA/GetLastError/CloseHandle),不做任何池分配(MemBuddy 分配非线程安全) -# - 依赖 CreateProcessA + Win32 句柄接口的多线程安全性 -# ============================================================ -# llc 并行最大线程数(参考 TPC Phase2Translator n_workers=os.cpu_count(),本机 16 核) +# llc parallel compile (Phase B, async threads): tasks/commands use stdlib.malloc (main pool mb +# gets reset); the worker never allocates from MemBuddy. Max threads = os.cpu_count() (16 here). define LLC_MAX_THREADS = 16 -# LlcTask.FailStage:worker 写"失败发生在哪一步" -# 0 = 未失败/成功;1 = mlir-opt 校验;2 = mlir-translate 降级;3 = ASan opt 插桩;4 = llc -# 此前 worker 只写 Result/ExitCode,主线程只能按 LlcCmd 打印 ⇒ MLIR 后端下 -# mlir-opt/mlir-translate 失败也被误报成 "llc 编译失败"(现场:.ll 里是 LLVM IR -# 时 mlir-opt 报 "unexpected character",日志却指向 llc,定位被带偏)。 +# LlcTask.FailStage: which step failed (0=none, 1=mlir-opt, 2=mlir-translate, 3=ASan opt, +# 4=llc). Without this the main thread misreported mlir-opt/translate failures as llc failures. define LLC_STAGE_NONE = 0 define LLC_STAGE_MLIR_OPT = 1 define LLC_STAGE_MLIR_TRANSLATE = 2 @@ -562,39 +456,38 @@ define LLC_STAGE_LLC = 4 class LlcTask: - """llc 异步任务:命令串(stdlib.malloc)+ 模块 SHA1 + 结果""" - OptCmd: bytes # ASan 插桩命令(非 ASan 为 None) - LlcCmd: bytes # llc 命令 - ModuleSha1: bytes # 模块 SHA1(报错用) - Result: int # worker 写:0 成功,非 0 失败 - # ExitCode 追加在结尾:既有字段偏移不变;LlcTask.__sizeof__() 随之增大, - # 所有构造/初始化点均按 __sizeof__() 算首址并对整条 memset,无需改偏移。 - ExitCode: int # worker 写:失败命令退出码(成功为 0) - # MLIR 后端降级命令(非 MLIR 为 None);同样追加在结尾,不影响既有偏移。 - MlirOptCmd: bytes # mlir-opt 校验命令 - MlirTranslateCmd: bytes # mlir-translate 降级命令 - # 失败阶段(LLC_STAGE_*,见上方常量);追加在结尾,既有偏移不变。 - FailStage: int # worker 写:失败发生在哪一步 + """Async llc task: command strings (stdlib.malloc), module SHA1 and result.""" + OptCmd: bytes # ASan instrumentation command (None if not ASan) + LlcCmd: bytes # llc command + ModuleSha1: bytes # module SHA1 (for error reporting) + Result: int # worker-written: 0 success, non-zero failure + # ExitCode is appended at the end so existing offsets are unchanged; all construction + # sites use __sizeof__() and memset the whole record, so offsets need no update. + ExitCode: int # worker-written: exit code of the failing command (0 on success) + # MLIR-backend lowering commands (None if not MLIR); also appended at the end. + MlirOptCmd: bytes # mlir-opt validation command + MlirTranslateCmd: bytes # mlir-translate lowering command + # failure stage (LLC_STAGE_*, see the constants above); appended at the end. + FailStage: int # worker-written: which step failed def init_llc_task(task: LlcTask*, ir_path: str, output_dir: str, module_name: str, cc_cmd: str, cc_flags: str) -> int: - """填充 llc 任务(全部 stdlib.malloc,线程运行期不被回收),0 成功""" + """Fill an llc task (all stdlib.malloc, kept alive for the thread's run); 0 on success.""" if task is None or ir_path is None or output_dir is None or module_name is None: return 1 string.memset(task, 0, LlcTask.__sizeof__()) task.Result = -1 obj_path: str = build_sliced_path(output_dir, module_name, "obj") if obj_path is None: return 1 - # 模块 SHA1 + # module SHA1 sl: sizet = len(module_name) task.ModuleSha1 = stdlib.malloc(sl + 1) if task.ModuleSha1 is None: return 1 string.strcpy(task.ModuleSha1, module_name) - # llc 输入选择:ASan 模式必须喂插桩后的 .asan.ll,与串行路径 - # compile_ll_to_obj 的 compile_input 同口径(否则 opt 生成了插桩产物 - # 却被 llc 忽略,App 侧 obj 里 0 处 __asan,内存排查半盲) + # llc input choice: ASan mode must feed the instrumented .asan.ll, same as the serial + # compile_ll_to_obj path (else llc ignores the instrumentation and ASan is half-blind). compile_input: str = ir_path - # MLIR 降级(单端固定,无开关):worker 先跑 mlir-opt(校验)→ mlir-translate - # (降级回 LLVM IR),之后的 ASan OptCmd / LlcCmd 输入必须是 <...>.ll.local。 + # MLIR lowering (fixed): the worker runs mlir-opt (validate) -> mlir-translate first, so + # the later ASan OptCmd / LlcCmd input must be <...>.ll.local. ll_local_path: str = _ir_sibling_path(ir_path, ".ll.local") mlir_opt_out: str = _ir_sibling_path(ir_path, ".mlir.opt") if ll_local_path is None or mlir_opt_out is None: return 1 @@ -607,8 +500,8 @@ def init_llc_task(task: LlcTask*, ir_path: str, output_dir: str, module_name: st if task.MlirTranslateCmd is None: return 1 viperlib.snprintf(task.MlirTranslateCmd, mt_len, "%s --mlir-to-llvmir %s -o %s", MLIR_TRANSLATE_CMD, mlir_opt_out, ll_local_path) compile_input = ll_local_path - # ASan 模式:opt 插桩命令(worker 先跑 MLIR 降级/opt 再跑 llc,故 LlcCmd 的输入 - # 必须指向 asan_path;失败即置 Result) + # ASan mode: opt instrumentation command (the worker runs MLIR lowering/opt before llc, + # so LlcCmd's input must point at asan_path; failure sets Result). if _asan_mode != 0: asan_path: str = build_sliced_path(output_dir, module_name, "asan.ll") if asan_path is None: return 1 @@ -617,7 +510,7 @@ def init_llc_task(task: LlcTask*, ir_path: str, output_dir: str, module_name: st if task.OptCmd is None: return 1 viperlib.snprintf(task.OptCmd, opt_len, "opt -passes=forceattrs,asan -force-attribute=sanitize_address" " -asan-globals=false -o %s %s", asan_path, compile_input) compile_input = asan_path - # llc 命令(输入 = compile_input:非 ASan 为 .ll,ASan 为 .asan.ll) + # llc command (input = compile_input: .ll normally, .asan.ll under ASan) llc_fp3: str = _llc_frame_ptr_flag() cmd_len: sizet = len(cc_cmd) + len(cc_flags) + len(llc_fp3) + len(obj_path) + len(compile_input) + 64 task.LlcCmd = stdlib.malloc(cmd_len) @@ -627,8 +520,7 @@ def init_llc_task(task: LlcTask*, ir_path: str, output_dir: str, module_name: st def free_llc_task(task: LlcTask*): - """释放任务的命令串;记录本身是任务数组的连续槽(非独立 malloc), - 由调用方对数组整体 stdlib.free(此前对槽 free 导致 double-free)""" + """Free the task's command strings; the record itself is a slot in the task array.""" if task is None: return if task.OptCmd is not None: stdlib.free(task.OptCmd) @@ -648,11 +540,7 @@ def free_llc_task(task: LlcTask*): def _phase2_write_cmd(cmd: str) -> None: - """--phase2command 模式:把命令追加写入 bat(不执行) - - 首次调用写入 header(@echo off + 说明),后续命令以 \r\n 分行追加, - 与 cmd.exe 批处理格式一致。bat 路径由模块级 _phase2_cmd_path 指定。 - """ + """--phase2command mode: append the command to the bat file without executing it.""" global _phase2_bat_init if _phase2_cmd_path is None or cmd is None: return f: rawptr[fileio.File] = fileio.File(_phase2_cmd_path, fileio.MODE.A) @@ -671,15 +559,7 @@ def _phase2_write_cmd(cmd: str) -> None: def _run_cmd_wait(cmd: str) -> int: - """CreateProcessA 同步执行命令并等待退出,返回退出码。 - - 替代 stdlib.system:Windows asan 环境下 cmd /c 经 CreateProcess 受 - RTL allocator hook 干扰可能挂起(LLVM issue #111166 同类,ClassArrayTest - llc 固定卡死根因);CreateProcessA 直接执行目标程序并继承父进程控制台 - (stdout/stderr 直接显示,同 system 语义)。全 stdlib.malloc/栈分配, - worker 线程安全。CreateProcessA 返回 0 时立即取 GetLastError 并以 - VLogger.warning 打印(外部命令未启动)。 - """ + """Run a command synchronously via CreateProcessA and return its exit code.""" if cmd is None: return -1 cl_len: sizet = len(cmd) cmd_line: bytes = stdlib.malloc(cl_len + 1) @@ -688,10 +568,8 @@ def _run_cmd_wait(cmd: str) -> int: sa: w32.win32base.SECURITY_ATTRIBUTES = w32.win32base.SECURITY_ATTRIBUTES() si: win32process.STARTUPINFOA = win32process.STARTUPINFOA() pi: win32process.PROCESS_INFORMATION = win32process.PROCESS_INFORMATION() - # T() 构造仅 alloca 不 memset:STARTUPINFOA 未设字段(dwFlags 等)全为 - # 栈垃圾,多线程并发调用时垃圾随机 → dwFlags 垃圾位让 CreateProcessA - # 用垃圾句柄作子进程 stdio → 子进程行为不定/父进程线程崩溃(ClassArrayTest - # e5fb worker 崩溃、edd8 正常的原因)。si/sa/pi 全部显式清零。 + # T() only allocas, no memset: unset STARTUPINFOA fields are stack garbage, so concurrent + # threads get random dwFlags and CreateProcessA uses junk stdio handles. Clear si/sa/pi. string.memset(t.CPtr(&si), 0, win32process.STARTUPINFOA.__sizeof__()) string.memset(t.CPtr(&sa), 0, w32.win32base.SECURITY_ATTRIBUTES.__sizeof__()) string.memset(t.CPtr(&pi), 0, win32process.PROCESS_INFORMATION.__sizeof__()) @@ -699,13 +577,11 @@ def _run_cmd_wait(cmd: str) -> int: sa.lpSecurityDescriptor = t.CPtr(0) sa.bInheritHandle = 1 si.cb = win32process.STARTUPINFOA.__sizeof__() - # create_flags=0:子进程继承父控制台(输出直接显示,同 system 语义); - # si.dwFlags 保持 memset 后的 0(不置 STARTF_USESTDHANDLES、不设 std 句柄) + # create_flags=0: the child inherits the parent console (output shown, like system); + # si.dwFlags stays 0 after memset (no STARTF_USESTDHANDLES, no std handles). ok: int = win32process.CreateProcessA(t.CPtr(0), cmd_line, t.CPtr(0), t.CPtr(0), 1, 0, t.CPtr(0), t.CPtr(0), t.CPtr(&si), t.CPtr(&pi)) - # CreateProcessA 失败诊断:GetLastError 必须在返回后立即取(此后 stdlib.free/ - # CloseHandle/SetConsoleTextAttribute 等都会覆盖它)。用 warning 不用 error—— - # VLogger.error 会 sys.exit(1),而本函数可能在 worker 线程里执行;warning 只走 - # CRT printf + 控制台着色,不做 MemBuddy 池分配,满足 worker 线程安全约束。 + # CreateProcessA failure diagnostics: read GetLastError immediately (later calls overwrite + # it). Use warning, not error (error would sys.exit in a worker thread). if ok == 0: cp_err: uint32 = w32.win32base.GetLastError() VLogger.warning(f"CreateProcessA 失败 (GetLastError={cp_err}) 外部命令未启动: cmd={cmd}", "LLC") @@ -720,12 +596,10 @@ def _run_cmd_wait(cmd: str) -> int: def _llc_task_worker(arg: t.CPtr) -> t.CPtr: - """llc 线程入口:只调 _run_cmd_wait(),不做任何池分配(线程安全) - Result 仅在命令链执行完后写入(初始 -1;join 超时保持 -1 → 报失败); - ExitCode 记录失败命令的退出码,供主线程打印""" + """llc thread entry: only calls _run_cmd_wait, no pool allocation (thread-safe).""" task: LlcTask* = (LlcTask*)(arg) if task is None: return t.CPtr(0) - # MLIR 后端:先校验(mlir-opt)→ 降级(mlir-translate),再 ASan opt → llc + # MLIR backend: validate (mlir-opt) -> lower (mlir-translate), then ASan opt -> llc if task.MlirOptCmd is not None: mo_rc: int = _run_cmd_wait(task.MlirOptCmd) if mo_rc != 0: @@ -759,13 +633,9 @@ def _llc_task_worker(arg: t.CPtr) -> t.CPtr: def run_llc_parallel(tasks: bytes, task_count: int, max_threads: int) -> int: - """并行执行 llc:分批 max_threads 个线程,全部 join 后检查结果 - - tasks: LlcTask 记录连续数组(stdlib.malloc),每个记录由 init_llc_task 填充。 - Returns: 0 全部成功;非 0 有失败(已打印失败模块与命令) - """ + """Run llc in parallel: batches of max_threads threads, join all then check results.""" if tasks is None or task_count <= 0: return 0 - # --phase2command 模式:不 spawn 线程,把 opt(ASan)+ llc 命令全部写入 bat + # --phase2command mode: do not spawn threads; write all opt (ASan) + llc commands to the bat if _phase2_cmd_path is not None: for pi2 in range(task_count): tsk_p: LlcTask* = (LlcTask*)(uint64(tasks) + sizet(pi2) * LlcTask.__sizeof__()) @@ -777,39 +647,37 @@ def run_llc_parallel(tasks: bytes, task_count: int, max_threads: int) -> int: VLogger.info(f"phase2command 模式:{task_count} 条 llc 命令已写入 {_phase2_cmd_path}", "phase2cmd") return 0 if max_threads <= 0: max_threads = 1 - # 线程句柄数组:list[str](str=bytes=i8*,elem_size=8 显式)存句柄值;list[t.CPtr] 特化下 append 会读零页崩溃(原因待查),list[str] 全库验证稳定。 - # start() 后立即取 handle 存入,join/close 直接对句柄操作,不依赖 Thread 对象存续。 + # Thread-handle array: list[str] (str=bytes=i8*, explicit elem_size=8) stores handles; + # list[t.CPtr] append crashes reading page zero. Grab the handle right after start(). handles: list[str]* = list[str](_mbuddy, 8) if handles is None: VLogger.error("run_llc_parallel: 线程句柄数组分配失败", "PhaseB") return 1 slot_size: sizet = LlcTask.__sizeof__() ret: int = 0 - # 分批执行:batch_start 按 max_threads 步进(每批 spawn+join+close 一批句柄)。 + # Batched execution: batch_start steps by max_threads (each batch spawn+join+close). for batch_start in range(0, task_count, max_threads): batch_n: int = max_threads if batch_start + batch_n > task_count: batch_n = task_count - batch_start - # 本批 spawn + # spawn this batch for j in range(batch_n): tsk: LlcTask* = (LlcTask*)(uint64(tasks) + sizet(batch_start + j) * slot_size) th: rawptr[vthreading.Thread] = vthreading.Thread(_llc_task_worker, t.CPtr(tsk)) th.start() - # start() 后立即取出句柄(Thread 对象栈地址可能被后续构造复用, - # join 时再读 self.handle 可能已被 close 清空/覆盖) + # Read the handle right after start() (the Thread object's stack slot may be + # reused; reading self.handle at join could see a cleared/overwritten value). handles.append(t.CPtr(th.handle)) for j in range(batch_n): h_j: t.CPtr = t.CPtr(handles.get(batch_start + j)) wr: uint32 = win32sync.WaitForSingleObject(h_j, 600000) if wr != 0: VLogger.error("llc 线程 join 超时/失败(600 秒未完成)", "PhaseB") w32.win32base.CloseHandle(h_j) - # 本批结果检查 + # check this batch's results for j in range(batch_n): tsk2: LlcTask* = (LlcTask*)(uint64(tasks) + sizet(batch_start + j) * slot_size) if tsk2 is not None and tsk2.Result != 0: - # 失败:按 FailStage 定位到**具体哪一步**并打印对应该步的命令。 - # 此前一律打印 LlcCmd + "llc 编译失败":MLIR 后端下 mlir-opt / - # mlir-translate 失败(如 .ll 里其实是 LLVM IR 时 mlir-opt 报 - # "unexpected character")被误指到 llc,定位被带偏。 + # Failure: use FailStage to pinpoint the step and print that step's command. + # Previously everything printed the LlcCmd as "llc failed", misdirecting MLIR failures. st: int = tsk2.FailStage stage_name: str = "未知阶段(worker 未跑完)" stage_cmd: bytes = tsk2.LlcCmd @@ -831,10 +699,7 @@ def run_llc_parallel(tasks: bytes, task_count: int, max_threads: int) -> int: def _CollectObjFilesImpl(base_dir: str, bd_len: sizet, out_buf: bytes, out_pos: sizet, out_size: sizet) -> sizet: - """递归扫描 base_dir,收集 .obj 文件路径到 out_buf,返回新的 out_pos - - 支持切片子目录(如 dir/43/{sha1}.obj)。 - """ + """Recursively scan base_dir, collect .obj paths into out_buf, return the new out_pos.""" find_data: w32.win32file.WIN32_FIND_DATAA* = _mbuddy.alloc(w32.win32file.WIN32_FIND_DATAA.__sizeof__()) if find_data is None: VLogger.warning("_CollectObjFilesImpl 分配 find_data 失败,少收集 .obj", "collect") @@ -858,7 +723,7 @@ def _CollectObjFilesImpl(base_dir: str, bd_len: sizet, out_buf: bytes, out_pos: if fname is not None: fname_len: sizet = len(fname) if fname_len > 0: - # 跳过 . 和 .. + # skip . and .. is_dot: int = 0 if fname_len == 1 and fname[0] == '.': is_dot = 1 elif fname_len == 2 and fname[0] == '.' and fname[1] == '.': is_dot = 1 @@ -867,7 +732,7 @@ def _CollectObjFilesImpl(base_dir: str, bd_len: sizet, out_buf: bytes, out_pos: is_dir: int = 0 if (attrs & w32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0: is_dir = 1 if is_dir != 0: - # 递归扫描子目录 + # recurse into the subdirectory sub_dir: bytes = _mbuddy.alloc(bd_len + fname_len + 2) if sub_dir is not None: viperlib.snprintf(sub_dir, bd_len + fname_len + 2, "%s/%s", base_dir, fname) @@ -875,7 +740,7 @@ def _CollectObjFilesImpl(base_dir: str, bd_len: sizet, out_buf: bytes, out_pos: out_pos = _CollectObjFilesImpl(sub_dir, sub_len, out_buf, out_pos, out_size) _mbuddy.free(sub_dir) else: - # 检查是否是 .obj 文件 + # check whether this is a .obj file if fname_len > 4 and fname[fname_len - 4] == '.' and fname[fname_len - 3] == 'o' and fname[fname_len - 2] == 'b' and fname[fname_len - 1] == 'j': need: sizet = bd_len + 1 + fname_len + 2 if out_pos + need >= out_size: break @@ -892,18 +757,7 @@ def _CollectObjFilesImpl(base_dir: str, bd_len: sizet, out_buf: bytes, out_pos: def collect_obj_files(includes_binary_dir: str, out_buf: bytes, out_size: sizet) -> sizet: - """递归扫描 includes_binary_dir 目录,收集所有 .obj 文件路径到 out_buf - - 支持切片子目录(如 includes_binary_dir/43/{sha1}.obj)。 - - Args: - includes_binary_dir: includes.binary 目录路径 - out_buf: 输出缓冲区(用于存放空格分隔的 .obj 文件路径) - out_size: 输出缓冲区大小 - - Returns: - 写入的字节数(不含 null 终止符),0 表示无文件或错误 - """ + """Recursively scan includes_binary_dir, collect all .obj paths into out_buf; bytes written.""" if includes_binary_dir is None or out_buf is None or out_size == 0: return 0 out_buf[0] = '\0' @@ -914,21 +768,7 @@ def collect_obj_files(includes_binary_dir: str, out_buf: bytes, out_size: sizet) def _append_host_export_implib(linker_flags: str, implib_path: str) -> str: - """宿主导出模式:为链接参数追加 `--out-implib=<绝对路径>`(REPL 片段导入宿主用) - - 触发条件 = linker_flags 显式含 "--export-all-symbols"(工程级开关,宿主的 - project.vpj 里声明)。此时 exe 会带导出表,REPL 片段 DLL 需要一份 import lib - 才能把 IR 里的跨模块引用(形如 `<16位hex>.<函数名>`,见 .text.ll 的 - `define i32 @".utf8_is_valid"`)解析到宿主的导出符号。 - - 为什么路径由本层计算而不写进 project.vpj:Config 对 linker.flags 既不做 - %VAR% 展开、也不按 project_dir 解析(Config.vpj:180-230 的 _join_flags_array - 只做拼接;:570-632 只重写 -T 后的脚本路径;env 展开只发生在 static_libs/ - dynamic_libs/includes/build_dir 等字段),而 out-implib 的值是相对 - clang 进程 cwd 解析的 ⇒ 只有本层能从 output_dir + linker_output 算出绝对路径。 - - 未开启工程的 linker_flags 不含该标志 ⇒ 原样返回,链接命令逐字节不变。 - """ + """Host-export mode: append --out-implib= when linker_flags has --export-all-symbols.""" if linker_flags is None or implib_path is None: return linker_flags if string.strstr(linker_flags, "--export-all-symbols") is None: return linker_flags extra: sizet = string.strlen(" -Wl,--out-implib=") + string.strlen(implib_path) + 1 @@ -942,30 +782,15 @@ def _append_host_export_implib(linker_flags: str, implib_path: str) -> str: def link_obj_to_exe(output_dir: str, module_name: str, linker_cmd: str, linker_flags: str, linker_output: str, includes_binary_dir: str, linker_static_libs: str = None, linker_dynamic_libs: str = None) -> int: - """调用 clang++ 链接 .obj 为 .exe - - Args: - output_dir: 输出目录(包含 .obj 文件) - module_name: 模块名(SHA1) - linker_cmd: 链接器命令(如 "clang++") - linker_flags: 链接器参数 - linker_output: 输出文件名(如 "test.exe") - includes_binary_dir: includes.binary 目录路径(链接时附加预编译 .obj) - linker_static_libs: 显式静态链接文件(.a/.obj,空格分隔绝对路径,可为 None) - linker_dynamic_libs: 显式动态链接文件(.dll/.so,空格分隔绝对路径,可为 None) - - Returns: - 0 成功,非 0 失败 - """ + """Link .obj files into an .exe via clang++; 0 on success, non-zero on failure.""" if output_dir is None or linker_cmd is None or module_name is None: return 1 - # 构造切片 .obj 路径: output_dir/{sha1前缀}/{module_name}.obj + # build sliced .obj path: output_dir/{sha1 prefix}/{module_name}.obj obj_path: str = build_sliced_path(output_dir, module_name, "obj") if obj_path is None: return 1 - # 宿主 exe 导出(工程 linker.flags 含 --export-all-symbols 时)同步产出 import lib: - # {output_dir}/{linker_output}.dll.a —— REPL 片段链接时把它加进命令行即可导入 - # 宿主(同一进程/同一份 includes 代码与数据)。未开启的工程不受影响。 + # Host-exe export (when linker.flags has --export-all-symbols) also emits an import lib + # {output_dir}/{linker_output}.dll.a; REPL fragments link it to import the host. if linker_output is not None: implib_len: sizet = string.strlen(output_dir) + string.strlen(linker_output) + 8 implib_path: str = _mbuddy.alloc(implib_len) @@ -976,7 +801,7 @@ def link_obj_to_exe(output_dir: str, module_name: str, linker_cmd: str, linker_f # --traceback: link dbghelp (after the host-export rewrite, so both survive) linker_flags = _append_traceback_dbghelp(linker_flags) - # 收集 includes.binary 的 .obj 文件路径 + # collect .obj paths from includes.binary EXTRA_BUF_SIZE: sizet = 32768 extra_objs: bytes = _mbuddy.alloc(EXTRA_BUF_SIZE) if extra_objs is None: return 1 @@ -986,16 +811,14 @@ def link_obj_to_exe(output_dir: str, module_name: str, linker_cmd: str, linker_f if extra_len > 0: VLogger.info(f"附加 {extra_len} 字节的 includes.binary .obj 文件", "link") else: VLogger.warning(f"includes.binary 无 .obj 文件: {includes_binary_dir}", "link") - # 构造命令: clang++ {obj_path} extra_objs [static_libs/dynamic_libs] -o {output_dir}/{linker_output} linker_flags - # 注意: .obj 与显式指定的 .a/.obj/.dll/.so 都必须在 -l 库标志之前,否则链接器无法解析符号依赖。 - # ASan 运行库不由 TPV 硬编码(clang 无 x86_64-w64-windows-gnu 的 ASan 运行库,借用 clang64 会牵进 MSVC 专有指令), - # 改由 project.vpj 的 linker.static_libs / linker.dynamic_libs 显式声明,main.vp 在 --asan 下用 Config.asan_runtime_declared 校验。 + # Command: clang++ {obj_path} extra_objs [libs] -o {...}/{linker_output} linker_flags. Objects + # and explicit libs must precede -l flags. The ASan runtime is declared in project.vpj, not hardcoded. use_flags: str = linker_flags static_libs: str = linker_static_libs if static_libs is None: static_libs = "" if linker_dynamic_libs is not None and len(linker_dynamic_libs) > 0: - # 合并 static + dynamic 为单个空格分隔字符串(Viper str += 拼接 - # 会生成非法 add i8* IR,须用 snprintf 构造) + # Merge static + dynamic into one space-separated string (Viper str += would emit + # invalid add i8* IR, so build it with snprintf). merged_sl: str = _mbuddy.alloc(len(static_libs) + len(linker_dynamic_libs) + 2) if merged_sl is not None: if len(static_libs) > 0: @@ -1016,7 +839,7 @@ def link_obj_to_exe(output_dir: str, module_name: str, linker_cmd: str, linker_f else: viperlib.snprintf(cmd, cmd_len, "%s %s -o %s/%s %s", linker_cmd, obj_path, output_dir, linker_output, use_flags) - # --phase2command 模式:不执行链接,命令写入 bat + # --phase2command mode: do not link; write the command to the bat if _phase2_cmd_path is not None: _phase2_write_cmd(cmd) return 0 @@ -1028,8 +851,8 @@ def link_obj_to_exe(output_dir: str, module_name: str, linker_cmd: str, linker_f if result.returncode != 0: VLogger.error(f"链接失败,返回码: {result.returncode}", "link") VLogger.error(f"命令: {cmd}", "link") - # 显示链接器错误输出 - # 注意: subprocess 在 Windows 下将 stderr 合并到 stdout,result.stderr 总是 None + # Show linker error output. + # Note: subprocess merges stderr into stdout on Windows, so result.stderr is always None. if result.stdout is not None: VLogger.error(result.stdout, "link") else: VLogger.error("无输出捕获", "link") if result.stderr is not None: VLogger.error(result.stderr, "link") @@ -1038,22 +861,7 @@ def link_obj_to_exe(output_dir: str, module_name: str, linker_cmd: str, linker_f def asm_to_obj(asm_path: str, sha1_val: str, output_dir: str, triple: str) -> int: - """用 llvm-mc 汇编 .s 源文件为 .obj(切片路径) - - 裸机项目(VKernel 等)含 .s 汇编文件(如 Kernel/intr/isr.s 定义 - isr0/syscall_entry/_per_cpu 等符号)。.s 不经过 TPV 翻译/llc, - 直接用配套的 llvm-mc 汇编为 .obj,obj 路径与其他模块一致 - (output_dir/{sha1前缀}/{sha1}.obj),参与 Phase C 链接。 - - Args: - asm_path: .s 源文件完整路径 - sha1_val: 文件内容 SHA1 前16字符(切片路径用) - output_dir: 输出目录 - triple: 目标 triple(如 x86_64-none-elf) - - Returns: - 0 成功,非 0 失败 - """ + """Assemble a .s source into .obj with llvm-mc on the sliced path; 0 on success, non-zero on failure.""" if asm_path is None or sha1_val is None or output_dir is None: return 1 obj_path: str = build_sliced_path(output_dir, sha1_val, "obj") if obj_path is None: return 1 @@ -1064,7 +872,7 @@ def asm_to_obj(asm_path: str, sha1_val: str, output_dir: str, triple: str) -> in if cmd is None: return 1 viperlib.snprintf(cmd, cmd_len, "llvm-mc -triple=%s -filetype=obj -o %s %s", trip, obj_path, asm_path) - # --phase2command 模式:命令写入 bat(与 llc/链接一致) + # --phase2command mode: write the command to the bat (same as llc/link) if _phase2_cmd_path is not None: _phase2_write_cmd(cmd) return 0 @@ -1082,29 +890,16 @@ def asm_to_obj(asm_path: str, sha1_val: str, output_dir: str, triple: str) -> in def compile_module_to_obj(ir_buf: bytes, ir_len: sizet, temp_dir: str, output_dir: str, module_name: str, cc_cmd: str, cc_flags: str) -> int: - """编译 IR 到 .obj(不链接) - - Args: - ir_buf: LLVM IR 文本缓冲区 - ir_len: IR 文本长度 - temp_dir: 临时目录 - output_dir: 输出目录 - module_name: 模块名(SHA1) - cc_cmd: 编译器命令 - cc_flags: 编译器参数 - - Returns: - 0 成功,非 0 失败 - """ + """Compile IR to .obj without linking; 0 on success, non-zero on failure.""" if ir_buf is None or temp_dir is None or output_dir is None or module_name is None: return 1 - # Step 1: 写 .ll 文件 + # Step 1: write the .ll file ret: int = write_ir_to_file(ir_buf, ir_len, temp_dir, module_name) if ret != 0: VLogger.error(f"写 .ll 失败: {module_name}", "compile") return 1 - # Step 2: 构造切片 .ll 路径并编译 → .obj + # Step 2: build the sliced .ll path and compile -> .obj ir_path: str = build_sliced_path(temp_dir, module_name, "ll") if ir_path is None: return 1 @@ -1116,24 +911,11 @@ def compile_module_to_obj(ir_buf: bytes, ir_len: sizet, temp_dir: str, output_di def link_objs_to_exe(obj_paths: str, obj_paths_len: sizet, linker_cmd: str, linker_flags: str, linker_output: str, includes_binary_dir: str, linker_static_libs: str = None, linker_dynamic_libs: str = None) -> int: - """链接多个 .obj 文件为 .exe - - Args: - obj_paths: 空格分隔的 .obj 文件完整路径字符串 - obj_paths_len: obj_paths 长度 - linker_cmd: 链接器命令(如 "clang++") - linker_flags: 链接器参数 - linker_output: 输出文件完整路径 - includes_binary_dir: includes.binary 目录路径(链接时附加预编译 .obj) - linker_static_libs: 显式静态链接文件(.a/.obj,空格分隔绝对路径,可为 None) - - Returns: - 0 成功,非 0 失败 - """ + """Link multiple .obj files into an .exe; 0 on success, non-zero on failure.""" if obj_paths is None or obj_paths_len == 0 or linker_cmd is None: return 1 - # 去重 .obj 路径:同一模块的 obj 可能被多个收集环节加入(Phase B+ 新编译的 obj_paths 与 Phase C reachable 收集指向同一文件); - # 同一路径在链接命令中出现两次即重复符号(无 --allow-multiple-definition 的工程会 ld.lld 报错),故保留首次出现、后续跳过。 + # Dedup .obj paths: the same module's obj can be added by several collection stages, and + # a path appearing twice causes duplicate symbols. Keep the first occurrence, skip the rest. if obj_paths_len > 0: dedup: bytes = _mbuddy.alloc(obj_paths_len + 1) if dedup is not None: @@ -1141,13 +923,13 @@ def link_objs_to_exe(obj_paths: str, obj_paths_len: sizet, linker_cmd: str, link dpos: sizet = 0 dsrc_i: sizet = 0 while dsrc_i < obj_paths_len: - # 跳过前导空格 + # skip leading spaces while dsrc_i < obj_paths_len and obj_paths[dsrc_i] == ' ': dsrc_i += 1 if dsrc_i >= obj_paths_len: break dstart: sizet = dsrc_i while dsrc_i < obj_paths_len and obj_paths[dsrc_i] != ' ': dsrc_i += 1 dtlen: sizet = dsrc_i - dstart - # 检查 dedup 中是否已有相同 token + # check whether dedup already has this token ddup: int = 0 dj: sizet = 0 while dj < dpos: @@ -1178,17 +960,15 @@ def link_objs_to_exe(obj_paths: str, obj_paths_len: sizet, linker_cmd: str, link obj_paths = dedup obj_paths_len = dpos - # 收集 includes.binary 的 .obj 文件路径 + # collect .obj paths from includes.binary EXTRA_BUF_SIZE: sizet = 32768 extra_objs: bytes = _mbuddy.alloc(EXTRA_BUF_SIZE) if extra_objs is None: return 1 extra_len: sizet = 0 if includes_binary_dir is not None: extra_len = collect_obj_files(includes_binary_dir, extra_objs, EXTRA_BUF_SIZE) - # 构造命令: clang++ obj_paths extra_objs -o linker_output linker_flags - # ASan 运行库同样不再由 TPV 注入(在 link_obj_to_exe 里已有说明): - # 由 project.vpj 声明,--asan 下由 main.vp 校验。 - # 宿主导出:linker_output 是完整输出路径 ⇒ import lib 取名 .dll.a + # Command: clang++ obj_paths extra_objs -o linker_output linker_flags. The ASan runtime + # comes from project.vpj, not TPV. Host export: import lib is .dll.a. if linker_output is not None: implib_len2: sizet = string.strlen(linker_output) + 8 implib_path2: str = _mbuddy.alloc(implib_len2) @@ -1201,7 +981,7 @@ def link_objs_to_exe(obj_paths: str, obj_paths_len: sizet, linker_cmd: str, link static_libs2: str = linker_static_libs if static_libs2 is None: static_libs2 = "" if linker_dynamic_libs is not None and len(linker_dynamic_libs) > 0: - # 合并 static + dynamic(Viper str += 拼接生成非法 IR,用 snprintf) + # merge static + dynamic (Viper str += emits invalid IR; use snprintf) merged_sl2: str = _mbuddy.alloc(len(static_libs2) + len(linker_dynamic_libs) + 2) if merged_sl2 is not None: if len(static_libs2) > 0: @@ -1213,7 +993,7 @@ def link_objs_to_exe(obj_paths: str, obj_paths_len: sizet, linker_cmd: str, link cmd_len: sizet = 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 - # linker.static_libs 显式静态链接文件位于 .obj 之后、-o 之前 + # linker.static_libs explicit static libs go after the .obj files and before -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: @@ -1223,10 +1003,8 @@ def link_objs_to_exe(obj_paths: str, obj_paths_len: sizet, linker_cmd: str, link else: viperlib.snprintf(cmd, cmd_len, "%s %s -o %s %s", linker_cmd, obj_paths, linker_output, use_flags) - # 用 subprocess.run 捕获 clang++ 的 stdout/stderr 输出 - # 之前用 stdlib.system() 无法捕获具体错误(undefined reference 等), - # 导致 1.txt 中只看到"链接失败,返回码: 1"而看不到未解析符号 - # --phase2command 模式:不执行链接,命令写入 bat + # Capture clang++ stdout/stderr via subprocess.run (stdlib.system could not capture errors like + # undefined reference). --phase2command mode: do not link; write the command to the bat. if _phase2_cmd_path is not None: _phase2_write_cmd(cmd) return 0 @@ -1235,8 +1013,8 @@ def link_objs_to_exe(obj_paths: str, obj_paths_len: sizet, linker_cmd: str, link VLogger.error("subprocess.run 返回 None", "link") return 1 if result.returncode != 0: - # 先用 stdio.printf 直接输出 clang++ 的完整错误输出(含 undefined reference) - # 必须在 VLogger.error 之前输出,因为 VLogger.error 会调用 sys.exit(1) 终止进程 + # First print clang++'s full error output (incl. undefined reference) with stdio.printf; + # it must precede VLogger.error, which calls sys.exit(1) and terminates the process. if result.stdout is not None: stdio.printf("=== clang++ 链接错误输出 ===\n%s\n", result.stdout) if result.stderr is not None: stdio.printf("=== clang++ stderr ===\n%s\n", result.stderr) @@ -1245,27 +1023,11 @@ def link_objs_to_exe(obj_paths: str, obj_paths_len: sizet, linker_cmd: str, link return 0 -# ============================================================ -# --traceback: .stackmap sidecar writer (address -> source line) -# -# Ported from TPC Phase2Translator._WriteStackMap. Runs after a successful -# link: llvm-dwarfdump parses the final PE's .debug_line/.debug_info, every -# address is normalised to a runtime RVA (addr - ImageBase) and the result is -# written as .stackmap for the runtime traceback.vp handler. -# -# Layout (all little-endian): -# header 20B = "TPCS" | u32 version=1 | u32 entry_count | u32 str_count | u32 str_bytes -# entries = entry_count * 20B, strictly ascending rva: -# u64 rva | u32 file_off | u32 line | u32 func_off -# pool = str_bytes UTF-8 bytes, '\0'-terminated strings, offset 0 is an -# empty-string sentinel, func_off==0 means "no function name" -# -# Every failure aborts the build (VLogger.error exits); there is no fallback. -# ============================================================ +# --traceback: .stackmap sidecar writer (address -> source line). After a successful link, +# llvm-dwarfdump parses the PE's DWARF; addresses are normalised to RVAs and written out. -# row record: u64 rva | u64 file_path | u32 line | u32 pad | u64 func_name(可空) -# func_name 仅我方 .vpcall 派生行携带(objdump 标签名);DWARF 行写 0,序列化时 -# 回退到 DWARF 子程序表查名。 +# row record: u64 rva | u64 file_path | u32 line | u32 pad | u64 func_name (nullable). +# func_name is only carried by our .vpcall rows; DWARF rows write 0 and fall back to the subprogram table. define TB_ROW_SIZE = 32 define TB_ROW_OFF_FILE = 8 define TB_ROW_OFF_LINE = 16 @@ -1451,17 +1213,7 @@ def _tb_join(mb: memhub.MemBuddy*, base: str, name: str) -> str: def _tb_parse_dec(s: str) -> int: - """Parse a leading decimal integer, skipping leading blanks. - - dwarfdump right-aligns the bracketed indices to a 3-wide field - (`file_names[ 1]:` / `include_directories[ 12]`), so `_tb_bracket_idx` - hands us a string whose first char is a space. Without the blank skip the - digit loop never runs and every index parses as 0 -> every file record's - pidx becomes 0 while a row's fidx is the real 1..N fit -> `_tb_file_rec` - never matches and EVERY row degrades to the "??" placeholder. Measured on - Test/LLvmLiteTest: all 9146 stackmap records carried the single '??' file - string while function names and line numbers were correct. - """ + """Parse a leading decimal integer, skipping leading blanks (dwarfdump right-aligns indices).""" v: int = 0 i: sizet = 0 while _tb_cc(s, i) == 32 or _tb_cc(s, i) == 9: i += 1 @@ -1507,11 +1259,7 @@ def _tb_hex_digit(ch: int) -> int: def _tb_hex_in_parens(s: str, key: str) -> uint64: - """Parse the 0x... value inside the parentheses right after key (DW_AT_low_pc (0x...)). - - Returns 0 when the strict "( 0x" form is absent; DWARF prints the address form - that way, and a 0 result is harmless (out of the image VA range -> dropped). - """ + """Parse the 0x value inside the parentheses right after key; 0 when the form is absent.""" p: str = string.strstr(s, key) if p is None: return 0 p = p + string.strlen(key) @@ -1837,8 +1585,8 @@ def _tb_parse_dump(mb: memhub.MemBuddy*, dump: str) -> TbDump*: pkind = 0 continue if _tb_startswith(st, "dir_index:") != 0 and pkind == 1: - # `"dir_index:"` 含冒号共 10 字符:st + 9 指向冒号本身,解析恒得 0 - # (目录索引全丢 → 相对文件名拼不出绝对路径,File 行只剩裸文件名)。 + # "dir_index:" is 10 chars with the colon; st + 9 would point at the colon and + # always parse 0 (losing dir indices). Use st + 10. pdir = _tb_parse_dec(_tb_ltrim(st + 10)) continue if _tb_startswith(st, "Address") != 0 and _tb_contains(st, "Column") != 0: @@ -1930,34 +1678,23 @@ def _tb_parse_dump(mb: memhub.MemBuddy*, dump: str) -> TbDump*: return d -# ============================================================ -# --traceback 调用点归一:.vpcall + llvm-objdump -# -# MLIR 后端不产 DWARF(我方 .vp 函数在 exe 里没有 .debug_line),故用编译器在翻译期 -# 落盘的 .vpcall 文本 + 链接后对 exe 的 `llvm-objdump -d` 反汇编,把每个直接 -# callq 的**返回地址**(调用方帧的 PC)还原为「文件 + 调用点行 + 调用方函数名」。 -# -# 匹配口径:objdump 的函数标签地址 → 下一标签地址 构成函数区间;对每条指令行 -# `^([0-9a-f]+): (.. )+\s+(\S+)\s*(.*)$`,`callq 0xADDR ` 的返回地址 = 下一条 -# 指令(或下一个标签)的地址。用「所在函数 + callee 符号 + 该 (caller,callee) 对内的 -# 出现序号」匹配编译器 C 记录(同一 (caller,callee) 在构建序与地址序一致,llc -O0)。 -# 另为我方每个函数在入口地址生成一条兜底记录(F 记录 → (文件, 定义行, 函数名))。 -# ============================================================ +# --traceback call-site normalisation: .vpcall + llvm-objdump. The MLIR backend emits no DWARF +# for our .vp functions, so each direct callq's return address is mapped back to file/line/function. -# 调用记录:u64 caller | u64 callee | u64 file | u32 line | u32 next(组内下一个, NONE=尾) +# call record: u64 caller | u64 callee | u64 file | u32 line | u32 next (next in group, NONE=tail) define TB_CR_SIZE = 32 define TB_CR_OFF_CALLER = 0 define TB_CR_OFF_CALLEE = 8 define TB_CR_OFF_FILE = 16 define TB_CR_OFF_LINE = 24 define TB_CR_OFF_NEXT = 28 -# 函数记录:u64 sym | u64 file | u32 defline | u32 pad +# function record: u64 sym | u64 file | u32 defline | u32 pad define TB_FR_SIZE = 24 define TB_FR_OFF_SYM = 0 define TB_FR_OFF_FILE = 8 define TB_FR_OFF_DEFLINE = 16 -# 组哈希槽(以 (caller,callee) 为键;开放寻址 + 线性探测): -# u64 caller | u64 callee | u32 used | u32 head | u32 tail | u32 cursor +# group hash slots (keyed by (caller,callee); open addressing + linear probing): +# u64 caller | u64 callee | u32 used | u32 head | u32 tail | u32 cursor define TB_GRP_SIZE = 32 define TB_GRP_OFF_CALLER = 0 define TB_GRP_OFF_CALLEE = 8 @@ -1965,18 +1702,18 @@ define TB_GRP_OFF_USED = 16 define TB_GRP_OFF_HEAD = 20 define TB_GRP_OFF_TAIL = 24 define TB_GRP_OFF_CURSOR = 28 -# 无效索引哨兵(u32 全 1) +# invalid-index sentinel (all-ones u32) TB_IDX_NONE: uint64 = 0xFFFFFFFF class TbCallParse: - Cr: bytes # TB_CR_SIZE 记录数组 + Cr: bytes # array of TB_CR_SIZE records CrCap: int CrN: int - Fr: bytes # TB_FR_SIZE 记录数组 + Fr: bytes # array of TB_FR_SIZE records FrCap: int FrN: int - Grp: bytes # TB_GRP_SIZE 槽数组 + Grp: bytes # array of TB_GRP_SIZE slots GrpCap: int def __new__(self) -> t.CPtr: @@ -2000,7 +1737,7 @@ def _tb_next_pow2(n: int) -> int: def _tb_strhash(s: str) -> uint64: - """FNV-1a(32 位起点 + 64 位素数,仅用于哈希表寻址)。""" + """FNV-1a hash (32-bit offset basis, 64-bit prime) used only for table addressing.""" h: uint64 = 0x811C9DC5 if s is None: return h i: sizet = 0 @@ -2011,7 +1748,7 @@ def _tb_strhash(s: str) -> uint64: def _tb_grp_get(grp: bytes, cap: int, caller: str, callee: str, do_insert: int) -> int: - """按 (caller,callee) 查槽;do_insert=1 时未命中就地插入。返回槽号或 -1。""" + """Look up a slot by (caller, callee); insert on miss when do_insert=1; slot or -1.""" if grp is None or cap <= 0 or caller is None or callee is None: return -1 mask: uint64 = uint64(cap - 1) hv: uint64 = (_tb_strhash(caller) * 0x100000001B3 + _tb_strhash(callee)) & mask @@ -2085,7 +1822,7 @@ def _tb_cp_add_fr(mb: memhub.MemBuddy*, cp: TbCallParse*, sym: str, file: str, d def _tb_read_whole(mb: memhub.MemBuddy*, path: str) -> str: - """读整个文件为 NUL 结尾字符串(失败返回 None)。""" + """Read the whole file into a NUL-terminated string; None on failure.""" f: rawptr[fileio.File] = fileio.File(path, fileio.MODE.R) if f.closed: return None sz: LONGLONG = f.size() @@ -2103,7 +1840,7 @@ def _tb_read_whole(mb: memhub.MemBuddy*, path: str) -> str: def _tb_parse_f_line(mb: memhub.MemBuddy*, s: str, a: sizet, b: sizet, cp: TbCallParse*) -> int: - """解析 `F\t\t\t`。""" + """Parse an F record line with symbol, defline and file fields (tab-separated).""" p: sizet = a while p < b and _tb_cc(s, p) != 9: p += 1 if p >= b: return 0 @@ -2124,7 +1861,7 @@ def _tb_parse_f_line(mb: memhub.MemBuddy*, s: str, a: sizet, b: sizet, cp: TbCal def _tb_parse_c_line(mb: memhub.MemBuddy*, s: str, a: sizet, b: sizet, cp: TbCallParse*) -> int: - """解析 `C\t\t\t\t`。""" + """Parse a C record line with caller, callee, line and file fields (tab-separated).""" p: sizet = a while p < b and _tb_cc(s, p) != 9: p += 1 if p >= b: return 0 @@ -2184,7 +1921,7 @@ def _tb_load_vpcalls(mb: memhub.MemBuddy*, cp: TbCallParse*) -> int: def _tb_build_groups(mb: memhub.MemBuddy*, cp: TbCallParse*) -> int: - """把所有 C 记录按 (caller,callee) 串成组链表(组槽存 head/tail/cursor)。""" + """Chain all C records into group lists keyed by (caller, callee) (head/tail/cursor).""" if cp.CrN <= 0: return 0 cp.GrpCap = _tb_next_pow2(cp.CrN * 2 + 16) cp.Grp = mb.alloc(sizet(cp.GrpCap) * TB_GRP_SIZE) @@ -2216,7 +1953,7 @@ def _tb_build_groups(mb: memhub.MemBuddy*, cp: TbCallParse*) -> int: def _tb_emit_call_row(mb: memhub.MemBuddy*, d: TbDump*, cp: TbCallParse*, img_lo: uint64, caller: str, callee: str, ret_va: uint64) -> int: - """按 (caller,callee) 的下一条未消费 C 记录为返回地址生成一条行记录。""" + """Emit a row for a return address from the next unconsumed C record of (caller, callee).""" if caller is None or callee is None or cp.Grp is None or ret_va < img_lo: return 0 slot: int = _tb_grp_get(cp.Grp, cp.GrpCap, caller, callee, 0) if slot < 0: return 0 @@ -2233,7 +1970,7 @@ def _tb_emit_call_row(mb: memhub.MemBuddy*, d: TbDump*, cp: TbCallParse*, img_lo def _tb_emit_entry_row(mb: memhub.MemBuddy*, d: TbDump*, cp: TbCallParse*, img_lo: uint64, addr: uint64, sym: str) -> int: - """为 objdump 标签 `sym` 生成函数入口兜底行(文件/定义行取自 F 记录)。""" + """Emit a fallback function-entry row for an objdump label sym (file/line from the F record).""" if sym is None or cp.Fr is None or cp.FrN <= 0 or addr < img_lo: return 0 i: int = 0 while i < cp.FrN: @@ -2279,7 +2016,7 @@ def _tb_run_objdump(mb: memhub.MemBuddy*, exe_path: str) -> str: def _tb_scan_objdump(mb: memhub.MemBuddy*, dump: str, img_lo: uint64, d: TbDump*, cp: TbCallParse*) -> int: - """解析 `llvm-objdump -d` 文本,匹配 C/F 记录并把我方行写入 d。""" + """Parse llvm-objdump -d text, match C/F records and write our rows into d.""" dlen: sizet = string.strlen(dump) pos: sizet = 0 in_text: int = 0 @@ -2295,7 +2032,7 @@ def _tb_scan_objdump(mb: memhub.MemBuddy*, dump: str, img_lo: uint64, d: TbDump* if le > ls and _tb_cc(dump, le - 1) == 13: le -= 1 if le <= ls: continue if _tb_startswith(dump + ls, "Disassembly of section") != 0: - # 跨段:上一个段末尾未决的 callq 无有效返回地址,丢弃 + # section change: a pending callq at the previous section's end has no valid return address prev_is_call = 0 if _tb_contains(dump + ls, ".text") != 0: in_text = 1 else: in_text = 0 @@ -2305,11 +2042,11 @@ def _tb_scan_objdump(mb: memhub.MemBuddy*, dump: str, img_lo: uint64, d: TbDump* is_hex0: int = 0 if (c0 >= 48 and c0 <= 57) or (c0 >= 97 and c0 <= 102) or (c0 >= 65 and c0 <= 70): is_hex0 = 1 if is_hex0 == 0: continue - # 首个空格 + # first space sp: sizet = ls while sp < le and _tb_cc(dump, sp) != 32: sp += 1 if sp + 1 < le and _tb_cc(dump, sp + 1) == 60: - # ---- 标签行 `ADDR :` ---- + # ---- label line `ADDR :` ---- laddr: uint64 = _tb_hex_span(dump, ls, sp) if prev_is_call != 0: _tb_emit_call_row(mb, d, cp, img_lo, prev_func, prev_callee, laddr) @@ -2320,7 +2057,7 @@ def _tb_scan_objdump(mb: memhub.MemBuddy*, dump: str, img_lo: uint64, d: TbDump* cur_func = _tb_dup_span(mb, dump + lb, j - lb) if cur_func is not None: _tb_emit_entry_row(mb, d, cp, img_lo, laddr, cur_func) continue - # ---- 指令行 `ADDR: bytes mnemonic operands` ---- + # ---- instruction line `ADDR: bytes mnemonic operands` ---- k: sizet = ls while k < le and _tb_cc(dump, k) != 58: k += 1 if k >= le: continue @@ -2328,7 +2065,7 @@ def _tb_scan_objdump(mb: memhub.MemBuddy*, dump: str, img_lo: uint64, d: TbDump* if prev_is_call != 0: _tb_emit_call_row(mb, d, cp, img_lo, prev_func, prev_callee, iaddr) prev_is_call = 0 - # 跳过字节 token(恰好 2 个 hex 字符),定位 mnemonic 起点 + # skip byte tokens (exactly 2 hex chars) and locate the mnemonic start p: sizet = k + 1 while p < le and (_tb_cc(dump, p) == 32 or _tb_cc(dump, p) == 9): p += 1 ms: sizet = p @@ -2350,7 +2087,7 @@ def _tb_scan_objdump(mb: memhub.MemBuddy*, dump: str, img_lo: uint64, d: TbDump* os: sizet = me while os < le and (_tb_cc(dump, os) == 32 or _tb_cc(dump, os) == 9): os += 1 if os + 1 < le and _tb_cc(dump, os) == 48 and _tb_cc(dump, os + 1) == 120: - # 直接调用:提取 `` + # direct call: extract `` ab: sizet = os while ab < le and _tb_cc(dump, ab) != 60: ab += 1 if ab < le: @@ -2404,9 +2141,8 @@ def WriteStackMap(mb: memhub.MemBuddy*, exe_path: str) -> int: d: TbDump* = _tb_parse_dump(mb, dump) if d is None: return 1 - # ---- 1b) 我方调用点行记录:.vpcall + llvm-objdump ---- - # DWARF 行仅覆盖链接进来的 mingw CRT(我方 .vp 函数无 .debug_line);这里用编译器 - # 落盘的 .vpcall + objdump 反汇编补上「返回地址 → 调用点行 / 函数入口行」。 + # ---- 1b) our call-site rows via .vpcall + llvm-objdump ---- + # DWARF rows only cover the linked mingw CRT; .vpcall + objdump add return-address -> call-site rows. if _tb_vpcall_n > 0: cp: TbCallParse* = _tb_cp_init(mb) if cp is None: @@ -2531,7 +2267,7 @@ def WriteStackMap(mb: memhub.MemBuddy*, exe_path: str) -> int: pth: str = rawptr(_tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FILE)) pool_need += string.strlen(pth) + 1 i += 1 - # 我方行直接携带函数名(走字符串池);DWARF 行名来自子程序表。 + # our rows carry the function name directly (string pool); DWARF rows take it from the subprogram table. i = 0 while i < uniq: fp: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FUNC) @@ -2650,41 +2386,23 @@ def WriteStackMap(mb: memhub.MemBuddy*, exe_path: str) -> int: def run_pipeline(ir_buf: bytes, ir_len: sizet, 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, linker_static_libs: str = None, linker_dynamic_libs: str = None) -> BuildResult*: - """执行完整编译管线 - - Args: - ir_buf: LLVM IR 文本缓冲区 - ir_len: IR 文本长度 - temp_dir: 临时目录 - output_dir: 输出目录 - module_name: 模块名 - cc_cmd: 编译器命令 - cc_flags: 编译器参数 - linker_cmd: 链接器命令 - linker_flags: 链接器参数 - linker_output: 输出文件名 - includes_binary_dir: includes.binary 目录路径(链接时附加预编译 .obj) - linker_static_libs: 显式静态链接文件(.a/.obj,空格分隔绝对路径,可为 None) - - Returns: - BuildResult 对象 - """ + """Run the full compile pipeline; returns a BuildResult object.""" result: BuildResult* = BuildResult() if result is None: return None - # Step 0: 确保 temp/output 目录存在(自动创建,避免写文件失败) + # Step 0: ensure temp/output dirs exist (auto-create to avoid write failures) ensure_dir(temp_dir) ensure_dir(output_dir) - # Step 1: 写 .ll 文件 + # Step 1: write the .ll file ret: int = write_ir_to_file(ir_buf, ir_len, temp_dir, module_name) if ret != 0: result.Success = 0 result.ErrorMsg = "写 .ll 文件失败" return result - # Step 2: llc 编译 .ll → .obj - # 构造切片 .ll 文件路径 + # Step 2: llc-compile .ll -> .obj. + # build the sliced .ll file path ir_path: str = build_sliced_path(temp_dir, module_name, "ll") if ir_path is None: result.Success = 0 @@ -2697,7 +2415,7 @@ def run_pipeline(ir_buf: bytes, ir_len: sizet, temp_dir: str, output_dir: str, m result.ErrorMsg = "llc 编译失败" return result - # Step 3: clang++ 链接 .obj → .exe(附加 includes.binary 预编译 .obj 与 static_libs/dynamic_libs) + # Step 3: clang++ link .obj -> .exe (plus includes.binary prebuilt .obj and static/dynamic libs) ret = link_obj_to_exe(output_dir, module_name, linker_cmd, linker_flags, linker_output, includes_binary_dir, linker_static_libs, linker_dynamic_libs) if ret != 0: result.Success = 0 diff --git a/App/lib/Phase/Phase1/Phase1Main.vp b/App/lib/Phase/Phase1/Phase1Main.vp index 8ba3a44..84340d0 100644 --- a/App/lib/Phase/Phase1/Phase1Main.vp +++ b/App/lib/Phase/Phase1/Phase1Main.vp @@ -30,21 +30,17 @@ import lib.core.Gen.GenApi as GenApi import lib.core.Gen.DebugInfoGen as DebugInfoGen -# ============================================================ -# _InjectedCreatesCycle - 容器模块自动注入前的破环检查 -# -# 注入 _list/_dict 前检查被注入模块自身的依赖是否已含当前模块,命中(如 memhub↔_list)则跳过注入—— -# 否则 Phase A 多遍收敛死锁("无法完成翻译");去掉环边后 _list 仍因当前模块被其依赖而处于闭包,list 类仍由 Phase1b 注册。 -# ============================================================ +# _InjectedCreatesCycle - cycle check before auto-injecting a container module +# (_list/_dict); a hit (e.g. memhub<->_list) skips injection to avoid a Phase A deadlock. def _InjectedCreatesCycle(entry: rawptr[IncludesScanner.FileEntry], mod_name: str) -> int: - """注入 mod_name 到当前模块前检查是否成环,返回 1=成环需跳过注入""" + """Return 1 if injecting mod_name into the current module would create a cycle""" 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 基名(最后 '/' 或 '\\' 之后)去扩展名 + # Current module name = basename of RelPath (after the last '/' or '\'), without extension base: str = string.strrchr(rp, '/') if base is None: base = string.strrchr(rp, '\\') if base is not None: base = base + 1 @@ -76,7 +72,7 @@ def _InjectedCreatesCycle(entry: rawptr[IncludesScanner.FileEntry], mod_name: st def _DepsContainsName(deps: str, name: str) -> int: - """deps 字符串(空格分隔模块名)是否含 name token(词边界精确匹配)""" + """Return 1 if the space-separated deps string contains the name token exactly""" if deps is None or name is None: return 0 nl: sizet = string.strlen(name) dl: sizet = string.strlen(deps) @@ -91,7 +87,7 @@ def _DepsContainsName(deps: str, name: str) -> int: def _DepsRemoveName(deps: str, name: str) -> int: - """从 deps 字符串移除 name token(原地重建,返回 1=有改动)""" + """Remove the name token from the deps string in place; returns 1 if changed""" if deps is None or name is None: return 0 nl: sizet = string.strlen(name) dl: sizet = string.strlen(deps) @@ -119,7 +115,7 @@ def _DepsRemoveName(deps: str, name: str) -> int: def _RewriteDepsTxt(temp_dir: str, sha1: str, deps_str: str) -> None: - """重写 {temp_dir}/{sha1前2位}/{sha1}.deps.txt(内存破环后同步磁盘)""" + """Rewrite the on-disk deps.txt file to match the in-memory deps after breaking a cycle""" if temp_dir is None or sha1 is None or deps_str is None: return td_len: sizet = string.strlen(temp_dir) dpath: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1, "deps.txt") @@ -133,17 +129,7 @@ def _RewriteDepsTxt(temp_dir: str, sha1: str, deps_str: str) -> None: 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)。 - """ + """Remove _list/_dict injection cycle edges after scanning and sync deps.txt""" if StubMerger._includes_deps_sha1 is None or StubMerger._includes_deps_str is None: return 0 cnt: sizet = StubMerger._includes_deps_count ci: sizet @@ -152,7 +138,7 @@ def _BreakContainerCyclesInDeps(temp_dir: str) -> int: deps_m: str = StubMerger._includes_deps_str + sizet(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 基名去扩展名) + # Module name (basename of RelPath without extension) rel_m: str = StubMergerStore.LookupSha1RelPath(sha1_m) if rel_m is None: continue base_m: str = string.strrchr(rel_m, '/') @@ -171,16 +157,16 @@ def _BreakContainerCyclesInDeps(temp_dir: str) -> int: if nm_m is None: continue string.strncpy(nm_m, base_m, 128) nm_m[128] = '\0' - # _list ↔ M 环 + # _list <-> M cycle 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(诊断回退路径读文件) + # Rewrite deps.txt after breaking the cycle in memory (diagnostic fallback reads the file) _RewriteDepsTxt(temp_dir, sha1_m, deps_m) - # _dict ↔ M 环 + # _dict <-> M cycle if string.strstr(deps_m, "_dict") is not None: dct_sha1: str = StubMergerStore.LookupSha1ByModName("_dict") if dct_sha1 is not None: @@ -191,18 +177,10 @@ def _BreakContainerCyclesInDeps(temp_dir: str) -> int: 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 -# ============================================================ +# _BuildReachableResult - rebuild the reachable FileEntry list from the Sha1Store. +# Only incdir-nonempty (includes) entries enter result; App entries would cause duplicate symbols. def _BuildReachableResult(mb: memhub.MemBuddy*, includes_dir: str) -> rawptr[IncludesScanner.ScanResult]: - """从 Sha1Store(仅可达 includes + App)构建可达 FileEntry 列表""" + """Build the reachable FileEntry list from the Sha1Store (reachable includes only)""" if mb is None: return None store_count: sizet = sizet(StubMerger._sha1_store_count) if store_count <= 0 or StubMerger._sha1_store_arr is None: return None @@ -213,18 +191,18 @@ def _BuildReachableResult(mb: memhub.MemBuddy*, includes_dir: str) -> rawptr[Inc for i in range(store_count): sidx: sizet = sizet(i) * 17 if StubMerger._sha1_store_arr[sidx] == '\0': continue - # 仅 includes 条目(incdir 非空);App 条目不进入 result + # Only includes entries (non-empty incdir); App entries do not enter result iidx: sizet = sizet(i) * StubMerger.MAX_INC_DIR_LEN if StubMerger._sha1_store_incdir[iidx] == '\0': continue eaddr: uint64 = uint64(result.Entries) + uint64(result.Count) * entry_size entry: rawptr[IncludesScanner.FileEntry] = rawptr[IncludesScanner.FileEntry](eaddr) if entry is None: continue - # SHA1 深拷贝(store 槽 17 字节) + # Deep-copy SHA1 (store slot is 17 bytes) 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 深拷贝 + # Deep-copy RelPath / IncDir ridx: sizet = sizet(i) * StubMerger.MAX_REL_PATH_LEN rel_len: sizet = string.strlen(StubMerger._sha1_store_rel + ridx) inc_len: sizet = string.strlen(StubMerger._sha1_store_incdir + iidx) @@ -239,7 +217,7 @@ def _BuildReachableResult(mb: memhub.MemBuddy*, includes_dir: str) -> rawptr[Inc string.strcpy(inc_buf, StubMerger._sha1_store_incdir + iidx) entry.RelPath = rel_buf entry.IncDir = inc_buf - # 绝对路径 = inc_dir + '/' + rel_path(与 _TryResolveModuleFile 拼接一致) + # Absolute path = inc_dir + '/' + rel_path (matches _TryResolveModuleFile) abs_buf: bytes = stdlib.malloc(inc_len + 1 + rel_len + 1) if abs_buf is None: stdlib.free(sha1_buf) @@ -250,7 +228,7 @@ def _BuildReachableResult(mb: memhub.MemBuddy*, includes_dir: str) -> rawptr[Inc abs_buf[inc_len] = '/' string.strcpy(abs_buf + inc_len + 1, rel_buf) entry.Path = abs_buf - # 模块名(store 登记时保存的绝对模块名) + # Module name (absolute module name saved when registering in the store) midx: sizet = sizet(i) * 64 mod_len: sizet = string.strlen(StubMerger._sha1_store_mod + midx) if mod_len > 0: @@ -262,43 +240,33 @@ def _BuildReachableResult(mb: memhub.MemBuddy*, includes_dir: str) -> rawptr[Inc return result # ============================================================ -# Phase1Main - Phase1 主流程(扫描 → 预注册 → 1a/1b 翻译 → 生成 stub) -# -# 函数实现直接位于本模块,调用方经本模块前缀访问;全局符号经 Phase1._xxx 跨模块访问。 -# ============================================================ +# Phase1Main - Phase1 main flow (scan -> pre-register -> 1a/1b translate -> generate stub) # ============================================================ -# RunPhase1 - Phase1: 扫描 includes 目录,按需翻译并生成 stub -# -# 对每个 .vp/.py 文件检查 stub 是否已存在,不存在才翻译并分离 stub(避免重复工作)。 -# -# Args: mb 内存池;includes_dir includes 目录路径;temp_dir 临时目录(保存 stub);log 日志器 -# Returns: 0 成功,非 0 失败 -# ============================================================ +# RunPhase1 - scan the includes dir; translate and split stubs only when missing. def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawptr[VLogger.Logger]) -> int: - """Phase1: 扫描 includes 目录,按需翻译并生成 stub""" + """Phase1: scan the includes directory and translate/generate stubs on demand""" if includes_dir is None or temp_dir is None: VLogger.error("includes_dir 或 temp_dir 为空,无法运行 Phase1", "Phase1") return 1 - # 设置全局 temp_dir(供跨模块 CDefine 查找使用) + # Set the global temp_dir (used for cross-module CDefine lookup) GenApi.set_temp_dir(temp_dir) - # 同步设置 HandlesExprCall 的 temp_dir(供 vpi stub 返回注解缓存按需加载)。 - # 必须在 includes 翻译前设置:Phase1 翻译 includes 时「同模块方法调用(定义在后)」需读 {temp_dir}/{sha1[0:2]}/{sha1}.vpi 兜底,Phase2 的 SetTempDir 太晚。 + # Also set HandlesExprCall's temp_dir (for the on-demand vpi return-annotation cache); + # must run before includes translation since Phase2's SetTempDir is too late. GenApi.SetTempDir(temp_dir) - # 确保 temp 目录存在(build_dir/temp 可能尚未创建) + # Ensure the temp dir exists (build_dir/temp may not exist yet) BuildPipeline.ensure_dir(temp_dir) log.banner("Phase1: 扫描 includes(按需翻译)") - # 初始化 AST 表(只需一次):必须在依赖图 BFS 扫描之前调用。 - # _init_tables 懒分配的关键字/操作符表(全局 _kw_head 等)若首次触发在 BFS 的文件级 mark 之后,reset_to 会回收表节点 → 全局悬空 → 后续 tokenize 崩溃。 + # Init the AST tables once, before the dependency-graph BFS: if _init_tables first runs + # after the BFS file-level mark, reset_to frees the tables and later tokenize crashes. ast._init_tables(mb) - # 依赖图按需扫描(App 优先模型):先扫 App 源文件(Config.SourceDir)的 import 依赖作为初始队列, - # BFS 按文件系统解析 includes 文件(只对可达者读文件+算 SHA1+解析 import)并登记 Sha1Store/deps/写 deps.txt; - # 未引用的 includes 从不读取。 + # On-demand dependency scan (App-first): seed the queue from App imports, then BFS-resolve + # reachable includes only (read + SHA1 + import parse); unreferenced ones are never read. t_scan0: uint64 = w32.win32base.GetTickCount64() reachable_set: str = None reachable_count: int = 0 @@ -326,58 +294,48 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt t_scan1: uint64 = 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 即可达子集) + # Build the reachable FileEntry list from the Sha1Store; 1a/1b-pre/1b iterate result + # (non-empty incdir = includes). use_reachable filtering is idempotent. result: rawptr[IncludesScanner.ScanResult] = _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。 + # After scanning, clear _list/_dict injection cycle edges (e.g. _list<->memhub) so the + # graph has none and Phase A converges; deps.txt is rewritten in sync. _BreakContainerCyclesInDeps(temp_dir) - # 写 _sha1_map.txt(仅可达 includes,人类可读输出;机器分析使用内存存储器) + # Write _sha1_map.txt (reachable includes only, human-readable; analysis uses the in-memory store) StubMergerCollect.WriteIncludesSha1Map(mb, temp_dir, result, reachable_set, reachable_count) - # 兼容旧引用:sha1_set/set_count 对齐可达集合(use_reachable=1 时不被使用) + # Legacy compat: sha1_set/set_count alias the reachable set (unused when use_reachable=1) sha1_set: str = reachable_set set_count: int = reachable_count - # 模块 SHA1 映射已由依赖图扫描登记到 StubMerger 全局存储器(仅可达 includes + App), - # HandlesExprCall._lookup_module_sha1 直接查全局存储器。 + # Module SHA1 map is registered into StubMerger's global store by the dependency scan; + # HandlesExprCall._lookup_module_sha1 queries that store directly. - # FileEntry 结构体大小(Phase 1a 和 1b 共用) + # FileEntry struct size (shared by Phase 1a and 1b) entry_size: sizet = IncludesScanner.FileEntry.__sizeof__() # ============================================================ - # Phase 1a: 注册可达 includes 文件的 struct/enum/union - # - # 解决字母序依赖问题:1a 先把所有 struct/enum/union 注册到全局表,1b 全量翻译时 struct 已注册、走 existing 路径只翻译方法体; - # 只处理可达文件;处理前按类继承关系拓扑排序,确保父类所在文件先于子类所在文件。 - # ============================================================ + # Phase 1a: register struct/enum/union for reachable includes (fixes alphabetical-order deps). p1a_reg: int = 0 p1a_skp: int = 0 p1a_fl: int = 0 # ============================================================ - # [临时取证打点] Phase1 早期扁平缓冲分配失败现场快照(定位后清理);仅只读 mb 状态 + 打印,不改变任何分配行为与逻辑。 - # 三维度判定:① OOM(dump_stats 显示 used≈total、free≈0);② mb 损坏(usable 基址/大小荒谬、self_check != 0);③ 本次请求 size 算出的 order > max_order。 - # 用 VLogger.info(非终止;VLogger.error 会 sys.exit(1) 掩盖后续打点)。 - # ============================================================ + # [temp diagnostic] Snapshot mb state if the early Phase1 flat buffer allocation fails. if mb is not None: - # 先做带环检测的 self_check:free list 被写坏成环时 dump_stats 的 - # free_bytes/free_count 无步数上限会死循环(打点自身卡死),故 - # self_check 返回 4(成环)时跳过 dump_stats。 + # Run a cycle-detecting self_check first: a corrupted free list would loop forever in + # dump_stats, so skip dump_stats when self_check returns 4 (cycle). p1_sc: int = mb.self_check() if p1_sc != 4: mb.dump_stats("phase1-pre") else: VLogger.info("[取证] self_check=4(free list 成环),跳过 dump_stats 防死循环", "Phase1") p1_mo: int = mb.max_order if p1_mo < 0 or p1_mo > 32: - p1_mo = 32 # 钳制:max_order 被写坏时防本打点自身越界读 free_lists + p1_mo = 32 # Clamp: prevents this probe from out-of-bounds reading free_lists if max_order is corrupted p1_fl_head: uint64 = uint64(mb.free_lists[p1_mo]) p1_mb_addr: uint64 = uint64(mb) p1_base_addr: uint64 = uint64(mb.base) @@ -397,7 +355,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt p1_fl_ds: uint64 = uint64(mb.free_lists[p1_o_ds]) VLogger.info(f"[取证] order(class_names {p1_sz_cn}B+16)={p1_o_cn} fl={p1_fl_cn} " f"order(def_sha1s {p1_sz_ds}B+16)={p1_o_ds} fl={p1_fl_ds} " f"order(topo {p1_sz_tp}B+16)={p1_o_tp} order(box {p1_sz_bx}B+16)={p1_o_bx} " f"(fl==0 只说明该阶空、仍可由更高阶拆分;若 alloc 失败而 dump_stats 的 free>0 才是元数据损坏铁证," f"任一 order>max_order={mb.max_order} 则是 size 异常)", "Phase1") - # 预扫描类继承关系,构建拓扑顺序(用扁平字节数组,避免结构体 str 指针 NULL 崩溃) + # Pre-scan class inheritance and build the topo order (flat byte arrays avoid str-pointer NULL crashes) class_names_buf: bytes = mb.alloc(Phase1.MAX_CLASSES * Phase1.CLASS_NAME_LEN) parent_names_buf: bytes = mb.alloc(Phase1.MAX_CLASSES * Phase1.CLASS_NAME_LEN) def_sha1s_buf: bytes = mb.alloc(Phase1.MAX_CLASSES * Phase1.SHA1_LEN) @@ -409,9 +367,8 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt if generic_file_count_box is not None: string.memset(generic_file_count_box, 0, 4) topo_count: int = 0 - # 三块扁平缓冲是 Phase1Scan._ScanClassInheritance 的契约前置缓冲:该函数入口对 NULL fail-fast(静默跳过整段会退化为字母序并掩盖 OOM 等内存故障), - # 故分配失败时逐块显式报错终止(VLogger.error 内部 sys.exit(1)),绝不静默回退。 - # 注:topo_order/topo_count_box 仅供 Phase1Topo._TopoSortFiles 使用,有效性由其入口自身校验(NULL 即返回 -1 并终止),不并入此处终止逻辑。 + # These three flat buffers are Phase1Scan._ScanClassInheritance's contract pre-buffers; it + # fails fast on NULL, so report and abort per buffer on allocation failure (never fall back). if class_names_buf is None: VLogger.error("类继承预扫描扁平缓冲分配失败:class_names_buf 为 NULL,终止编译", "Phase1") if parent_names_buf is None: VLogger.error("类继承预扫描扁平缓冲分配失败:parent_names_buf 为 NULL,终止编译", "Phase1") if def_sha1s_buf is None: VLogger.error("类继承预扫描扁平缓冲分配失败:def_sha1s_buf 为 NULL,终止编译", "Phase1") @@ -441,32 +398,23 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt topo_count = 0 # ============================================================ - # Phase 1a-pre-generic-all: mark1 前全量预注册 includes 泛型模板 - # - # 非可达泛型文件(_list/_dict/linkedlist/mlirlite 等)的模板若在 Phase2(mark2 之后)才注册会被 reset 回收 → list[int]/dict[...] 特化失败。 - # 故对全部 includes 轻量预注册,使模板表与节点都落在 mark1 之前(_PreRegisterAllGenericTemplates 内有 generic marker 文本预筛,不 parse 全部 AST,OOM 风险可控)。 + # Phase 1a-pre-generic-all: pre-register all includes' generic templates before mark1. full_scan_g: rawptr[IncludesScanner.ScanResult] = IncludesScanner.scan_includes(mb, includes_dir) if full_scan_g is not None: Phase1Scan._PreRegisterAllGenericTemplates(mb, full_scan_g) else: Phase1Scan._PreRegisterAllGenericTemplates(mb, result) # ============================================================ - # Phase 1a: 注册 struct/enum/union(mark1 前,全量 includes) - # - # OOM 治理:1a 对全部 includes parse 完整 AST,若驻留到 mark1 会累积至 1GB 主池耗尽。故: - # 1) StdlibAlloc=1:1a 注册的跨文件持久数据(StructTable 表、Fields/Name/ModuleSha1、struct_ty 深拷贝、枚举/Renum/泛型模板等)全部落 perm_pool(永不 reset); - # 2) 每文件翻译完 reset_to(文件级 mark):只回收该文件的 AST/lexer/token/IR 等瞬时数据,注册数据不受影响。 - # mark1 仍在 1a 结束后打,1b 每文件 reset_to(mark1) 语义不变。 - # ============================================================ + # Phase 1a: register struct/enum/union (before mark1, all includes) with OOM control. GenApi.set_stdlib_alloc(1) - # 遍历文件:优先使用拓扑顺序,回退到字母序 + # Iterate files: topo order preferred, falling back to alphabetical iter_count: int = topo_count if topo_count > 0 else result.Count - # 1a 阶段总耗时起点(struct/enum/union 注册) + # 1a total-time start (struct/enum/union registration) t_a0: uint64 = w32.win32base.GetTickCount64() for i in range(iter_count): - # 每文件级 mark:翻译完回收该文件 AST(注册数据在 perm_pool) + # Per-file mark: free this file's AST after translating (registered data is in perm_pool) p1a_mark: sizet = mb.mark() - # 获取文件索引:拓扑顺序或字母序 + # Get the file index: topo order or alphabetical file_idx: int = i if topo_count > 0: od_addr: uint64 = uint64(topo_order) + i * 4 @@ -484,7 +432,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt p1a_fl += 1 continue - # 按需翻译过滤(拓扑排序已过滤,但字母序回退需要检查) + # On-demand filter (topo already filtered; the alphabetical fallback must check) if topo_count == 0: if use_reachable != 0: if StubMergerCollect._is_in_sha1_set(sha1_r, reachable_set, reachable_count) == 0: @@ -495,17 +443,17 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt p1a_skp += 1 continue - # 单文件计时起点(读文件 + parse 段,定位 1a 卡点) + # Per-file timer start (read + parse, to locate 1a stalls) t_fa0: uint64 = w32.win32base.GetTickCount64() - # 读取文件内容 + # Read file contents file_path_r: str = entry_r.Path f_r: rawptr[fileio.File] = fileio.File(file_path_r, fileio.MODE.R) if f_r.closed: p1a_fl += 1 continue - # read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被静默截断 + # read_all's capacity includes the terminator (reads at most cap-1 bytes); longer files are truncated if f_r.size() >= Phase1.SRC_BUF_SIZE: VLogger.error(f"源文件超过 {Phase1.SRC_BUF_SIZE} 字节上限, 拒绝读取: {file_path_r}", "Phase1") f_r.close() @@ -526,7 +474,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt continue if bytes_read_r < Phase1.SRC_BUF_SIZE: src_buf_r[bytes_read_r] = 0 - # 解析 AST + # Parse AST lx_r: ast.Lexer* = ast.new_lexer(mb) if lx_r is None: stdlib.free(src_buf_r) @@ -540,10 +488,10 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt p1a_fl += 1 continue - # 计时点 1:读文件 + parse 完成 + # Timer 1: read + parse done t_fa1: uint64 = w32.win32base.GetTickCount64() - # 创建 Translator,设置 _declare_only=1(注册 struct/enum/union) + # Create Translator with _declare_only=1 (register struct/enum/union) tr_r: rawptr[HandlesTranslator.Translator] = HandlesTranslator.Translator() if tr_r is None: stdlib.free(src_buf_r) @@ -559,34 +507,32 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt GenApi.clear_ctypedefs() GenApi.reset_visible_structs(mb, 0) ret_r: int = tr_r.translate(tree_r) - # 计时点 2:translate(declare=1) 返回 + # Timer 2: translate(declare=1) returned t_fa2: uint64 = w32.win32base.GetTickCount64() if ret_r != 0: p1a_fl += 1 else: p1a_reg += 1 - # 逐文件进度日志(parse/translate 两段,定位 1a 卡点) + # Per-file progress log (parse/translate segments, to locate 1a stalls) VLogger.info(f"[{i+1}/{iter_count}] 1a: {file_path_r} (parse={t_fa1-t_fa0} ms, translate={t_fa2-t_fa1} ms, total={t_fa2-t_fa0} ms)", "Phase1") - # 释放 Translator 的 C malloc 资源 + # Release the Translator's C malloc resources if tr_r._global_names is not None: stdlib.free(tr_r._global_names) if tr_r._nonlocal_names is not None: stdlib.free(tr_r._nonlocal_names) stdlib.free(src_buf_r) - # [TEMP-DIAG] 1a 每文件哨兵:校验本文件窗口 [p1a_mark, chk_depth) 内所有存活块的块尾 canary。 - # 位置必须在 reset_to 之前:reset_to 会把 chk_depth 截断回 p1a_mark,之后再查同一 mark 时 check_alive_blocks 直接 return 0(窗口为空、必然漏报)。 - # p1a_mark 在循环体首行捕获(本文件 L597),故窗口恰为「本文件 parse + translate(declare=1)」期间分配的块。 - # 仅 --detection-deep(mb.deep_detect != 0)时执行(每文件 O(n) 遍历活块);读法同本文件既有 mb.detection:直接读 MemBuddy 实例的公开 int 字段。 + # [TEMP-DIAG] Per-file sentinel: check the tail canary of live blocks in [p1a_mark, + # chk_depth). Must run before reset_to; only under --detection-deep (mb.deep_detect != 0). if mb.deep_detect != 0: chk_rc_1a: int = mb.check_alive_blocks(p1a_mark) if chk_rc_1a != 0: VLogger.error(f"内存损坏(1a 每文件后续, rc={chk_rc_1a}): {file_path_r}", "Phase1") stdlib.exit(1) - # 回收本文件 AST/lexer/token/IR 等瞬时数据(注册数据已在 perm_pool, - # reset 不影响;IncludesScanner result 等在 p1a_mark 前分配同样保留) + # Reclaim this file's transient AST/lexer/token/IR (registered data is in perm_pool, so + # reset does not affect it; IncludesScanner result predates p1a_mark and is kept too). mb.reset_to(p1a_mark) - # 1a 阶段总耗时(struct/enum/union 注册,含每文件读+parse+翻译声明) + # 1a total time (struct/enum/union registration, incl. per-file read+parse+declaration) t_a1: uint64 = w32.win32base.GetTickCount64() VLogger.info(f"1a 总耗时: {t_a1-t_a0} ms (注册 struct/enum, reg={p1a_reg} skp={p1a_skp})", "Phase1") @@ -594,33 +540,26 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt VLogger.error(f"Phase 1a 有 {p1a_fl} 个文件 struct 注册失败", "Phase1") return 1 - # 释放拓扑排序资源(topo_order/topo_count_box 保留到 Phase 1b 结束, - # 1b 按拓扑序翻译需要它们;class/def 数组 1b 不再使用) + # Release topo-sort resources (topo_order/topo_count_box stay until Phase 1b ends; + # the class/def arrays are no longer used by 1b). if class_names_buf is not None: mb.free(class_names_buf) if parent_names_buf is not None: mb.free(parent_names_buf) if def_sha1s_buf is not None: mb.free(def_sha1s_buf) if generic_file_indices is not None: mb.free(generic_file_indices) if generic_file_count_box is not None: mb.free(generic_file_count_box) - # ============================================================ - # Phase 1b: 全量翻译(struct 已注册,走 existing 路径翻译方法体) - # ============================================================ - # OOM 回收:mark1 在 1a-pre/泛型预注册/1a 全部结束后打,所有跨文件持久表(_struct_table/_enum_table/_generic_class_nodes/_renum_*/关键字表等)都落在 mark1 之前; - # 1b 每文件翻译完后 reset_to(mark1) 回收该文件的 AST/IR/Handler/dump 临时,避免 90 文件累积导致 1GB 池 OOM。 + # Phase 1b: full translation (structs already registered; translate method bodies). + # mark1 precedes all cross-file persistent tables; reset per file to avoid pool exhaustion. mark1: sizet = mb.mark() - # 1b 阶段新注册的 struct/enum 条目改 stdlib 分配(跨文件永久存活,不被 reset 回收) + # Struct/enum entries registered in 1b use stdlib allocation (permanent, not reset) GenApi.set_stdlib_alloc(1) # ============================================================ - # Phase 1b-pre: 预生成所有待翻译文件的 .vpi stub - # - # 依赖模块的 .vpi 必须在翻译前可用:1b 主循环按扫描顺序(非拓扑)翻译,若依赖模块后翻译则 LoadDepVpiIntoModule 读不到 .vpi - # → declare 未注入 → 跨模块调用的返回类型推断错误(如误推为 Ptr 而 define 返回 i8*)→ 运行时返回值截断崩溃。 - # ============================================================ + # Phase 1b-pre: pre-generate .vpi stubs for all files to translate (deps must exist first). p1bpre_failed: int = 0 dir_len_pv: sizet = string.strlen(temp_dir) - # 1b-pre 阶段总耗时起点(vpi 预生成) + # 1b-pre total-time start (vpi pre-generation) t_pv0: uint64 = w32.win32base.GetTickCount64() for i_pv in range(result.Count): entry_addr_pv: uint64 = uint64(result.Entries) + i_pv * entry_size @@ -633,7 +572,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt if use_reachable != 0: in_set_pv = StubMergerCollect._is_in_sha1_set(sha1_pv, reachable_set, reachable_count) else: in_set_pv = StubMergerCollect._is_in_sha1_set(sha1_pv, sha1_set, set_count) if in_set_pv == 0: continue - # vpi 已存在则跳过 + # Skip if the vpi already exists vpi_path_pv: str = StubMergerCollect._sliced_path(temp_dir, dir_len_pv, sha1_pv, "vpi") if vpi_path_pv is None: p1bpre_failed += 1 @@ -645,14 +584,14 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt stdlib.free(vpi_path_pv) continue stdlib.free(vpi_path_pv) - # 读取文件、解析 AST、生成 vpi + # Read the file, parse the AST, generate the vpi file_path_pv: str = entry_pv.Path f_pv: rawptr[fileio.File] = fileio.File(file_path_pv, fileio.MODE.R) if f_pv.closed: p1bpre_failed += 1 VLogger.error(f"[Phase 1b-pre] {file_path_pv} | 无法打开源文件", "Phase1") continue - # read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被静默截断 + # read_all's capacity includes the terminator (reads at most cap-1 bytes); longer files are truncated if f_pv.size() >= Phase1.SRC_BUF_SIZE: p1bpre_failed += 1 VLogger.error(f"[Phase 1b-pre] {file_path_pv} | 源文件超过 {Phase1.SRC_BUF_SIZE} 字节上限", "Phase1") @@ -684,10 +623,8 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt if tree_pv is None: stdlib.free(src_buf_pv) p1bpre_failed += 1 - # 解析失败:**日志行内联 行/列 + 原因**,并附完整 Python 风格 traceback - # (Traceback + File/行 + 源码行 + caret + SyntaxError: 消息;消息含 token - # 类型与前/后 token 附注,见 includes/ast/parser.vp 的 _record_parse_err_ctx - # / parse_tokens 兜底补记),与单文件管线 BuildPipeline 同一条上报路径。 + # Parse failure: the log line inlines line/col + reason, plus a full Python-style + # traceback (same reporting path as the single-file BuildPipeline). VLogger.error(f"[Phase 1b-pre] {file_path_pv} | AST 解析失败(第 {ast.get_parse_err_line()} 行第 {ast.get_parse_err_col()} 列: {ast.get_parse_err_msg()})", "Phase1") GenApi.dump_parse_traceback(file_path_pv) continue @@ -707,19 +644,18 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt p1bpre_failed += 1 VLogger.error(f"[Phase 1b-pre] {file_path_pv} | pyi 缓冲分配失败(OOM)", "Phase1") stdlib.free(src_buf_pv) - # [TEMP-DIAG] 1b-pre 每文件哨兵(同上):校验本文件窗口 [mark1, chk_depth) 内所有存活块的块尾 canary。 - # 同样必须在 reset_to(mark1) 之前(否则窗口被截断为空)。mark1 在 1b 段前捕获(本文件 L756),是 1b/1b-pre 的统一窗口起点。 - # 仅 --detection-deep(mb.deep_detect != 0)时执行,读法同上方 1a 哨兵。 + # [TEMP-DIAG] 1b-pre per-file sentinel (as above): check the tail canary of live blocks + # in [mark1, chk_depth). Must run before reset_to(mark1); only under --detection-deep. if mb.deep_detect != 0: chk_rc_pv: int = mb.check_alive_blocks(mark1) if chk_rc_pv != 0: VLogger.error(f"内存损坏(1b-pre 每文件后续, rc={chk_rc_pv}): {file_path_pv}", "Phase1") stdlib.exit(1) - # 回收本文件 AST(vpi 已写盘,AST 不再需要;mark1 后无其他持久数据) + # Reclaim this file's AST (the vpi is written; nothing else persists after mark1) mb.reset_to(mark1) - # 1b-pre 阶段总耗时(vpi 预生成) + # 1b-pre total time (vpi pre-generation) t_pv1: uint64 = w32.win32base.GetTickCount64() VLogger.info(f"1b-pre 总耗时: {t_pv1-t_pv0} ms (vpi 预生成)", "Phase1") @@ -727,18 +663,18 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt VLogger.error(f"Phase 1b-pre 有 {p1bpre_failed} 个文件 vpi 预生成失败(失败文件与原因见上方逐条 [Phase 1b-pre] 日志)", "Phase1") return 1 - # 遍历每个文件(拓扑序优先:父类所在文件先翻译,确保跨模块 vtable - # 生成时依赖模块的函数 TypeStr 已注册;topo_count==0 回退扫描顺序) + # Iterate each file (topo order preferred so base-class files translate first and their + # TypeStr is registered for cross-module vtables; fall back to scan order when topo_count==0). translated: int = 0 skipped: int = 0 failed: int = 0 - # 1b 阶段总耗时起点(全量翻译) + # 1b total-time start (full translation) t_b0: uint64 = w32.win32base.GetTickCount64() loop_n_b: int = result.Count if topo_count > 0: loop_n_b = topo_count for i in range(loop_n_b): - # 获取 entry(拓扑序:file_idx 从 topo_order 取,否则用扫描索引) + # Get the entry (topo: file_idx from topo_order, else the scan index) file_idx_b: int = i if topo_count > 0: od_addr_b: uint64 = uint64(topo_order) + sizet(i) * 4 @@ -755,7 +691,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt failed += 1 continue - # 检查 SHA1 是否在翻译集合中(拓扑序已过滤可达;仅回退扫描顺序时检查) + # Check whether the SHA1 is in the translate set (topo already filtered; only the fallback checks) in_set: int = 1 if topo_count == 0: in_set = 0 @@ -765,7 +701,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt skipped += 1 continue - # 构造 stub 路径: {temp_dir}/{sha1}.stub.ll + # Build the stub path: {temp_dir}/{sha1}.stub.ll dir_len: sizet = string.strlen(temp_dir) sha1_len: sizet = string.strlen(sha1) stub_path: str = StubMergerCollect._sliced_path(temp_dir, dir_len, sha1, "stub.ll") @@ -773,9 +709,8 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt failed += 1 continue - # 检查 stub 是否已存在(按需翻译:跳过已存在的) - # 例外:含 class 定义的模块必须翻译——类注册只在翻译时发生,增量编译跳过会导致跨模块类构造 find_struct_by_module 落空 → 返回类型 fail-fast。 - # 判据:扫描 1b-pre 生成的 vpi 是否有 "class " 行(vpi 为解析后存根,精确无注释/字符串误判)。 + # Skip already-existing stubs, except modules with class definitions: class registration + # only happens at translation, so skipping breaks cross-module find_struct_by_module. force_translate: int = 0 vpi_path_1b: str = StubMergerCollect._sliced_path(temp_dir, dir_len, sha1, "vpi") if vpi_path_1b is not None: @@ -803,22 +738,22 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt else: vf_1b.close() stdlib.free(vpi_path_1b) if force_translate == 1: - # 含 class:强制翻译(覆盖 stub/text,重新注册类) + # Has class: force translation (overwrite stub/text, re-register classes) rp: str = entry.RelPath VLogger.info(f"含 class 定义,强制翻译: {rp}(sha1={sha1})", "Phase1") else: - # 检查 stub 是否已存在(按需翻译:跳过已存在的) + # Skip already-existing stubs (on-demand translation) sf: rawptr[fileio.File] = fileio.File(stub_path, fileio.MODE.R) if not sf.closed: sf.close() - # 检查 text.ll 是否也存在(虚表扫描需要 text.ll) + # Check that text.ll exists too (vtable scan needs it) text_path: str = StubMergerCollect._sliced_path(temp_dir, dir_len, sha1, "text.ll") if text_path is not None: tf: rawptr[fileio.File] = fileio.File(text_path, fileio.MODE.R) if not tf.closed: tf.close() - # sidecar({sha1}.meta)与在线 FULL 产物({sha1}.full.ll)也必须存在: - # 缺失则重新翻译以补写(否则 Phase B+/C 的声明判定与编译拿不到数据)。 + # The sidecar ({sha1}.meta) and online FULL artifact ({sha1}.full.ll) must also exist; + # otherwise re-translate to regenerate them for Phase B+/C. meta_path_1b: str = StubMergerCollect._sliced_path(temp_dir, dir_len, sha1, "meta") meta_ok_1b: int = 0 if meta_path_1b is not None: @@ -840,20 +775,20 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt stdlib.free(stub_path) continue stdlib.free(text_path) - # text.ll 不存在,需要重新翻译 + # text.ll missing, re-translate rp: str = entry.RelPath VLogger.info(f"text.ll 不存在,重新翻译: {rp}(sha1={sha1})", "Phase1") else: - # stub 不存在,需要翻译 + # stub missing, translate rp: str = entry.RelPath VLogger.info(f"翻译: {rp}(sha1={sha1})", "Phase1") stdlib.free(stub_path) - # 单文件计时起点:读文件(parse/translate/dump 三段,对齐 Phase2 插桩) + # Per-file timer start: read (parse/translate/dump segments, matching Phase2 instrumentation) t_f0: uint64 = w32.win32base.GetTickCount64() - # 读取文件内容 + # Read file contents file_path: str = entry.Path f: rawptr[fileio.File] = fileio.File(file_path, fileio.MODE.R) if f is None: @@ -865,7 +800,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt failed += 1 continue - # read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被静默截断 + # read_all's capacity includes the terminator (reads at most cap-1 bytes); longer files are truncated if f.size() >= Phase1.SRC_BUF_SIZE: VLogger.error(f"源文件超过 {Phase1.SRC_BUF_SIZE} 字节上限, 拒绝读取: {file_path}", "Phase1") f.close() @@ -887,7 +822,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt continue if bytes_read < Phase1.SRC_BUF_SIZE: src_buf[bytes_read] = 0 - # 解析 AST + # Parse AST lx: ast.Lexer* = ast.new_lexer(mb) if lx is None: stdlib.free(src_buf) @@ -897,23 +832,22 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt tokens: ast.Token* = ast.tokenize(lx) tree: ast.AST* = ast.parse_tokens(mb, tokens) if tree is None: - # 解析失败:行/列 + 原因一并进日志行(原因由 parser.vp 的 _record_* / - # parse_tokens 兜底补记提供) + # Parse failure: line/col + reason go into the log line (reason supplied by parser.vp's + # _record_* / parse_tokens fallback). VLogger.error(f"AST 解析失败: {file_path}(第 {ast.get_parse_err_line()} 行第 {ast.get_parse_err_col()} 列: {ast.get_parse_err_msg()})", "Phase1") stdlib.free(src_buf) failed += 1 continue - # 计时点 1:读文件 + lexer/tokenize/parse 完成 + # Timer 1: read + lexer/tokenize/parse done t_f1: uint64 = w32.win32base.GetTickCount64() - # 文本扫描注册 CTypedef:AST 字符串字段(attr/id)token 零拷贝指向 src_buf,跨阶段内容不稳定(悬空),读 attr 会 0xC0000005; - # 故改用源码文本逐行匹配 "NAME: t.CTypedef = TYPE",在 src_buf 有效期内注册。 + # Register CTypedef by scanning source text: AST string fields (attr/id) point into src_buf + # and dangle across phases (reading attr faults), so match "NAME: t.CTypedef = TYPE" lines. GenApi.RegisterCTypedefFromSource(mb, src_buf) - # 生成 .vpi 存根文件(直接遍历 AST,不依赖 PythonToStubConverter) - # 1b-pre 已为所有待翻译文件生成 vpi,此处仅兜底:vpi 缺失才生成 - # (消除 1b-pre 与 1b 对同一文件的重复 _GeneratePyiFromAst 工作) + # Generate the .vpi stub (walk the AST directly). 1b-pre already generated one, so this + # is only a fallback when the vpi is missing. vpi_path_1b: str = StubMergerCollect._sliced_path(temp_dir, dir_len, sha1, "vpi") if vpi_path_1b is not None: vf_1b: rawptr[fileio.File] = fileio.File(vpi_path_1b, fileio.MODE.R) @@ -933,7 +867,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt stdlib.free(pyi_buf) stdlib.free(vpi_path_1b) - # 翻译 AST → LLVM IR + # Translate AST -> LLVM IR tr: rawptr[HandlesTranslator.Translator] = HandlesTranslator.Translator() if tr is None: stdlib.free(src_buf) @@ -942,10 +876,8 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt tr.ModuleSha1 = sha1 tr.CurrentPackage = GenApi.compute_package_from_relpath(mb, entry.RelPath) GenApi.set_current_file_type(file_path) - # --traceback: mirror the DWARF switch into the Gen-layer emitter (the - # same BuildPipeline._traceback_mode that gates -frame-pointer=all / - # dbghelp / the Install() ctor). include/*.vp modules are compiled here, - # so this is what gives their frames DWARF line info. + # --traceback: mirror the DWARF switch into the Gen-layer emitter (same + # BuildPipeline._traceback_mode that gates -frame-pointer=all / dbghelp / Install()). DebugInfoGen.SetEnabled(BuildPipeline._traceback_mode) GenApi.set_current_file_imports(file_path) GenApi.set_current_module_sha1(sha1) @@ -953,44 +885,40 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt GenApi.clear_ctypedefs() GenApi.reset_visible_structs(mb, 0) - # 翻译前哨兵:AST 解析(lexer/tokenize/parse_tokens)同样在 pool 分配, - # 若解析器越界写破坏了存活块 header,此处立即定位到当前文件 - # (崩溃点漂移排查:把"翻译过程中随机崩"变成确定性定位) + # Pre-translate sentinel: parsing also allocates from the pool, so a parser overrun that + # corrupts a live block header is pinned to the current file (deterministic crash location). pre_rc: int = mb.check_alive_blocks(mark1) if pre_rc != 0: VLogger.error(f"内存损坏(翻译前存活块, rc={pre_rc}): {file_path}", "Phase1") stdlib.exit(1) ret: int = tr.translate(tree) - # 计时点 2:translate 返回 + # Timer 2: translate returned t_f2: uint64 = w32.win32base.GetTickCount64() - # 诊断:translate 正常返回(若挂起在 translate 内部,此行不会打印, - # 用于区分"translate 内部挂起"与"后续 dump 挂起") + # Diagnostic: translate returned normally (if it hangs inside, this line is not printed, + # distinguishing an internal hang from a later dump hang). if ret == 0: VLogger.info(f"翻译完成(translate返回): {file_path}", "Phase1") if ret != 0: - VLogger.error(f"翻译失败: {file_path}", "Phase1") # error 终止 + VLogger.error(f"翻译失败: {file_path}", "Phase1") # error terminates stdlib.free(src_buf) if tr._global_names is not None: stdlib.free(tr._global_names) if tr._nonlocal_names is not None: stdlib.free(tr._nonlocal_names) failed += 1 continue - # --traceback: inject the startup Install() ctor when translating traceback.vp - # (its stub/text.ll below are what Phase2 reuses to build the .obj). The - # injector lives in the Gen layer (already depends on mlirlite), so this - # module gains no cross-module mlirlite symbol dependency. + # --traceback: inject the startup Install() ctor when translating traceback.vp; + # the injector lives in the Gen layer, adding no cross-module mlirlite dependency. if BuildPipeline._traceback_mode != 0 and BuildPipeline._tb_basename_is_traceback(file_path) != 0: HandlesTranslator.InjectTracebackInstallCtor(tr) - # 诊断:pre_rc 哨兵在 translate 前、覆盖不到 translate 期间的 AST/IR 写穿,故 dump 前立即检查存活块 header/canary - # (rc=6 块尾 canary 损坏 = 越界写穿数据区),把「dump 时链表成环卡死」变成「定位到 translate 期间写穿发生的文件」。 + # Diagnostic: the pre-translate sentinel misses write-throughs during translate, so check + # live block headers/canaries before dump (rc=6 means a tail canary was overwritten). post_rc: int = mb.check_alive_blocks(mark1) if post_rc != 0: VLogger.error(f"内存损坏(translate后存活块, rc={post_rc}): {file_path}", "Phase1") - # dump stub IR(declarations only) - # 1MB 不够:ast/parser.vp 的 text.ll 超 1MB 被截断 → 合并 IR 尾部丢字 - # (opt: "end of file in global variable name"),提至 8MB。 + # Dump stub IR (declarations only). 1MB is not enough: ast/parser.vp's text.ll exceeds it + # and is truncated (opt: "end of file in global variable name"), so use 8MB. PHASE1_IR_SIZE: sizet = 8388608 - # FULL dump = stub + text(同一模块),留 2× 余量 + # FULL dump = stub + text (same module), with 2x headroom PHASE1_FULL_SIZE: sizet = 16777216 stub_buf: bytes = stdlib.malloc(PHASE1_IR_SIZE) if stub_buf is None: @@ -1034,11 +962,11 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt stdlib.free(text_path_p1) stdlib.free(text_buf) - # save {sha1}.meta(结构化 sidecar:函数清单/结构体布局/虚表,替代从 IR 文本反推) + # save {sha1}.meta (structured sidecar: function list/struct layout/vtables, no IR-text reverse-engineering) StubMergerIR.WriteModuleMeta(tr.Module, temp_dir, dir_len_p1, sha1) - # save {sha1}.full.ll(在线 FULL 产物:模型在线时的自足单模块 IR,供 Phase B 直接编译, - # 取代 stub + 依赖 stub/text 文本合并) + # save {sha1}.full.ll (the online FULL artifact: a self-contained single-module IR for + # Phase B to compile directly, replacing the stub + dependency text merge). full_buf_p1: bytes = stdlib.malloc(PHASE1_FULL_SIZE) if full_buf_p1 is None: VLogger.error("full.ll 缓冲区分配失败", "Phase1") tr.dump_ir(full_buf_p1, PHASE1_FULL_SIZE, mlirlite.OUTPUT_FULL) @@ -1052,15 +980,15 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt stdlib.free(full_path_p1) stdlib.free(full_buf_p1) - # --traceback: 取出本模块的调试调用点记录(mlirlite 累积)并写 .vpcall, - # 供链接后 WriteStackMap 结合 objdump 还原调用点行号。关闭时不产生任何旁文件。 + # --traceback: take this module's debug call-site records (accumulated in mlirlite) and + # write .vpcall for WriteStackMap to reconstruct call-site lines. No file when off. if BuildPipeline._traceback_mode != 0: vpcall_text_p1: str = mlirlite.DebugTakeText() if vpcall_text_p1 is not None: BuildPipeline.WriteCallSidecar(temp_dir, sha1, vpcall_text_p1) stdlib.free(vpcall_text_p1) - # 计时点 3:dump(stub+text)完成;输出单文件三段耗时 + # Timer 3: dump (stub+text) done; print the per-file three-segment timing t_f3: uint64 = w32.win32base.GetTickCount64() VLogger.info(f"[{i+1}/{loop_n_b}] 翻译: {file_path} (parse={t_f1-t_f0} ms, translate={t_f2-t_f1} ms, dump={t_f3-t_f2} ms, total={t_f3-t_f0} ms)", "Phase1") @@ -1075,33 +1003,30 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt df_p1.close() stdlib.free(deps_path_p1) - # 释放 Translator 的 C malloc 资源 + # Release the Translator's C malloc resources if tr._global_names is not None: stdlib.free(tr._global_names) if tr._nonlocal_names is not None: stdlib.free(tr._nonlocal_names) - # 所有 dump_ir 完成,src_buf 不再需要 + # All dump_ir calls done, src_buf no longer needed stdlib.free(src_buf) translated += 1 - # OOM 回收:回收本文件翻译产生的瞬时数据(AST/tokens/lexer/IR/Handler/dump 临时);持久表(struct/enum/REnum/spec 缓存/全局函数名等)已迁出 reset 区间(stdlib), - # reset_to(mark1) 只回收 mark1 之后分配、未被单独 free 的块。 - # - # 内存哨兵(崩溃点漂移排查):reset 前检查存活块 header(抓越界写),reset 后检查 free list 完整性(抓链表损坏);任一损坏立即终止,把「随机崩溃漂移」变成「确定性定位到当前文件」。 - # rc 语义(check_alive_blocks):1=块地址低于 usable,2=高于 usable+size,3=order 为负,4=order 超 max_order,5=块未对齐到 order 大小,6=块尾 canary 损坏(越界写穿数据区) - # rc 语义(self_check):1/2=越界,3=对齐错,4=free list 成环/超步数 + # OOM reclamation: free this file's transient AST/tokens/lexer/IR/dump data (persistent + # tables moved to stdlib). Memory sentinels locate overruns deterministically. chk_rc: int = mb.check_alive_blocks(mark1) if chk_rc != 0: VLogger.error(f"内存损坏(存活块 header, rc={chk_rc}): {file_path}", "Phase1") - # 越界写是崩溃根因(与回收/UAF 无关,已由 A/B 实验确认)——恢复 reset_to,改用块尾 canary(check_alive_blocks 校验)把越界写事发地定位到具体文件。 + # Out-of-bounds writes are the crash root cause (unrelated to reclamation/UAF, confirmed + # by A/B tests); reset_to plus the tail canary pins the offending file. mb.reset_to(mark1) sc_rc: int = mb.self_check() if sc_rc != 0: VLogger.error(f"内存损坏(free list, rc={sc_rc}): {file_path}", "Phase1") - # 1b 阶段总耗时(全量翻译 + dump) + # 1b total time (full translation + dump) t_b1: uint64 = w32.win32base.GetTickCount64() VLogger.info(f"1b 总耗时: {t_b1-t_b0} ms (全量翻译, trans={translated} skip={skipped} fail={failed})", "Phase1") - # 释放 Phase 1b 使用的拓扑排序资源 + # Release the topo-sort resources used by Phase 1b if topo_order is not None: mb.free(topo_order) if topo_count_box is not None: mb.free(topo_count_box) @@ -1111,7 +1036,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: rawpt VLogger.error(f"Phase1 有 {failed} 个文件翻译失败", "Phase1") return 1 - # 释放可达 SHA1 集合(如果分配了) + # Release the reachable SHA1 set (if allocated) if reachable_set is not None: stdlib.free(reachable_set) return 0 diff --git a/App/lib/Phase/Phase1/Phase1Scan.vp b/App/lib/Phase/Phase1/Phase1Scan.vp index 06fd3bc..1c0829b 100644 --- a/App/lib/Phase/Phase1/Phase1Scan.vp +++ b/App/lib/Phase/Phase1/Phase1Scan.vp @@ -31,41 +31,28 @@ import lib.Phase.Phase1 as Phase1 import lib.core.Gen.GenApi as GenApi -# ============================================================ -# Phase1Scan - 扫描类继承关系、泛型类标记与容器使用 -# -# 函数实现直接位于本模块,调用方经本模块前缀访问;全局符号经 Phase1._xxx 跨模块访问。 -# ============================================================ +# Phase1Scan - scan class inheritance, generic-class markers and container usage. +# Functions live in this module; globals are accessed cross-module via Phase1._xxx. -# 拓扑排序使用扁平字节数组存储类信息和文件依赖, -# 避免结构体中的 str 指针字段在 mb.alloc 零填充时为 NULL 导致崩溃。 -# 类名/父类名存储在 class_names_buf/parent_names_buf(每个 Phase1.CLASS_NAME_LEN 字节), -# 定义 SHA1 存储在 def_sha1s_buf(每个 Phase1.SHA1_LEN 字节)。 +# Topological sort stores class info/deps in flat byte arrays (names in +# class_names_buf/parent_names_buf, SHA1s in def_sha1s_buf) to avoid NULL str fields. -# ============================================================ -# _ScanClassInheritance - 预扫描 includes 文件,收集类继承关系 -# -# 遍历所有可达 includes 文件解析 AST,收集 ClassDef 的类名与父类名存入扁平字节数组,返回收集到的类数量。 -# -# Args: mb 内存池;result IncludesScanner 扫描结果;reachable_set 可达 SHA1 集合;reachable_count 可达 SHA1 数量;use_reachable 是否使用可达集合过滤; -# sha1_set SHA1 集合(use_reachable=0 时使用);set_count SHA1 集合数量;class_names_buf 类名扁平数组(每个 Phase1.CLASS_NAME_LEN 字节); -# parent_names_buf 父类名扁平数组(每个 Phase1.CLASS_NAME_LEN 字节,无父类填 '\0');def_sha1s_buf 定义 SHA1 扁平数组(每个 Phase1.SHA1_LEN 字节) -# Returns: 收集到的类数量,-1 失败 -# ============================================================ +# _ScanClassInheritance - pre-scan includes files, parse ASTs and collect ClassDef +# names/parent names into flat byte arrays; returns the class count (-1 on failure). def _ScanClassInheritance(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.ScanResult], reachable_set: str, reachable_count: int, use_reachable: int, sha1_set: str, set_count: int, class_names_buf: bytes, parent_names_buf: bytes, def_sha1s_buf: bytes, generic_file_indices: t.CPtr, generic_file_count_box: t.CPtr) -> int: - """预扫描 includes 文件,收集类继承关系""" + """Pre-scan includes files and collect class inheritance info.""" if result is None: return -1 - # class_names_buf/parent_names_buf/def_sha1s_buf 由调用方 mb.alloc 分配(Phase1Main 预扫描前),本函数把其地址直接作为 strcpy 目的缓冲; - # 分配失败返回 NULL 时目的指针为近零地址 → strcpy 写零页(ASan access-violation),故按 fail-fast 显式报错终止(VLogger.error 内部 sys.exit(1)),不静默返回 -1。 + # The three flat buffers are mb.alloc'ed by the caller; a NULL buffer would make + # strcpy write the zero page, so fail-fast abort instead of returning -1 silently. if class_names_buf is None or parent_names_buf is None or def_sha1s_buf is None: VLogger.error("类继承预扫描扁平缓冲分配失败(class_names_buf/parent_names_buf/def_sha1s_buf 为 NULL)", "Phase1") entry_size: sizet = IncludesScanner.FileEntry.__sizeof__() class_count: int = 0 for i in range(result.Count): - # 文件级 mark:收集完类继承信息后立即回收本文件 AST(可达 includes 全量 parse 的 AST 若驻留到 mark1 会耗尽 1GB 主池 OOM); - # class_names_buf/parent_names_buf/def_sha1s_buf 与 generic_file_indices 在调用前分配、位于本 mark 之前,reset 不影响。 + # Per-file mark: reclaim this file's AST right after collecting inheritance + # info (keeping all ASTs alive until mark1 would OOM the 1GB main pool). scan_mark: sizet = mb.mark() entry_addr: uint64 = uint64(result.Entries) + i * entry_size entry: rawptr[IncludesScanner.FileEntry] = rawptr[IncludesScanner.FileEntry](entry_addr) @@ -74,18 +61,18 @@ def _ScanClassInheritance(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.S sha1_e: str = entry.Sha1 file_has_generic: int = 0 - # 按需翻译过滤 + # on-demand translation filter in_set: int = 0 if use_reachable != 0: in_set = StubMergerCollect._is_in_sha1_set(sha1_e, reachable_set, reachable_count) else: in_set = StubMergerCollect._is_in_sha1_set(sha1_e, sha1_set, set_count) if in_set == 0: continue - # 读取文件 + # read the file file_path: str = entry.Path f: rawptr[fileio.File] = fileio.File(file_path, fileio.MODE.R) if f.closed: continue - # read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被静默截断 + # read_all's capacity includes the terminator (reads at most cap-1 bytes); longer files are truncated if f.size() >= Phase1.SRC_BUF_SIZE: VLogger.error(f"源文件超过 {Phase1.SRC_BUF_SIZE} 字节上限, 拒绝读取: {file_path}", "Phase1") f.close() @@ -103,7 +90,7 @@ def _ScanClassInheritance(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.S continue if bytes_read < Phase1.SRC_BUF_SIZE: src_buf[bytes_read] = 0 - # 解析 AST + # parse the AST lx: ast.Lexer* = ast.new_lexer(mb) if lx is None: stdlib.free(src_buf) @@ -115,7 +102,7 @@ def _ScanClassInheritance(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.S stdlib.free(src_buf) continue - # 遍历 AST 顶层节点,收集 ClassDef + # walk top-level AST nodes, collect ClassDef ch: list[ast.AST*]* = tree.children if ch is not None: cn: sizet = len(ch) @@ -128,7 +115,7 @@ def _ScanClassInheritance(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.S cd: ast.ClassDef* = ast.ClassDef*(child) if cd is None or cd.name is None: continue - # 检测泛型类(有 type_params),记录文件索引以供 Phase 1a 预注册 + # detect generic classes (type_params); record the file index for Phase 1a if file_has_generic == 0: tp_scan: list[str]* = cd.type_params if tp_scan is not None: @@ -145,7 +132,7 @@ def _ScanClassInheritance(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.S if class_count >= Phase1.MAX_CLASSES: break - # 填充扁平数组:类名(偏移 = class_count * Phase1.CLASS_NAME_LEN) + # fill flat array: class name (offset = class_count * Phase1.CLASS_NAME_LEN) name_dst: str = class_names_buf + class_count * Phase1.CLASS_NAME_LEN nm_len: sizet = string.strlen(cd.name) if nm_len >= Phase1.CLASS_NAME_LEN: @@ -153,11 +140,11 @@ def _ScanClassInheritance(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.S name_dst[Phase1.CLASS_NAME_LEN - 1] = '\0' else: string.strcpy(name_dst, cd.name) - # 填充扁平数组:父类名(无父类填 '\0') + # fill flat array: parent name (no parent -> '\0') parent_dst: str = parent_names_buf + class_count * Phase1.CLASS_NAME_LEN parent_dst[0] = '\0' parent_nm: str = GenApi._get_parent_class(cd, None) - # parent_nm 是 AST 节点内部指针,不需要释放 + # parent_nm is an internal AST pointer; no need to free if parent_nm is not None: pnm_len: sizet = string.strlen(parent_nm) if pnm_len >= Phase1.CLASS_NAME_LEN: @@ -165,92 +152,75 @@ def _ScanClassInheritance(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.S parent_dst[Phase1.CLASS_NAME_LEN - 1] = '\0' else: string.strcpy(parent_dst, parent_nm) - # 填充扁平数组:定义 SHA1(偏移 = class_count * Phase1.SHA1_LEN) + # fill flat array: defining SHA1 (offset = class_count * Phase1.SHA1_LEN) sha1_dst: str = def_sha1s_buf + class_count * Phase1.SHA1_LEN string.strcpy(sha1_dst, sha1_e) class_count += 1 stdlib.free(src_buf) - # 回收本文件 AST/lexer/token(类名/父类/SHA1 已拷入扁平数组) + # reclaim this file's AST/lexer/token (names/SHA1 already copied into flat arrays) mb.reset_to(scan_mark) return class_count -# ============================================================ -# _BufHasGenericClassMarker - 文本扫描检测文件是否含泛型类标记 -# -# 泛型类(PEP 695)语法: class Name[T]:(类名后紧跟 '[')。逐字节扫描找词边界 'class' 关键字,其后标识符后非空白是 '[' → 命中。 -# 用于 _PreRegisterAllGenericTemplates 预注册前筛选,避免 parse 全部 98 个 includes 致 AST 驻留 mark1 前把 pool 推到 OOM。 -# 误报(字符串/注释里的 "class X[")无害(多 parse 一个文件);漏报有害(泛型模板未预注册 → 悬空复发),故保守只匹配单行形式。 -# ============================================================ +# _BufHasGenericClassMarker - text scan for a generic-class marker (PEP 695 "class +# Name["). Used to pre-filter files before registering generic templates. def _BufHasGenericClassMarker(buf: bytes, blen: sizet) -> int: - """文本扫描:buf 中是否有 'class 标识符[' 泛型类标记,返回 1/0""" + """Return 1 if buf contains a 'class identifier[' generic-class marker, else 0.""" if buf is None or blen <= 6: return 0 pos: sizet = 0 while pos + 5 < blen: if buf[pos] == 'c' and buf[pos + 1] == 'l' and buf[pos + 2] == 'a' and buf[pos + 3] == 's' and buf[pos + 4] == 's': - # class 前是词边界(行首或非标识符字符) + # 'class' preceded by a word boundary (line start or non-identifier) prev_ok: int = 1 if pos > 0: pc: char = buf[pos - 1] if(pc >= 'a' and pc <= 'z') or(pc >= 'A' and pc <= 'Z') or(pc >= '0' and pc <= '9') or pc == '_': prev_ok = 0 if prev_ok != 0: p: sizet = pos + 5 - # 跳过空白 + # skip whitespace while p < blen and(buf[p] == ' ' or buf[p] == '\t'): p += 1 - # 读类名标识符 + # read the class-name identifier if p < blen and((buf[p] >= 'a' and buf[p] <= 'z') or(buf[p] >= 'A' and buf[p] <= 'Z') or buf[p] == '_'): while p < blen and((buf[p] >= 'a' and buf[p] <= 'z') or(buf[p] >= 'A' and buf[p] <= 'Z') or(buf[p] >= '0' and buf[p] <= '9') or buf[p] == '_'): p += 1 - # 类名后非空白是 '[' → 泛型类 + # non-space after the name is '[' -> generic class while p < blen and(buf[p] == ' ' or buf[p] == '\t'): p += 1 if p < blen and buf[p] == '[': return 1 pos += 1 else: pos += 1 return 0 -# ============================================================ -# _BufHasGenericFuncMarker - 文本扫描检测文件是否含泛型函数标记 -# -# 泛型函数(PEP 695)语法: def name[(函数名后紧跟 '[',如 def f[T](...):)。 -# 与 _BufHasGenericClassMarker 同款逐字节词边界扫描;用于泛型函数模板的 -# 轻量预筛(避免为无泛型函数的文件 parse AST)。误报无害、漏报有害(模板未 -# 预注册),故保守只匹配单行形式。 -# ============================================================ +# _BufHasGenericFuncMarker - text scan for a generic-function marker (PEP 695 +# "def name["); cheap pre-filter before parsing for generic function templates. def _BufHasGenericFuncMarker(buf: bytes, blen: sizet) -> int: - """文本扫描:buf 中是否有 'def 标识符[' 泛型函数标记,返回 1/0""" + """Return 1 if buf contains a 'def identifier[' generic-function marker, else 0.""" if buf is None or blen <= 4: return 0 pos: sizet = 0 while pos + 3 < blen: if buf[pos] == 'd' and buf[pos + 1] == 'e' and buf[pos + 2] == 'f': - # def 前是词边界(行首或非标识符字符) + # 'def' preceded by a word boundary (line start or non-identifier) prev_ok: int = 1 if pos > 0: pc: char = buf[pos - 1] if(pc >= 'a' and pc <= 'z') or(pc >= 'A' and pc <= 'Z') or(pc >= '0' and pc <= '9') or pc == '_': prev_ok = 0 if prev_ok != 0: p: sizet = pos + 3 - # 跳过空白 + # skip whitespace while p < blen and(buf[p] == ' ' or buf[p] == '\t'): p += 1 - # 读函数名标识符 + # read the function-name identifier if p < blen and((buf[p] >= 'a' and buf[p] <= 'z') or(buf[p] >= 'A' and buf[p] <= 'Z') or buf[p] == '_'): while p < blen and((buf[p] >= 'a' and buf[p] <= 'z') or(buf[p] >= 'A' and buf[p] <= 'Z') or(buf[p] >= '0' and buf[p] <= '9') or buf[p] == '_'): p += 1 - # 函数名后非空白是 '[' → 泛型函数 + # non-space after the name is '[' -> generic function while p < blen and(buf[p] == ' ' or buf[p] == '\t'): p += 1 if p < blen and buf[p] == '[': return 1 pos += 1 else: pos += 1 return 0 -# ============================================================ -# _PreRegisterAllGenericTemplates - 预注册 includes 泛型类模板(mark1 前) -# -# 泛型模板表(GenericClassNames/GenericClassNodes/GenericClassSha1s/GenericClassFiles + 模板节点)是懒分配的跨文件持久表, -# 首次注册必须落在 mark1 之前才能跨 Phase1 1b(reset_to(mark1))与 Phase2(reset_to(mark2))存活;否则表与节点被 reset 回收, -# _find_generic_template 遍历悬空内存 strcmp 崩溃。本函数先文本扫描(_BufHasGenericClassMarker,不 parse)筛选出含泛型类的文件, -# 仅对命中文件用**永久池(perm_pool)**解析(模板节点与表直接进 perm_pool,跨 mark 永久存活、不占主 pool);重复注册由 _register_generic_template 内部查重跳过。 -# ============================================================ +# _PreRegisterAllGenericTemplates - pre-register includes generic templates before +# mark1 so the lazy tables survive later resets; parses only marker-matched files. def _PreRegisterAllGenericTemplates(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.ScanResult]) -> int: - """预注册全部 includes 文件的泛型类模板,返回处理到的泛型类数量""" + """Pre-register generic templates from all includes files and return the count.""" if result is None or mb is None: return 0 perm: memhub.MemBuddy* = CompilerFactory.factory.PermPool if perm is None: return 0 @@ -260,11 +230,11 @@ def _PreRegisterAllGenericTemplates(mb: memhub.MemBuddy*, result: rawptr[Include entry_addr_pg: uint64 = uint64(result.Entries) + i * p1e_size entry_pg: rawptr[IncludesScanner.FileEntry] = (rawptr[IncludesScanner.FileEntry])(entry_addr_pg) if entry_pg is None or entry_pg.Sha1 is None: continue - # 读取文件 + # read the file file_path_pg: str = entry_pg.Path f_pg: rawptr[fileio.File] = fileio.File(file_path_pg, fileio.MODE.R) if f_pg.closed: continue - # read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被静默截断 + # read_all's capacity includes the terminator (reads at most cap-1 bytes); longer files are truncated if f_pg.size() >= Phase1.SRC_BUF_SIZE: VLogger.error(f"源文件超过 {Phase1.SRC_BUF_SIZE} 字节上限, 拒绝读取: {file_path_pg}", "Phase1") f_pg.close() @@ -279,12 +249,12 @@ def _PreRegisterAllGenericTemplates(mb: memhub.MemBuddy*, result: rawptr[Include stdlib.free(src_buf_pg) continue if bytes_read_pg < Phase1.SRC_BUF_SIZE: src_buf_pg[bytes_read_pg] = 0 - # 文本扫描筛选:非泛型文件跳过(不 parse、不驻留,防 OOM)。 - # 泛型类(class X[)与泛型函数(def f[)任一命中即进入 parse。 + # Text-scan filter: skip non-generic files (no parse, no residency, avoids + # OOM). A class or function marker hit selects the file for parsing. if _BufHasGenericClassMarker(src_buf_pg, bytes_read_pg) == 0 and _BufHasGenericFuncMarker(src_buf_pg, bytes_read_pg) == 0: stdlib.free(src_buf_pg) continue - # 泛型文件:永久池解析 → 注册模板(节点/表进 perm_pool,跨 reset 存活) + # generic file: parse in perm_pool and register templates (survives reset) lx_pg: ast.Lexer* = ast.new_lexer(perm) if lx_pg is None: stdlib.free(src_buf_pg) @@ -295,7 +265,7 @@ def _PreRegisterAllGenericTemplates(mb: memhub.MemBuddy*, result: rawptr[Include if tree_pg is None: stdlib.free(src_buf_pg) continue - # 遍历顶层 ClassDef/FunctionDef,注册泛型模板(type_params 非 None) + # walk top-level ClassDef/FunctionDef, register generic templates (type_params != None) ch_pg: list[ast.AST*]* = tree_pg.children if ch_pg is not None: cn_pg: sizet = len(ch_pg) @@ -323,33 +293,20 @@ def _PreRegisterAllGenericTemplates(mb: memhub.MemBuddy*, result: rawptr[Include if handled_count > 0: VLogger.info(f"mark1 前永久池预注册 {handled_count} 个 includes 泛型模板(类+函数)", "Phase1") else: VLogger.info(f"mark1 前永久池预注册 0 个泛型模板(扫描 {result.Count} 个文件,可能 marker 未命中或全量扫描失败)", "Phase1") return handled_count -# ============================================================ -# _FindClassDefSha1 - 在扁平数组中查找类名对应的定义文件 SHA1 -# -# Args: class_names_buf 类名扁平数组;def_sha1s_buf 定义 SHA1 扁平数组;class_count 类数量;class_name 要查找的类名 -# Returns: SHA1 字符串指针(指向 def_sha1s_buf 内部偏移),None 未找到 -# ============================================================ +# _FindClassDefSha1 - look up a class name's defining file SHA1 in the flat arrays; +# returns a pointer into def_sha1s_buf, or None if not found. def _FindClassDefSha1(class_names_buf: bytes, def_sha1s_buf: bytes, class_count: int, class_name: str) -> str: - """查找类名对应的定义文件 SHA1""" + """Find a class name's defining file SHA1 in the flat arrays.""" if class_names_buf is None or def_sha1s_buf is None or class_name is None: return None for i in range(class_count): name_ptr: str = class_names_buf + i * Phase1.CLASS_NAME_LEN if string.strcmp(name_ptr, class_name) == 0: return def_sha1s_buf + i * Phase1.SHA1_LEN return None -# ============================================================ -# _ScanTreeForContainers - 递归扫描 AST 检测 list/dict 容器使用 -# -# TPV 的 AST 中语句块挂在 children,而表达式/注解树全部以结构体字段挂载(value/slice/func/args/annotation/targets/returns/bases/left/right 等); -# 只递归 children 会漏掉 list[...]*、list[...](...)、字段注解等非语句块位置,故每个节点先递归表达式承载字段、再递归 children。 -# 命中即置 need_list/need_dict;误报无害(最多多编译两个空壳模块)。 -# ============================================================ +# _ScanTreeForContainers - recursively scan the AST for list/dict container usage: +# recurse expression/annotation struct fields first, then statement-block children. def _AnnIsContainer(ann: ast.AST*) -> int: - """注解是否为 list/dict 容器(Name 裸名或 Subscript list[...]/dict[...]) - - 数组初始化(t.CArray[...] / 裸数组注解)返回 0,避免 _ScanTreeForContainers - 把数组字面量误判为 list 容器使用(FIX 2026-08-30,联动 Translator.vp 同修)。 - """ + """Return 1 if the annotation is a list/dict container, else 0.""" while ann is not None: ak_c: int = ann.kind() if ak_c == ast.ASTKind.Name: @@ -366,9 +323,9 @@ def _AnnIsContainer(ann: ast.AST*) -> int: def _ScanTreeForContainers(node: ast.AST*, need_list_box: t.CPtr, need_dict_box: t.CPtr, depth: int = 0): - """递归扫描 AST(字段 + 语句块),检测 list/dict 容器使用""" - # 递归深度守卫:AST 损坏成自引用环(字段/children 回指祖先)时无限 - # 递归直至栈耗尽(每个 includes 文件 parse 后必执行)。fail-fast 终止。 + """Recursively scan the AST for list/dict container usage.""" + # Recursion depth guard: a self-referential AST cycle would recurse until the + # stack is exhausted; fail-fast abort. if depth > 128: VLogger.error("容器扫描递归过深(AST 环或嵌套超过 128 层)", "Phase1") if node is None: return k_s: int = node.kind() @@ -465,13 +422,8 @@ def _ScanTreeForContainers(node: ast.AST*, need_list_box: t.CPtr, need_dict_box: nv_s: sizet = len(bvs_s) i_v2: sizet for i_v2 in range(nv_s): _ScanTreeForContainers(bvs_s.get(i_v2), need_list_box, need_dict_box, depth + 1) - # `[...]`/`{}` 字面量不触发 _list/_dict 导入:字面量广泛用于编译期常量列表(c.Asm 的 op/inp/out CLOBBER 参数)与数组数据初始化,并非容器使用; - # 容器的真实使用由类型位置检测覆盖(Name/Subscript 注解、Call.func 构造名)。 - # elif k_s == ast.ASTKind.List: - # need_list_box[0] = 1 - # elif k_s == ast.ASTKind.Dict: - # need_dict_box[0] = 1 - # 递归语句块子节点 + # `[...]`/`{}` literals do not trigger _list/_dict imports (compile-time constant lists, array init); + # recurse into statement-block children. if node.children is not None: ch_s: list[ast.AST*]* = node.children n_s: sizet = len(ch_s) diff --git a/App/lib/Phase/Phase1/Phase1Topo.vp b/App/lib/Phase/Phase1/Phase1Topo.vp index af91dad..7ab6c51 100644 --- a/App/lib/Phase/Phase1/Phase1Topo.vp +++ b/App/lib/Phase/Phase1/Phase1Topo.vp @@ -27,32 +27,19 @@ import lib.Phase.Phase1.Phase1Scan as Phase1Scan import lib.Phase.Phase1.Phase1Topo as Phase1Topo import lib.Phase.Phase1 as Phase1 -# ============================================================ -# Phase1Topo - 按类继承与 import 依赖对源文件做拓扑排序 -# -# 函数实现直接位于本模块,调用方经本模块前缀访问;全局符号经 Phase1._xxx 跨模块访问。 -# ============================================================ +# Phase1Topo - topologically sort source files by class inheritance and import dependencies. +# Functions live in this module (accessed via its prefix); globals go through Phase1._xxx. -# ============================================================ -# _TopoSortFiles - 根据类继承关系对文件进行拓扑排序(Kahn) -# -# 步骤:1) 从扁平类信息数组构建「文件 SHA1 → 依赖文件 SHA1 集合」映射;2) 计算每个文件入度(DepCount,初始化 InDegree = DepCount); -# 3) 入度为 0 的先处理,处理后减少依赖它的文件入度;4) 重复直到全部处理完成。 -# 依赖方向是「父类所在文件 → 子类所在文件」,即父类文件必须先处理。 -# -# Args: mb 内存池;result IncludesScanner 扫描结果;reachable_set 可达 SHA1 集合;reachable_count 可达 SHA1 数量;use_reachable 是否用可达集合过滤; -# sha1_set SHA1 集合;set_count SHA1 集合数量;class_names_buf 类名扁平数组;parent_names_buf 父类名扁平数组;def_sha1s_buf 定义 SHA1 扁平数组; -# class_count 类数量;out_order 输出拓扑顺序的文件索引数组(调用者分配,int 数组);out_count 输出文件数量 -# Returns: 0 成功,-1 失败 -# ============================================================ +# _TopoSortFiles - topologically sort files by class inheritance (Kahn). +# Builds a "file SHA1 -> dependency SHA1 set" map, computes in-degrees (parent-file first), then outputs the order; returns 0 or -1. def _TopoSortFiles(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.ScanResult], reachable_set: str, reachable_count: int, use_reachable: int, sha1_set: str, set_count: int, class_names_buf: bytes, parent_names_buf: bytes, def_sha1s_buf: bytes, class_count: int, out_order: t.CPtr, out_count: t.CPtr, temp_dir: str, td_len_ts: sizet) -> int: - """根据类继承关系对文件进行拓扑排序""" + """Topologically sort files by class inheritance and import dependencies.""" if result is None or out_order is None or out_count is None: return -1 entry_size: sizet = IncludesScanner.FileEntry.__sizeof__() - # 1. 分配扁平数组:文件 SHA1、依赖 SHA1 列表、依赖数量、入度、处理状态 - # 用扁平数组代替 FileDepEntry 结构体,避免 str 指针字段 NULL 崩溃 + # 1. Allocate flat arrays: file SHA1, dependency SHA1 list, dep counts, in-degrees, processed flags. + # Flat arrays replace a FileDepEntry struct to avoid NULL str-pointer fields crashing. file_sha1s_buf: bytes = mb.alloc(Phase1.MAX_FILE_DEPS * Phase1.SHA1_LEN) file_deps_buf: bytes = mb.alloc(Phase1.MAX_FILE_DEPS * Phase1.MAX_DEP_EDGES * Phase1.SHA1_LEN) file_dep_counts: t.CPtr = mb.alloc(Phase1.MAX_FILE_DEPS * 4) @@ -74,7 +61,7 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.ScanResu string.memset(file_processed, 0, Phase1.MAX_FILE_DEPS * 4) file_count: int = 0 - # sha1→file_index 映射(简单线性查找,文件数不多) + # sha1 -> file_index map (simple linear search; file count is small) for i in range(result.Count): entry_addr: uint64 = uint64(result.Entries) + i * entry_size entry: rawptr[IncludesScanner.FileEntry] = rawptr[IncludesScanner.FileEntry](entry_addr) @@ -88,42 +75,42 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.ScanResu if file_count >= Phase1.MAX_FILE_DEPS: break - # 初始化扁平数组:文件 SHA1(偏移 = file_count * Phase1.SHA1_LEN) - # DepCount/InDegree/Processed 已由 memset 清零 + # Initialize the flat array: file SHA1 (offset = file_count * Phase1.SHA1_LEN). + # DepCount/InDegree/Processed are already zeroed by memset. sha1_dst: str = file_sha1s_buf + file_count * Phase1.SHA1_LEN string.strcpy(sha1_dst, sha1_e) file_count += 1 - # 2. 构建依赖边:对每个类,找到父类定义所在文件,建立 子文件→父文件 依赖 + # 2. Build dependency edges: for each class, find the parent's defining file and add a child->parent edge for ci in range(class_count): - # 读取父类名(偏移 = ci * Phase1.CLASS_NAME_LEN) + # Read parent class name (offset = ci * Phase1.CLASS_NAME_LEN) parent_ptr: str = parent_names_buf + ci * Phase1.CLASS_NAME_LEN if parent_ptr[0] == '\0': continue - # 读取子类定义 SHA1(偏移 = ci * Phase1.SHA1_LEN) + # Read child class definition SHA1 (offset = ci * Phase1.SHA1_LEN) child_def_sha1: str = def_sha1s_buf + ci * Phase1.SHA1_LEN - # 查找父类定义所在文件的 SHA1 + # Find the SHA1 of the file defining the parent class parent_sha1: str = Phase1Scan._FindClassDefSha1(class_names_buf, def_sha1s_buf, class_count, parent_ptr) if parent_sha1 is None: - continue # 父类不在 includes 中(如 Object、CEnum 等标记基类) + continue # parent not in includes (e.g. Object, CEnum marker base classes) - # 父类文件 SHA1 不能等于子类文件 SHA1(同文件内继承不需要依赖) + # The parent file SHA1 must differ from the child's (same-file inheritance needs no dependency) if string.strcmp(parent_sha1, child_def_sha1) == 0: continue - # 找到子类文件对应的索引,添加父文件 SHA1 到依赖列表 + # Find the child file's index and add the parent file SHA1 to its dependency list for fi in range(file_count): fsha1: str = file_sha1s_buf + fi * Phase1.SHA1_LEN if string.strcmp(fsha1, child_def_sha1) != 0: continue - # 读取当前依赖数量 + # Read the current dependency count cnt_addr: uint64 = uint64(file_dep_counts) + fi * 4 cnt_ptr: t.CPtr = (int*)(cnt_addr) if cnt_ptr is None: break cur_cnt: int = cnt_ptr[0] - # 检查依赖是否已添加(去重) + # Check whether the dependency is already present (dedupe) already: int = 0 for di in range(cur_cnt): dep_slot: str = file_deps_buf + fi * Phase1.MAX_DEP_EDGES * Phase1.SHA1_LEN + di * Phase1.SHA1_LEN @@ -138,11 +125,8 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.ScanResu cnt_ptr[0] = cur_cnt + 1 break - # 2.5 补充 import 依赖边:被 import 的模块先完整翻译。 - # - # 仅按类继承建边不够:跨模块全局变量 stub、跨模块 vtable TypeStr、泛型模板都要求依赖模块先翻译,而它们之间可能无类继承关系。 - # deps(_includes_deps_*)由 Phase 1a-pre 填充(含容器按需注入的 _list/_dict);用 _BuildIncludesSha1Map 复制局部 sha1→模块名 数组 - # (与 _BuildReachableSha1Set 的 BFS 一致),避免直接跨函数传全局 store 返回值导致地址异常。 + # 2.5 Add import dependency edges: imported modules must be fully translated first. + # Class inheritance alone is insufficient (cross-module globals/vtable TypeStr/generics need deps); deps come from Phase 1a-pre, mapped via _BuildIncludesSha1Map. sha1_arr_ts: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES * 17) mod_arr_ts: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES * 64) map_count_ts: int = 0 @@ -168,11 +152,11 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.ScanResu dsha1_imp: str = StubMergerIR._FindSha1ByModName(sha1_arr_ts, mod_arr_ts, map_count_ts, dname_imp) stdlib.free(dname_imp) if dsha1_imp is None: continue - # 找依赖文件在列表中的索引(跳过自身) + # Find the dependency file's index in the list (skip self) for fj_imp in range(file_count): if string.strcmp(file_sha1s_buf + fj_imp * Phase1.SHA1_LEN, dsha1_imp) != 0: continue if fj_imp == fi_imp: break - # 去重 + 加边:fi_imp 依赖 fj_imp(InDegree[fi_imp]++) + # Dedupe and add the edge: fi_imp depends on fj_imp (InDegree[fi_imp]++) cnt_addr_imp: uint64 = uint64(file_dep_counts) + fi_imp * 4 cnt_ptr_imp: t.CPtr = (int*)(cnt_addr_imp) if cnt_ptr_imp is None: break @@ -189,12 +173,12 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.ScanResu cnt_ptr_imp[0] = cur_cnt_imp + 1 break - # 释放 import 边用的局部映射数组 + # Free the local mapping arrays used for import edges if sha1_arr_ts is not None: stdlib.free(sha1_arr_ts) if mod_arr_ts is not None: stdlib.free(mod_arr_ts) - # 3. Kahn 算法:InDegree = DepCount(依赖多少个父文件) - # 依赖为 0 的先处理,处理后减少依赖它的文件入度 + # 3. Kahn: InDegree = DepCount (number of parent files depended on). + # Process zero-dependency files first, then decrement the in-degree of dependents. for fi in range(file_count): cnt_addr3: uint64 = uint64(file_dep_counts) + fi * 4 cnt_ptr3: t.CPtr = (int*)(cnt_addr3) @@ -202,46 +186,46 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.ScanResu ind_ptr3: t.CPtr = (int*)(ind_addr3) if cnt_ptr3 is not None and ind_ptr3 is not None: ind_ptr3[0] = cnt_ptr3[0] - # 4. 拓扑排序输出 + # 4. Topological sort output out_idx: int = 0 changed: int = 1 while changed != 0: changed = 0 for fi in range(file_count): - # 检查处理状态 + # Check processed flag proc_addr4: uint64 = uint64(file_processed) + fi * 4 proc_ptr4: t.CPtr = (int*)(proc_addr4) if proc_ptr4 is None or proc_ptr4[0] != 0: continue - # 检查入度 + # Check in-degree ind_addr4: uint64 = uint64(file_indegrees) + fi * 4 ind_ptr4: t.CPtr = (int*)(ind_addr4) if ind_ptr4 is None or ind_ptr4[0] > 0: continue - # 入度为 0,可以处理 - # 找到对应的 result.Entries 索引 + # In-degree 0: can be processed + # Find the matching result.Entries index target_sha1: str = file_sha1s_buf + fi * Phase1.SHA1_LEN for ri in range(result.Count): ea_r: uint64 = uint64(result.Entries) + ri * entry_size ent_r: rawptr[IncludesScanner.FileEntry] = rawptr[IncludesScanner.FileEntry](ea_r) if ent_r is not None and ent_r.Sha1 is not None: if string.strcmp(ent_r.Sha1, target_sha1) == 0: - # 写入输出索引数组 + # Write into the output index array out_idx_addr: uint64 = uint64(out_order) + out_idx * 4 out_int_ptr: t.CPtr = (int*)(out_idx_addr) if out_int_ptr is not None: out_int_ptr[0] = ri out_idx += 1 break - # 标记已处理 + # Mark as processed proc_ptr4[0] = 1 changed = 1 - # 减少依赖该文件的其他文件的入度 + # Decrement the in-degree of files that depend on this one for fj in range(file_count): proc_addr5: uint64 = uint64(file_processed) + fj * 4 proc_ptr5: t.CPtr = (int*)(proc_addr5) if proc_ptr5 is None or proc_ptr5[0] != 0: continue - # 读取 fj 的依赖数量 + # Read fj's dependency count cnt_addr5: uint64 = uint64(file_dep_counts) + fj * 4 cnt_ptr5: t.CPtr = (int*)(cnt_addr5) if cnt_ptr5 is None: continue @@ -254,10 +238,8 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.ScanResu if ind_ptr5 is not None: ind_ptr5[0] = ind_ptr5[0] - 1 break - # 处理循环依赖:环内优先输出被依赖者(被其余剩余文件依赖数量多者先)。 - # - # 容器按需注入(string 用 list 泛型 → 依赖 _list)与真实 import 会形成伪环,Kahn 无法完全归零;若按扫描顺序追加, - # 依赖者会先于被依赖者翻译 → 跨模块 vtable TypeStr 未注册 → 误杀。 + # Handle cycles: within a cycle emit the more-depended-upon files first. + # On-demand container injection (e.g. string -> _list) plus real imports form pseudo-cycles Kahn cannot fully drain; scan order would translate dependents first -> unregistered cross-module vtable TypeStr. if out_idx < file_count: rem_idx: t.CPtr = stdlib.malloc(4 * file_count) rem_depcnt: t.CPtr = stdlib.malloc(4 * file_count) @@ -270,7 +252,7 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.ScanResu rem_idx[rem_count] = fi rem_depcnt[rem_count] = 0 rem_count += 1 - # 计算每个剩余文件的被依赖数(被其余剩余文件依赖) + # Compute each remaining file's dependents count (among the remaining files) for i_a in range(rem_count): fi_a: int = rem_idx[i_a] target_sha1_a: str = file_sha1s_buf + fi_a * Phase1.SHA1_LEN @@ -286,7 +268,7 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.ScanResu if string.strcmp(dep_slot_b, target_sha1_a) == 0: rem_depcnt[i_a] = rem_depcnt[i_a] + 1 break - # 冒泡降序(被依赖数多者在前) + # Bubble sort descending (more dependents first) for i_a in range(rem_count): for j_a in range(rem_count - 1): if rem_depcnt[j_a] < rem_depcnt[j_a + 1]: @@ -296,7 +278,7 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.ScanResu tmp_d: int = rem_depcnt[j_a] rem_depcnt[j_a] = rem_depcnt[j_a + 1] rem_depcnt[j_a + 1] = tmp_d - # 按序输出剩余文件 + # Emit the remaining files in order for i_a in range(rem_count): fi_a2: int = rem_idx[i_a] proc_addr8: uint64 = uint64(file_processed) + fi_a2 * 4 @@ -317,11 +299,11 @@ def _TopoSortFiles(mb: memhub.MemBuddy*, result: rawptr[IncludesScanner.ScanResu stdlib.free(rem_idx) stdlib.free(rem_depcnt) - # 写入输出数量 + # Write the output count out_count_ptr: t.CPtr = (int*)(out_count) if out_count_ptr is not None: out_count_ptr[0] = out_idx - # 释放扁平数组 + # Free the flat arrays mb.free(file_sha1s_buf) mb.free(file_deps_buf) mb.free(file_dep_counts) diff --git a/App/lib/Phase/Phase1/__init__.vp b/App/lib/Phase/Phase1/__init__.vp index 50dc7b1..be77413 100644 --- a/App/lib/Phase/Phase1/__init__.vp +++ b/App/lib/Phase/Phase1/__init__.vp @@ -2,18 +2,17 @@ import t, c from stdint import * import memhub -# 全局 mbuddy 指针 +# Global mbuddy pointer _mbuddy: memhub.MemBuddy* -# 源代码缓冲区大小(1MB) +# Source buffer size (1MB) define SRC_BUF_SIZE = 1048576 -# vpi 缓冲区大小(256KB) -# 编译期常量,用 define 而非模块级全局:模块级同名全局会在多个模块各发 -# 一份强定义符号(@PYI_BUF_SIZE),llvm-link 报 symbol multiply defined。 +# vpi buffer size (256KB). +# Compile-time constant via define: a module-level global would emit a strong symbol per module, causing llvm-link errors. define PYI_BUF_SIZE = 262144 -# 拓扑排序相关常量 +# Topological sort constants define MAX_CLASSES = 512 define MAX_FILE_DEPS = 256 define MAX_DEP_EDGES = 16 diff --git a/App/lib/Phase/Phase2/__init__.vp b/App/lib/Phase/Phase2/__init__.vp index a8bde7c..72571d2 100644 --- a/App/lib/Phase/Phase2/__init__.vp +++ b/App/lib/Phase/Phase2/__init__.vp @@ -29,29 +29,25 @@ import lib.Stub.StubMerger.StubMergerStore as StubMergerStore import lib.Stub.StubMerger.StubMergerTopo as StubMergerTopo import lib.Stub.StubGen.Converter as StubConverter -# 全局 mbuddy 指针 +# Global mbuddy pointer _mbuddy: memhub.MemBuddy* -# 源代码缓冲区大小(1MB) +# Source buffer size (1MB) define SRC_BUF_SIZE = 1048576 -# 最大源文件数(递归扫描子目录后文件数增多;过小会截断文件列表 → 链接 undefined symbol,故留余量) +# Max source files (recursive scanning grows the count; too small truncates the list -> undefined symbol) define MAX_SRC_FILES = 128 @t.NoVTable class SrcFileEntry: - """源文件条目""" - Path: str # 完整路径 - Sha1: str # SHA1 前16字符 + """Source file entry""" + Path: str # full path + Sha1: str # first 16 chars of SHA1 -# ============================================================ -# _ScanDirForPyFiles - 递归扫描目录,收集 .vp/.py 文件到 entries(遇目录递归,遇文件读取内容、计算 SHA1、加入 entries) -# -# Args: mb 内存池;dir_path 当前扫描目录;entries SrcFileEntry 数组;entry_size 单个条目大小(SrcFileEntry.__sizeof__());file_count 当前已收集的文件数;max_files 最大文件数 -# Returns: 新的 file_count -# ============================================================ +# _ScanDirForPyFiles - recursively scan a dir and collect .vp/.py files into entries, returning the new count. +# Args: mb pool; dir_path; entries SrcFileEntry array; entry_size; file_count; max_files. def _ScanDirForPyFiles(mb: memhub.MemBuddy*, dir_path: str, entries: SrcFileEntry*, entry_size: sizet, file_count: int, max_files: int) -> int: - """递归扫描目录,收集 .vp/.py 文件到 entries,返回新的 file_count""" + """Recursively scan a directory, collect .vp/.py files into entries, return the new file_count""" if dir_path is None or entries is None: return file_count if file_count >= max_files: return file_count @@ -82,26 +78,26 @@ def _ScanDirForPyFiles(mb: memhub.MemBuddy*, dir_path: str, entries: SrcFileEntr if fname_len <= 0: if w32.win32file.FindNextFileA(handle, find_data) == 0: break continue - # 跳过 . 和 .. + # Skip . and .. is_dot: int = 0 if fname_len == 1 and fname[0] == '.': is_dot = 1 elif fname_len == 2 and fname[0] == '.' and fname[1] == '.': is_dot = 1 if is_dot != 0: if w32.win32file.FindNextFileA(handle, find_data) == 0: break continue - # 检查是否是目录 + # Check whether it is a directory attrs: ULONG = find_data.dwFileAttributes is_dir: int = 0 if (attrs & w32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0: is_dir = 1 if is_dir != 0: - # 递归扫描子目录 + # Recurse into the subdirectory sub_dir: bytes = Utils.JoinPath(dir_path, fname) if sub_dir is not None: file_count = _ScanDirForPyFiles(mb, sub_dir, entries, entry_size, file_count, max_files) stdlib.free(sub_dir) if w32.win32file.FindNextFileA(handle, find_data) == 0: break continue - # 检查是否是 .vp 源文件(Viper 源仅 .vp,不再识别 .py) + # Check for a .vp source file (Viper sources are only .vp now; .py is no longer recognized) if fname_len > 3 and Utils.is_vp_at(fname, 0, fname_len) != 0 and file_count < max_files: full_path: bytes = Utils.JoinPath(dir_path, fname) if full_path is not None: @@ -109,8 +105,8 @@ def _ScanDirForPyFiles(mb: memhub.MemBuddy*, dir_path: str, entries: SrcFileEntr if sbuf is None: if w32.win32file.FindNextFileA(handle, find_data) == 0: break continue - # 统一用 IncludesScanner.compute_file_sha1(CRLF→LF 归一化),与 StubMerger 对同一批 App 源文件的口径一致; - # 用原始字节口径会导致 stub/define 符号前缀不一致 → 链接 undefined reference。 + # Use IncludesScanner.compute_file_sha1 (CRLF->LF normalized) as StubMerger does for the same + # App sources; raw-byte hashing makes stub/define symbol prefixes differ -> undefined reference. sha1_val: str = IncludesScanner.compute_file_sha1(mb, full_path) if sha1_val is not None: ea: uint64 = uint64(entries) + file_count * entry_size @@ -122,7 +118,7 @@ def _ScanDirForPyFiles(mb: memhub.MemBuddy*, dir_path: str, entries: SrcFileEntr VLogger.info(f"{fname} (sha1={sha1_val})", "project") stdlib.free(sbuf) - # full_path 不释放:ent.Path 引用它 + # full_path is not freed: ent.Path references it if w32.win32file.FindNextFileA(handle, find_data) == 0: break @@ -132,15 +128,10 @@ def _ScanDirForPyFiles(mb: memhub.MemBuddy*, dir_path: str, entries: SrcFileEntr return file_count -# ============================================================ -# _ScanDirForAsmFiles - 递归扫描目录,收集 .s 汇编文件 -# -# 与 _ScanDirForPyFiles 同构:VKernel 等裸机项目含 .s(如 -# Kernel/intr/isr.s 定义 isr0/syscall_entry/_per_cpu 等符号)。 -# TPV 不对 .s 翻译,直接用 llvm-mc 汇编为 .obj 参与链接。 -# ============================================================ +# _ScanDirForAsmFiles - recursively scan a dir and collect .s assembly files. +# Like _ScanDirForPyFiles; TPV does not translate .s but assembles it to .obj via llvm-mc for linking. def _ScanDirForAsmFiles(mb: memhub.MemBuddy*, dir_path: str, entries: SrcFileEntry*, entry_size: sizet, file_count: int, max_files: int) -> int: - """递归扫描目录,收集 .s 汇编文件到 entries,返回新的 file_count""" + """Recursively scan a directory, collect .s assembly files into entries, return the new file_count""" if dir_path is None or entries is None: return file_count if file_count >= max_files: return file_count @@ -171,14 +162,14 @@ def _ScanDirForAsmFiles(mb: memhub.MemBuddy*, dir_path: str, entries: SrcFileEnt if fname_len <= 0: if w32.win32file.FindNextFileA(handle, find_data) == 0: break continue - # 跳过 . 和 .. + # Skip . and .. is_dot: int = 0 if fname_len == 1 and fname[0] == '.': is_dot = 1 elif fname_len == 2 and fname[0] == '.' and fname[1] == '.': is_dot = 1 if is_dot != 0: if w32.win32file.FindNextFileA(handle, find_data) == 0: break continue - # 检查是否是目录 + # Check whether it is a directory attrs: ULONG = find_data.dwFileAttributes is_dir: int = 0 if (attrs & w32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0: is_dir = 1 @@ -189,13 +180,13 @@ def _ScanDirForAsmFiles(mb: memhub.MemBuddy*, dir_path: str, entries: SrcFileEnt stdlib.free(sub_dir) if w32.win32file.FindNextFileA(handle, find_data) == 0: break continue - # 检查是否以 .s 后缀结尾(汇编源文件) + # Check for the .s suffix (assembly source) is_asm: int = 0 if fname_len > 2 and fname[fname_len - 2] == '.' and fname[fname_len - 1] == 's': is_asm = 1 if is_asm != 0 and file_count < max_files: full_path: bytes = Utils.JoinPath(dir_path, fname) if full_path is not None: - # SHA1 与 .vp 同口径(CRLF→LF 归一化),用于切片路径 + # Same SHA1 convention as .vp (CRLF->LF normalized), used for the sliced path sha1_val: str = IncludesScanner.compute_file_sha1(mb, full_path) if sha1_val is not None: ea: uint64 = uint64(entries) + file_count * entry_size @@ -206,7 +197,7 @@ def _ScanDirForAsmFiles(mb: memhub.MemBuddy*, dir_path: str, entries: SrcFileEnt file_count += 1 VLogger.info(f"{fname} (asm sha1={sha1_val})", "project") - # full_path 不释放:ent.Path 引用它 + # full_path is not freed: ent.Path references it if w32.win32file.FindNextFileA(handle, find_data) == 0: break @@ -216,25 +207,20 @@ def _ScanDirForAsmFiles(mb: memhub.MemBuddy*, dir_path: str, entries: SrcFileEnt return file_count -# ============================================================ -# _PreRegisterAppGenericTemplates - mark2 前预注册 App 源文件泛型类模板 -# -# 泛型模板表(GenericClassNames 等 4 张 + 模板节点)是 pool 懒分配的跨文件持久表,若在 mark2 之后(Phase A-pre 翻译 App 文件时) -# 才首次注册,reset_to(mark2) 会回收表与节点 → 后续查重/特化遍历悬空内存崩溃;App 文件数量少,mark2 前预注册的 AST 驻留内存可忽略。 -# 重复注册由 _register_generic_template 查重跳过。 -# ============================================================ +# _PreRegisterAppGenericTemplates - pre-register App generic class templates before mark2. +# The generic tables live in the pool; registering after mark2 would be reclaimed by reset_to, corrupting later passes. def _PreRegisterAppGenericTemplates(mb: memhub.MemBuddy*, entries: SrcFileEntry*, file_count: int, entry_size: sizet) -> int: - """mark2 前预注册 App 源文件的泛型类模板,返回处理到的泛型类数量""" + """Pre-register App source generic class templates before mark2, return the processed count""" if mb is None or entries is None or file_count <= 0: return 0 handled: int = 0 for i in range(file_count): ea_pa: uint64 = uint64(entries) + i * entry_size ent_pa: SrcFileEntry* = (SrcFileEntry*)(ea_pa) if ent_pa is None or ent_pa.Path is None or ent_pa.Sha1 is None: continue - # 读取文件 + # Read the file f_pa: rawptr[fileio.File] = fileio.File(ent_pa.Path, fileio.MODE.R) if f_pa.closed: continue - # read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被静默截断 + # read_all's capacity includes the terminator (reads at most cap-1 bytes); longer files are truncated if f_pa.size() >= SRC_BUF_SIZE: VLogger.error(f"源文件超过 {SRC_BUF_SIZE} 字节上限, 拒绝读取: {ent_pa.Path}", "PhaseA") f_pa.close() @@ -249,7 +235,7 @@ def _PreRegisterAppGenericTemplates(mb: memhub.MemBuddy*, entries: SrcFileEntry* stdlib.free(src_buf_pa) continue if bytes_read_pa < SRC_BUF_SIZE: src_buf_pa[bytes_read_pa] = 0 - # 解析 AST(只解析,不完整翻译;用永久池使模板节点跨 mark2 reset 存活) + # Parse the AST only (perm pool keeps template nodes alive across the mark2 reset) perm_pa: memhub.MemBuddy* = CompilerFactory.factory.PermPool if perm_pa is None: perm_pa = mb lx_pa: ast.Lexer* = ast.new_lexer(perm_pa) @@ -262,7 +248,7 @@ def _PreRegisterAppGenericTemplates(mb: memhub.MemBuddy*, entries: SrcFileEntry* if tree_pa is None: stdlib.free(src_buf_pa) continue - # 遍历顶层 ClassDef/FunctionDef,注册泛型模板(type_params 非 None) + # Walk top-level ClassDef/FunctionDef and register generic templates (type_params != None) ch_pa: list[ast.AST*]* = tree_pa.children if ch_pa is not None: cn_pa: sizet = len(ch_pa) @@ -291,17 +277,12 @@ def _PreRegisterAppGenericTemplates(mb: memhub.MemBuddy*, entries: SrcFileEntry* return handled -# ============================================================ -# Phase A 收敛失败依赖图诊断 -# -# 多遍收敛失败时仅报 "无法完成翻译: <路径>" 无法定位「直接依赖均已生成但缺失在传递依赖/MRO/vpi 侧」。 -# 故翻译过程中把每个待译文件的依赖模块名与缺失明细持久化到 stdlib 全局表(跨 mb.reset_to(mark2) 存活), -# 收敛失败时按文件输出依赖树,用 [MISS]/[OK] 标出缺 text.ll 的节点,并回显 pending 明细。 -# ============================================================ +# Phase A convergence-failure dependency-graph diagnostics. +# Each file's deps and missing details are persisted to a stdlib table (survives reset) and dumped on failure. PA_DEPS_MAX_STR: sizet = 1024 PA_MISS_MAX_STR: sizet = 768 -PA_GRAPH_DEPTH_MAX: int = 3 # 缺失链下钻深度(第 1 层开始计数) -PA_GRAPH_OK_PROBE: int = 2 # 就绪节点仅再探测 1 层(找隐藏缺失) +PA_GRAPH_DEPTH_MAX: int = 3 # missing-chain drill-down depth (counts from layer 1) +PA_GRAPH_OK_PROBE: int = 2 # ready nodes are probed 1 more level (to find hidden misses) _pa_diag_max: sizet = 0 _pa_diag_count: int = 0 _pa_diag_sha1: bytes = None @@ -310,7 +291,7 @@ _pa_diag_miss: bytes = None def _PhaseADiagInit(max_count: sizet) -> None: - """分配 Phase A 收敛失败诊断持久表(stdlib,跨 reset 存活)""" + """Allocate the persistent Phase A convergence-failure diagnostic table""" 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 @@ -323,7 +304,7 @@ def _PhaseADiagInit(max_count: sizet) -> None: def _PhaseADiagFree() -> None: - """释放诊断持久表""" + """Free the persistent diagnostic table""" 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) @@ -339,7 +320,7 @@ def _PhaseADiagFree() -> None: def _PhaseADiagRecord(sha1: str, deps: str, miss: str) -> None: - """记录/更新单文件诊断:sha1 → 依赖串 + 缺失明细(None 表示不更新该字段)""" + """Record or update one file's diagnostics: sha1 to deps plus missing detail""" 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 @@ -366,7 +347,7 @@ def _PhaseADiagRecord(sha1: str, deps: str, miss: str) -> None: def _PhaseADiagDepsLookup(sha1: str) -> str: - """按 sha1 查持久化依赖串(stdlib.malloc 副本,调用者 free;未命中 None)""" + """Look up the persisted deps string by sha1; caller frees the copy, None if absent""" 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 + sizet(i) * 17 @@ -381,7 +362,7 @@ def _PhaseADiagDepsLookup(sha1: str) -> str: def _PhaseADiagMissLookup(sha1: str) -> str: - """按 sha1 查持久化缺失明细(stdlib.malloc 副本,调用者 free;未命中/空 None)""" + """Look up the persisted missing detail by sha1; caller frees the copy, None if absent""" 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 + sizet(i) * 17 @@ -397,7 +378,7 @@ def _PhaseADiagMissLookup(sha1: str) -> str: def _PhaseATextLLExists(temp_dir: str, td_len: sizet, sha1: str) -> int: - """探测 {temp_dir}/{sha1前2位}/{sha1}.text.ll 是否存在,1=存在""" + """Probe whether {temp_dir}/{sha1 first 2 chars}/{sha1}.text.ll exists, 1=yes""" 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 @@ -411,7 +392,7 @@ def _PhaseATextLLExists(temp_dir: str, td_len: sizet, sha1: str) -> int: def _PhaseAResolveDepSha1(mod_name: str) -> str: - """依赖模块名 → SHA1(精确 → 后缀兜底),未命中 None""" + """Resolve a dependency module name to SHA1 (exact then suffix), None if absent""" if mod_name is None: return None s1: str = StubMergerStore.LookupSha1ByModName(mod_name) if s1 is not None: return s1 @@ -419,11 +400,11 @@ def _PhaseAResolveDepSha1(mod_name: str) -> str: def _PhaseADumpNode(temp_dir: str, td_len: sizet, sha1: str, depth: int, prefix: bytes) -> None: - """递归打印依赖子图;[MISS]=缺 text.ll(下钻整链),[OK]=就绪(仅探测 1 层)""" + """Recursively print the dependency subgraph; [MISS]=missing text.ll, [OK]=ready""" 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 写盘) + # Fallback: read {temp_dir}/{prefix}/{sha1}.deps.txt (Phase1 already wrote it for includes) dpath: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1, "deps.txt") if dpath is not None: df: rawptr[fileio.File] = fileio.File(dpath, fileio.MODE.R) @@ -432,7 +413,7 @@ def _PhaseADumpNode(temp_dir: str, td_len: sizet, sha1: str, depth: int, prefix: if deps is not None: rl: int64 = df.read_all(deps, PA_DEPS_MAX_STR) if rl < 0: rl = 0 - # read_all 的容量参数即缓冲区容量(含终止符):传真实容量 MAX 时最多读 MAX-1 字节,故 rl 恒 <= MAX-1,在界内直接写终止符即可。 + # read_all's size arg is the buffer capacity (incl. terminator): it reads at most MAX-1 deps[rl] = 0 df.close() stdlib.free(dpath) @@ -466,8 +447,8 @@ def _PhaseADumpNode(temp_dir: str, td_len: sizet, sha1: str, depth: int, prefix: 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 崩溃,图输出被吞) + # Branch characters are inlined (don't return string literals: TPV would return a dangling + # stack pointer for a literal -> printf %s crashes and the graph output is swallowed) br_1: str = "|-- " br_2: str = "`-- " if builtin != 0: @@ -482,7 +463,7 @@ def _PhaseADumpNode(temp_dir: str, td_len: sizet, sha1: str, depth: int, prefix: 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) + # Drill down: MISS follows the whole chain (to PA_GRAPH_DEPTH_MAX); OK probes 1 level only can_expand: int = 0 if dep_sha1 is not None and builtin == 0: if txt_ok == 0: @@ -490,8 +471,8 @@ def _PhaseADumpNode(temp_dir: str, td_len: sizet, sha1: str, depth: int, prefix: 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 缓冲丢失 + # +5: strcat appends "| "/" " (4 chars) + NUL; strlen+4 would overflow 1 byte and + # corrupt heap metadata, crashing later malloc/free and aborting the graph output npl: sizet = string.strlen(prefix) + 5 new_prefix: bytes = stdlib.malloc(npl) if new_prefix is not None: @@ -506,11 +487,8 @@ def _PhaseADumpNode(temp_dir: str, td_len: sizet, sha1: str, depth: int, prefix: def _PhaseADumpFailGraph(temp_dir: str, td_len: sizet, path: str, sha1: str) -> None: - """收敛失败文件诊断:pending 明细 + 依赖树(标出缺 text.ll 节点) - - 每步 printf 后立即 fflush:管道捕获下 stdout 全缓冲,若后续崩溃未刷出 - 则整段诊断丢失("ERROR 可见但依赖图无输出"即此类)。 - """ + """Convergence-failure diagnostics: pending detail plus dependency tree. + Flush after every printf so the trace survives a later crash.""" stdio.printf("[PhaseA-Fail] ==============================================================\n") stdio.fflush(None) stdio.printf("[PhaseA-Fail] FAIL TO TRANSLATE: %s [sha1=%s]\n", path, sha1) @@ -531,17 +509,14 @@ def _PhaseADumpFailGraph(temp_dir: str, td_len: sizet, path: str, sha1: str) -> def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, output_dir: str, cc_cmd: str, cc_flags: str, linker_cmd: str, linker_flags: str, linker_output: str, includes_binary_dir: str, includes_dir: str, do_phase1: int, do_phase2: int, log: rawptr[VLogger.Logger], args: rawptr[argparse.ParsedArgs], linker_static_libs: str = None, linker_dynamic_libs: str = None) -> int: - """多文件项目编译 - - Returns: 0 成功,非 0 失败 - """ + """Compile a multi-file project; return 0 on success, non-zero on failure""" if source_dir is None: VLogger.error("source_dir 为空", "project") return 1 VLogger.info(f"多文件项目编译: {source_dir}", "project") - # === 1. 递归扫描 source_dir 下的 .vp/.py 文件(包括子目录)=== + # === 1. Recursively scan .vp/.py files under source_dir (including subdirectories) === entry_size: sizet = SrcFileEntry.__sizeof__() entries: SrcFileEntry* = stdlib.malloc(MAX_SRC_FILES * entry_size) if entries is None: return 1 @@ -550,7 +525,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou file_count: int = _ScanDirForPyFiles(mb, source_dir, entries, entry_size, 0, MAX_SRC_FILES) VLogger.info(f"共 {file_count} 个源文件", "project") - # 扫描 .s 汇编文件(独立数组,不参与 Phase A 翻译,Phase B 后 llvm-mc 汇编) + # Scan .s assembly files (separate array; not translated in Phase A, assembled by llvm-mc after Phase B) asm_entries: SrcFileEntry* = stdlib.malloc(MAX_SRC_FILES * entry_size) if asm_entries is None: stdlib.free(entries) @@ -564,28 +539,25 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou stdlib.free(asm_entries) return 1 - # 初始化 AST 表 + # Initialize the AST tables ast._init_tables(mb) - # mark2 之前预注册 App 源文件的泛型类模板:模板表/节点是 pool 懒分配,必须在 mark2 之前注册才能跨 reset_to(mark2) 存活, - # 否则 Phase A-pre 首次注册后即被 reset 回收 → 后续查重/特化遍历悬空内存崩溃。 + # Pre-register App generic class templates before mark2: the tables/nodes are pool-allocated, so they + # must be registered before mark2 to survive reset_to(mark2); otherwise later passes hit dangling memory. _PreRegisterAppGenericTemplates(mb, entries, file_count, entry_size) - # OOM 清理(对齐 Phase1 1b 的 mark1/reset_to 模式):无回收时 98 个 includes 的 AST/IR 会全量累积 pool → 1GB OOM。 - # 此处打 mark2,各处翻译完成后 reset_to(mark2) 回收该文件瞬时数据(AST/tokens/lexer/IR/Handler/dump 临时); - # 跨文件持久表(struct/enum/docstring/CDefine/函数注册表等)已 stdlib 化或落在 mark2 之前,reset 不受影响。 + # OOM cleanup (mirrors Phase1's mark/reset pattern): without reclamation the AST/IR of all includes + # pile up in the pool. mark2 lets each file's transient data be freed by reset_to after translation. mark2: sizet = mb.mark() - # mark2 之后新注册的 struct/enum 条目改 stdlib 分配(跨 reset 存活,对齐 Phase1) + # struct/enum entries registered after mark2 use stdlib allocation (survive reset, as in Phase1) GenApi.set_stdlib_alloc(1) - # 确保 temp/output 目录存在 + # Ensure the temp/output directories exist BuildPipeline.ensure_dir(temp_dir) BuildPipeline.ensure_dir(output_dir) - # === 0.5 依赖图按需填充 Sha1Store(消除 Phase1 依赖)=== - # Phase2 不依赖 Phase1 的副作用:如果 Sha1Store 为空(Phase1 未运行或提前返回), - # 自行按依赖图扫描(App 优先 BFS)并填充 Sha1Store,确保后续"编译缺失 includes" - # 逻辑能正常工作。未引用的 includes 根本不扫描(对齐 Phase1 依赖图模型)。 + # === 0.5 Fill Sha1Store on demand from the dependency graph (removes the Phase1 dependency) === + # If Sha1Store is empty, scan the dependency graph (App-first BFS) so later "compile missing includes" works. if includes_dir is not None: if StubMergerStore.GetSha1StoreCount() == 0: rs_05: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES_SHA1 * 17) @@ -597,9 +569,8 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou 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) - # 同步追加到内存存储器(AppendToSha1MapStore,供跨模块 CDefine 查找) + # Append App source SHA1s to _sha1_map.txt (human-readable only; format {sha1}:{rel_path}) and to the + # in-memory store (AppendToSha1MapStore) for cross-module CDefine lookups. td_len_am: sizet = string.strlen(temp_dir) src_dir_len_am: sizet = string.strlen(source_dir) map_path_am: bytes = stdlib.malloc(td_len_am + 32) @@ -607,23 +578,23 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou viperlib.snprintf(map_path_am, td_len_am + 32, "%s/_sha1_map.txt", temp_dir) mf: rawptr[fileio.File] = fileio.File(map_path_am, fileio.MODE.A) line_am: bytes = stdlib.malloc(512) - # 先遍历一次填充内存存储器(不依赖文件 I/O),再写入 _sha1_map.txt + # First fill the in-memory store (no file I/O), then write _sha1_map.txt app_filled: int = 0 for i in range(file_count): ea_am: uint64 = uint64(entries) + i * entry_size ent_am: SrcFileEntry* = (SrcFileEntry*)(ea_am) if ent_am is None or ent_am.Path is None or ent_am.Sha1 is None: continue - # 计算相对路径(去除 source_dir 前缀,保留目录结构) + # Compute the relative path (strip the source_dir prefix, keep the directory structure) p_str: str = ent_am.Path p_len: sizet = string.strlen(p_str) rel_path_am: str = p_str if p_len > src_dir_len_am + 1: if string.strncmp(p_str, source_dir, src_dir_len_am) == 0: rel_path_am = p_str + src_dir_len_am + 1 - # 同步追加到内存存储器(供跨模块 CDefine 查找) - # rel_path_am 格式如 "lib/core/StubMerger.vp",与 _sha1_map.txt 一致 + # Also append to the in-memory store (for cross-module CDefine lookups) + # rel_path_am looks like "lib/core/StubMerger.vp", matching _sha1_map.txt if StubMergerStore.AppendToSha1MapStore(ent_am.Sha1, rel_path_am) != 0: VLogger.error(f"AppendToSha1MapStore 失败: {rel_path_am}", "project") app_filled += 1 - # 写入 _sha1_map.txt(人类可读输出) + # Write _sha1_map.txt (human-readable output) if not mf.closed and line_am is not None: viperlib.snprintf(line_am, 512, "%s:%s\n", ent_am.Sha1, rel_path_am) ll_am: sizet = string.strlen(line_am) @@ -634,16 +605,12 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou VLogger.info(f"已追加 {app_filled} 个 App 文件到 _sha1_map.txt 和内存存储器", "project") stdlib.free(map_path_am) - # 模块 SHA1 映射已由 PopulateSha1MapStore + AppendToSha1MapStore 统一填充到 - # StubMerger 全局存储器,HandlesExprCall._lookup_module_sha1 直接查全局存储器, - # 不再需要构建独立的 SHA1 映射表副本。 + # The module SHA1 map is filled into the StubMerger global store by PopulateSha1MapStore + + # AppendToSha1MapStore; HandlesExprCall._lookup_module_sha1 queries it directly, no local copy needed. - # === Phase A-pre-vpi: 预生成所有 App 源文件的 .vpi stub === - # - # 依赖模块的 .vpi 必须在依赖方翻译前可用:Phase A 主循环按扫描顺序(非拓扑)翻译,若依赖模块后翻译则 LoadDepVpiIntoModule 找不到 vpi - # → 跨模块全局变量 declare 未注入 → ExprGenAttr fallback 以 i8* 创建带前缀 global → llc "icmp slt 类型不匹配"。 - # 此处先为所有 App 源文件预生成 vpi,保证 Phase A 翻译任意模块时其依赖 vpi 已存在(App 源文件自身 vpi 随后由 TranslateFileGetTrans 覆盖重写,内容一致)。 + # === Phase A-pre-vpi: pre-generate .vpi stubs for all App sources === + # Deps' .vpi must exist before their dependents translate; pre-generating avoids missing dep declarations. td_len_vpa: sizet = string.strlen(temp_dir) PYI_BUF_SIZE_P2: sizet = 262144 vpi_failed_p2: int = 0 @@ -654,7 +621,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou vpi_failed_p2 += 1 VLogger.error("[Phase A-pre-vpi] 源文件条目无效(Path/Sha1 缺失),跳过", "PhaseA") continue - # vpi 已存在则跳过(sha1 基于内容,同名 vpi 必然有效;跨次编译缓存) + # Skip if the vpi already exists (sha1 is content-based, so a same-named vpi is valid; cross-run cache) vpi_path_vp2: str = StubMergerCollect._sliced_path(temp_dir, td_len_vpa, ent_vp2.Sha1, "vpi") if vpi_path_vp2 is None: vpi_failed_p2 += 1 @@ -666,15 +633,14 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou stdlib.free(vpi_path_vp2) continue stdlib.free(vpi_path_vp2) - # 读取源文件 → 解析 AST → 生成 vpi - # (rel_path 传 ent_vp2.Path:vpi 内容不依赖它——与 TranslateFileGetTrans - # 后续覆盖写的内容一致;此处仅用于 fatal_error 的报错归因,指向本源文件) + # Read the source -> parse the AST -> generate the vpi (rel_path = ent_vp2.Path: vpi content does + # not depend on it; it is only used for fatal_error attribution, pointing at this source file) f_vp2: rawptr[fileio.File] = fileio.File(ent_vp2.Path, fileio.MODE.R) if f_vp2.closed: vpi_failed_p2 += 1 VLogger.error(f"[Phase A-pre-vpi] {ent_vp2.Path} | 无法打开源文件", "PhaseA") continue - # read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被静默截断 + # read_all's capacity includes the terminator (reads at most cap-1 bytes); longer files are truncated if f_vp2.size() >= SRC_BUF_SIZE: vpi_failed_p2 += 1 VLogger.error(f"[Phase A-pre-vpi] {ent_vp2.Path} | 源文件超过 {SRC_BUF_SIZE} 字节上限", "PhaseA") @@ -706,11 +672,8 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou if tree_vp2 is None: stdlib.free(src_buf_vp2) vpi_failed_p2 += 1 - # 解析失败:**日志行内联 行/列 + 原因**(与下方 dump_parse_traceback 的完整 - # Python 风格 traceback 同源:Traceback + File/行 + 源码行 + caret + - # SyntaxError: 消息;消息含 token 类型与前/后 token 附注,见 - # includes/ast/parser.vp 的 _record_parse_err_ctx / parse_tokens 兜底补记)。 - # 只打 "AST 解析失败" 时用户看不到任何定位与原因(traceback 未落到终端时更甚)。 + # Parse failure: inline the line/column + reason in the log line (same source as the full + # Python-style traceback below), since "AST parse failed" alone gives no location or cause. VLogger.error(f"[Phase A-pre-vpi] {ent_vp2.Path} | AST 解析失败(第 {ast.get_parse_err_line()} 行第 {ast.get_parse_err_col()} 列: {ast.get_parse_err_msg()})", "PhaseA") GenApi.dump_parse_traceback(ent_vp2.Path) continue @@ -730,29 +693,25 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou vpi_failed_p2 += 1 VLogger.error(f"[Phase A-pre-vpi] {ent_vp2.Path} | pyi 缓冲分配失败(OOM)", "PhaseA") stdlib.free(src_buf_vp2) - # OOM 清理:回收本文件 AST(vpi 已写盘;mark2 后持久数据已 stdlib 化) + # OOM cleanup: free this file's AST (the vpi is already on disk; persistent data is stdlib-allocated) mb.reset_to(mark2) if vpi_failed_p2 > 0: VLogger.error(f"Phase A-pre-vpi 有 {vpi_failed_p2} 个文件 vpi 预生成失败(失败文件与原因见上方逐条 [Phase A-pre-vpi] 日志)", "PhaseA") return 1 - # === 2. Phase A: 为每个文件生成 stub + text === - # app_deps_buf: 收集所有 App 源文件的直接依赖模块名(空格分隔),供 Phase B+ 的 _BuildReachableSha1Set 作为初始 worklist; - # 声明在 if 块外确保 Phase B+ 和清理代码能访问。容量 65536 与 StubMerger.REACH_WORKLIST_BUF 对齐:全量 App imports 拼接超 8192, - # 过小会静默丢依赖致 Phase B+ 可达集合不全。 + # === 2. Phase A: generate stub + text for each file === + # app_deps_buf collects App files' direct deps (space-separated) as Phase B+'s worklist (65536, matching StubMerger). APP_DEPS_BUF_SIZE: sizet = 65536 app_deps_buf: bytes = None app_deps_len: sizet = 0 if do_phase1 != 0: log.banner("Phase A: 生成 stub + text") - # Phase A 收敛失败依赖图诊断表初始化(stdlib,跨 mark2 reset 存活) + # Initialize the Phase A convergence-failure diagnostic table (stdlib, survives reset) _PhaseADiagInit(sizet(file_count) + StubMerger.MAX_INCLUDES) - # === Phase A-pre-inc: includes 预注册 struct + 依赖填充 === - # 必须在 App 源文件预注册之前执行:App 源文件(strict_mode=1)可能继承或引用 includes 中的 struct(如 ast.AST), - # includes struct 未注册会使 App 翻译失败 → .obj 缺失 → undefined reference。同时填充 PopulateIncludesDeps 供 Phase B+ 使用。 - # 多遍扫描解决跨文件继承问题(如 Constant(AST) 在 AST 未注册时被跳过)。 + # === Phase A-pre-inc: pre-register includes structs + fill deps === + # Must run before App pre-registration (multi-pass resolves cross-file inheritance) and fills PopulateIncludesDeps. if includes_dir is not None and includes_binary_dir is not None: store_count_inc_pre: int = StubMergerStore.GetSha1StoreCount() if store_count_inc_pre > 0: @@ -761,9 +720,8 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou store_incdir_inc_pre: bytes = StubMergerStore.GetSha1StoreIncdirArrPtr() deps_filled_pre: int = 0 PHASE_A_INC_MAX_PASSES: int = 3 - # 记录预注册失败的 includes:多遍后仍失败 → 报错终止编译,不允许静默降级(静默失败会让 App 的跨模块类构造退化为普通函数调用, - # 返回类型默认推断成 i32,调用点报误导性的「找不到模块 SHA1」)。declare_only=1 时 import 的 SHA1 检查跳过,故预注册失败只可能是文件自身无法翻译。 - # 必须跳过 App 文件:Sha1Store 里含 App 源文件 SHA1,但其预注册由 Phase A-pre 负责(App 是严格模式且此阶段依赖未就绪,在此预注册必然失败误报)。 + # Track includes that fail pre-registration: after all passes, any failure aborts the build + # (no silent downgrade). App files must be skipped here; Phase A-pre handles them. inc_fail_flags: bytes = stdlib.malloc(store_count_inc_pre) if inc_fail_flags is not None: string.memset(inc_fail_flags, 1, store_count_inc_pre) for pass_inc_i in range(PHASE_A_INC_MAX_PASSES): @@ -772,7 +730,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou inc_sha1_pre: str = store_sha1_inc_pre + sizet(si_inc) * 17 inc_rel_pre: str = store_rel_inc_pre + sizet(si_inc) * StubMerger.MAX_REL_PATH_LEN if inc_rel_pre is None or inc_rel_pre[0] == '\0': continue - # 跳过 App 源文件(main.vp 等):由 Phase A-pre 预注册,不算失败 + # Skip App source files (main.vp etc.): pre-registered by Phase A-pre, not a failure is_app_file_inc: int = 0 for i_app_inc in range(file_count): ea_app_inc: uint64 = uint64(entries) + i_app_inc * entry_size @@ -784,7 +742,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou inc_fail_flags[si_inc] = 0 continue rel_len_pre: sizet = string.strlen(inc_rel_pre) - # 所属 includes 根目录(多目录逐条取自 IncDir 数组,空串回退 includes_dir) + # The includes root dir (per-entry from the IncDir array; empty falls back to includes_dir) incdir_pre: str = store_incdir_inc_pre + sizet(si_inc) * StubMerger.MAX_INC_DIR_LEN if incdir_pre is None or incdir_pre[0] == '\0': incdir_pre = includes_dir incdir_len_pre: sizet = string.strlen(incdir_pre) @@ -795,21 +753,21 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou tr_inc_pre: rawptr[HandlesTranslator.Translator] = BuildPipeline.TranslateFileGetTrans(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 存活) + # Record this file's deps (for convergence diagnostics; stdlib table survives reset) _PhaseADiagRecord(inc_sha1_pre, tr_inc_pre._imported_modules, None) - # 预注册成功:清除失败标记 + # Pre-registration succeeded: clear the failure flag if inc_fail_flags is not None: inc_fail_flags[si_inc] = 0 - # 依赖填充只在第一遍执行(PopulateIncludesDeps 内部有去重) - # 注意:必须在 reset_to(mark2) 之前调用——内部深拷贝到 stdlib + # Fill deps only on the first pass (PopulateIncludesDeps dedupes internally). + # Must be called before reset_to(mark2) since it deep-copies into stdlib. if pass_inc_i == 0 and tr_inc_pre._imported_modules is not None and StubMergerStore.PopulateIncludesDeps(inc_sha1_pre, tr_inc_pre._imported_modules) == 0: deps_filled_pre = deps_filled_pre + 1 - # OOM 清理:回收本 includes 文件预注册翻译的瞬时数据 - # (AST/tokens/lexer/IR/Handler);struct 注册表条目 stdlib 分配不受影响 + # OOM cleanup: free this includes file's transient pre-registration data + # (AST/tokens/lexer/IR/Handler); stdlib-allocated struct entries are unaffected mb.reset_to(mark2) struct_count_after_inc: int = GenApi.get_struct_count() - # 收敛检查:本遍没有新结构体注册 → 所有类已注册 + # Convergence check: no new struct registered this pass -> all classes are registered if struct_count_after_inc == struct_count_before_inc: break - # 多遍后仍预注册失败的 includes → 报错终止(不允许降级) + # Includes still failing after all passes -> abort (no downgrade) if inc_fail_flags is not None: for si_inc_f in range(store_count_inc_pre): if inc_fail_flags[si_inc_f] != 0: @@ -818,9 +776,8 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou stdlib.free(inc_fail_flags) VLogger.info(f"includes 预注册 + 依赖填充: {deps_filled_pre}/{store_count_inc_pre}", "prePhaseA") - # === Phase A-pre: 预注册所有源文件的 struct/enum/union === - # 解决循环引用问题(circ_a 翻译时需知道 circ_b.ClassB 的 struct 定义):仅注册 struct/enum/union(declare_only=1),不翻译方法体; - # 多遍扫描解决跨文件继承的字母序问题(如 AnnAssignHandle 继承 Mixin 时 Mixin 未注册),每遍注册新类后下一遍子类可继承。 + # === Phase A-pre: pre-register struct/enum/union for all sources === + # Registers declarations only (declare_only=1), no method bodies; multi-pass resolves cross-file ordering. VLogger.info("预注册 struct/enum/union...", "PhaseA") src_dir_len_pre: sizet = string.strlen(source_dir) PHASE_A_PRE_MAX_PASSES: int = 3 @@ -834,40 +791,36 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou if string.strlen(ent_pre.Path) > src_dir_len_pre + 1: rel_path_pre: str = ent_pre.Path + src_dir_len_pre + 1 pkg_pre = GenApi.compute_package_from_relpath(mb, rel_path_pre) - # App 预注册传 pre_allow_dep_inject=1:声明期也做导入处理 + 依赖 vpi 注入, - # 使类字段注解在预注册期即与阶段 A 同上下文(否则包重导出类 - # list[ast.ClassDef*] 的票名前缀会在预注册期被冻结成包前缀,与阶段 A - # 解析出的真定义前缀分裂 → 结构体布局不兼容,见 Translator 字段说明)。 + # App pre-registration passes pre_allow_dep_inject=1: imports and dep vpi injection happen at + # declaration time so field annotations share Phase A's context (else struct layouts diverge). tr_pre: rawptr[HandlesTranslator.Translator] = BuildPipeline.TranslateFileGetTrans(mb, ent_pre.Path, ent_pre.Sha1, pkg_pre, 1, None, 1) if tr_pre is None: VLogger.error(f"预注册失败: {ent_pre.Path}", "PhaseA") - # OOM 清理:回收本 App 文件预注册翻译的瞬时数据 + # OOM cleanup: free this App file's transient pre-registration data mb.reset_to(mark2) struct_count_after: int = GenApi.get_struct_count() - # 收敛检查:本遍没有新结构体注册 → 所有类已注册 + # Convergence check: no new struct registered this pass -> all classes are registered if struct_count_after == struct_count_before: break VLogger.info("预注册完成", "PhaseA") - # Phase A IR 缓冲:1MB 不够,ast/parser.vp 的 text.ll 超 1MB 被截断 → - # 合并 IR 尾部丢字(opt: "end of file in global variable name")。 + # Phase A IR buffer: 1MB is not enough, ast/parser.vp's text.ll exceeds 1MB and got truncated -> + # merged IR lost the tail (opt: "end of file in global variable name"). PHASE_A_IR_SIZE: sizet = 8388608 - # FULL dump = stub + text(同一模块),留 2× 余量 + # FULL dump = stub + text (same module), with 2x headroom PHASE_A_FULL_SIZE: sizet = 16777216 td_len_pa: sizet = string.strlen(temp_dir) - # 设置 temp 目录路径,使 HandlesExprCall 能读取依赖模块的 text.ll - # 用于跨模块方法返回类型查找(避免指针返回值被截断为 i32) + # Set the temp dir so HandlesExprCall can read dependency modules' text.ll for cross-module + # method return-type lookup (avoids pointer returns being truncated to i32) GenApi.SetTempDir(temp_dir) - # app_deps_buf: 收集所有 App 源文件的直接依赖模块名(空格分隔) - # 供 Phase B+ 的 _BuildReachableSha1Set 作为初始 worklist 使用 - # 声明已在 if 块外,此处仅赋值 + # app_deps_buf collects App files' direct dependency modules (space-separated) as Phase B+'s initial + # worklist for _BuildReachableSha1Set; declared outside the if, only assigned here. app_deps_buf = stdlib.malloc(APP_DEPS_BUF_SIZE) app_deps_len = 0 if app_deps_buf is not None: app_deps_buf[0] = '\0' - # Phase A 多遍重试:同项目多源文件时,main.vp 可能先于其 import 的 - # mbuddytest.vp 翻译(文件系统顺序);翻译失败(语法/解析错误)的文件 - # 记录待下遍重试。每遍至少完成一个文件,最多 file_count 遍收敛。 + # Phase A multi-pass retry: with several sources, main.vp may translate before its imports + # (filesystem order); failed files are retried next pass, converging in at most file_count passes. done_flags: bytes = stdlib.malloc(file_count) if done_flags is None: VLogger.error("Phase A done_flags 分配失败", "PhaseA") @@ -881,33 +834,33 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou if done_flags[i] != 0: continue ea: uint64 = uint64(entries) + i * entry_size ent: SrcFileEntry* = (SrcFileEntry*)(ea) - # 计时点 0:本文件处理开始(translate 段起点) + # Timing point 0: start processing this file (translate phase start) t_pa0: uint64 = w32.win32base.GetTickCount64() if ent is None or ent.Path is None: done_flags[i] = 1 completed_this_pass += 1 continue - # 计算源文件的包名(相对 source_dir 的目录部分) + # Compute the package name (the directory part relative to source_dir) src_dir_len_pa: sizet = string.strlen(source_dir) pkg_pa: str = None if string.strlen(ent.Path) > src_dir_len_pa + 1: rel_path_pa: str = ent.Path + src_dir_len_pa + 1 pkg_pa = GenApi.compute_package_from_relpath(mb, rel_path_pa) tr_a: rawptr[HandlesTranslator.Translator] = BuildPipeline.TranslateFileGetTrans(mb, ent.Path, ent.Sha1, pkg_pa, 0, temp_dir) - # 计时点 1:translate 返回 + # Timing point 1: translate returned t_pa1: uint64 = w32.win32base.GetTickCount64() if tr_a is None: - # 翻译失败(语法/解析错误):记录待下遍重试,收敛检查统一报错 - # OOM 清理:失败翻译也已产生 AST/tokens 瞬时数据,立即回收 + # Translation failed (syntax/parse error): retry next pass; convergence check reports it. + # OOM cleanup: a failed translation also produced AST/tokens, freed immediately. mb.reset_to(mark2) continue - # 记录本文件依赖(供收敛失败依赖图诊断;stdlib 表跨 reset 存活) + # Record this file's deps (for convergence diagnostics; stdlib table survives reset) _PhaseADiagRecord(ent.Sha1, tr_a._imported_modules, None) - # 收集 _imported_modules 到 app_deps_buf(供 Phase B+ 按需编译)。 - # 依赖在翻译早期已填充。 + # Collect _imported_modules into app_deps_buf (for Phase B+ on-demand compilation). + # Dependencies were filled early during translation. if tr_a._imported_modules is not None and app_deps_buf is not None: - # 逐模块去重拼接:多个 App 文件 import 同一模块(builtins/_list/_dict/stdlib/t/c 等公共依赖)会让 app_deps 无去重膨胀到接近 64KB, - # _BuildReachableSha1Set 的初始 worklist 即满 → BFS 展开必报「依赖 worklist 溢出」。 + # Dedupe per module: several App files importing the same module would otherwise inflate + # app_deps toward 64KB, filling the initial worklist and overflowing BFS. im_len_a: sizet = string.strlen(tr_a._imported_modules) im_pos_a: sizet = 0 while im_pos_a < im_len_a: @@ -920,7 +873,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou if im_mod_buf is None: continue string.strncpy(im_mod_buf, tr_a._imported_modules + im_start_a, im_mod_len) im_mod_buf[im_mod_len] = '\0' - # 已在 app_deps 中则跳过(词边界精确匹配) + # Skip if already in app_deps (exact word-boundary match) if app_deps_len > 0 and GenApi.is_module_imported(app_deps_buf, im_mod_buf) != 0: stdlib.free(im_mod_buf) continue @@ -938,7 +891,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou tr_a.dump_ir(stub_buf_a, PHASE_A_IR_SIZE, mlirlite.OUTPUT_STUB) stub_len_a: sizet = string.strlen(stub_buf_a) - # save stub.ll (切片路径: temp_dir/{sha1前缀}/{sha1}.stub.ll) + # Save stub.ll (sliced path: temp_dir/{sha1 prefix}/{sha1}.stub.ll) stub_path_a: str = StubMergerCollect._sliced_path(temp_dir, td_len_pa, ent.Sha1, "stub.ll") if stub_path_a is not None: sf_a: rawptr[fileio.File] = fileio.File(stub_path_a, fileio.MODE.W) @@ -954,7 +907,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou tr_a.dump_ir(text_buf_a, PHASE_A_IR_SIZE, mlirlite.OUTPUT_TEXT) text_len_a: sizet = string.strlen(text_buf_a) - # save text.ll (切片路径) + # Save text.ll (sliced path) text_path_a: str = StubMergerCollect._sliced_path(temp_dir, td_len_pa, ent.Sha1, "text.ll") if text_path_a is not None: tf_a: rawptr[fileio.File] = fileio.File(text_path_a, fileio.MODE.W) @@ -964,10 +917,10 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou stdlib.free(text_path_a) stdlib.free(text_buf_a) - # save {sha1}.meta(结构化 sidecar:函数清单/结构体布局/虚表) + # Save {sha1}.meta (structured sidecar: function list / struct layout / vtable) StubMergerIR.WriteModuleMeta(tr_a.Module, temp_dir, td_len_pa, ent.Sha1) - # save {sha1}.full.ll(在线 FULL 产物:模型在线时的自足单模块 IR) + # Save {sha1}.full.ll (in-memory FULL artifact: a self-contained single-module IR) full_buf_a: bytes = stdlib.malloc(PHASE_A_FULL_SIZE) if full_buf_a is None: VLogger.error("full.ll 缓冲区分配失败", "PhaseA") tr_a.dump_ir(full_buf_a, PHASE_A_FULL_SIZE, mlirlite.OUTPUT_FULL) @@ -981,14 +934,14 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou stdlib.free(full_path_a) stdlib.free(full_buf_a) - # --traceback: 取出本模块的调试调用点记录并写 .vpcall。 + # --traceback: take this module's debug call-site records and write .vpcall. if BuildPipeline._traceback_mode != 0: vpcall_text_a: str = mlirlite.DebugTakeText() if vpcall_text_a is not None: BuildPipeline.WriteCallSidecar(temp_dir, ent.Sha1, vpcall_text_a) stdlib.free(vpcall_text_a) - # save dependencies (_imported_modules) for Phase B (切片路径) + # Save dependencies (_imported_modules) for Phase B (sliced path) deps_path_a: str = StubMergerCollect._sliced_path(temp_dir, td_len_pa, ent.Sha1, "deps.txt") if deps_path_a is not None: df_a: rawptr[fileio.File] = fileio.File(deps_path_a, fileio.MODE.W) @@ -999,16 +952,16 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou df_a.close() stdlib.free(deps_path_a) - # 计时点 2:stub+text dump 及写盘完成;输出逐文件进度(对齐 Phase1 1b) + # Timing point 2: stub+text dump and write done; print per-file progress (as in Phase1 1b) t_pa2: uint64 = w32.win32base.GetTickCount64() VLogger.info(f"[{i+1}/{file_count}] PhaseA: {ent.Path} (translate={t_pa1-t_pa0} ms, dump={t_pa2-t_pa1} ms, total={t_pa2-t_pa0} ms)", "PhaseA") done_flags[i] = 1 completed_this_pass += 1 - # OOM 清理:stub/text/deps 已写盘,回收本文件翻译瞬时数据 - # (AST/tokens/lexer/IR/Handler);app_deps_buf 已深拷贝依赖,不受影响 + # OOM cleanup: stub/text/deps are on disk, so free this file's transient translation data + # (AST/tokens/lexer/IR/Handler); app_deps_buf already deep-copied the deps. mb.reset_to(mark2) - # 收敛检查:所有文件完成则结束 + # Convergence check: stop when all files are done all_done: int = 1 for i_ck in range(file_count): if done_flags[i_ck] == 0: @@ -1016,8 +969,8 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou break if all_done != 0: break if completed_this_pass == 0: - # 无进展:剩余文件无法完成(真实编译错误),终止并列出未完成文件。每个失败文件附带依赖图诊断(_PhaseADumpFailGraph):pending 明细 + 依赖树。 - # VLogger.error 内部 sys.exit(1),必须放在所有图形输出之后(否则进程在 ERROR 处即退出,图永远打不出)。 + # No progress: remaining files cannot finish (real compile errors). Abort and list them, each + # with a dependency-graph dump. VLogger.error calls sys.exit(1), so it must come after all output. td_len_fail: sizet = string.strlen(temp_dir) for i_err in range(file_count): if done_flags[i_err] == 0: @@ -1031,7 +984,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou stdlib.free(entries) return 1 stdlib.free(done_flags) - # Phase A 成功收敛:释放依赖图诊断表(仅失败路径需要,成功后无用途) + # Phase A converged: free the diagnostic table (needed only on the failure path) _PhaseADiagFree() if do_phase2 == 0: @@ -1040,12 +993,12 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou stdlib.free(entries) return 0 - # === 3. Phase B: 编译每个文件为 .obj === + # === 3. Phase B: compile each file to .obj === if do_phase2 != 0: log.banner("Phase B: 编译 .obj") - # 收集 .obj 路径(增大到 64KB,避免 includes .obj 路径溢出导致链接丢失符号) - # reachable_set/reachable_count: Phase B+ 填充,Phase C 用于按需链接 + # Collect .obj paths (64KB to avoid includes .obj path overflow dropping link symbols). + # reachable_set/reachable_count: filled by Phase B+, used by Phase C for on-demand linking. reachable_set: bytes = None reachable_count: int = 0 OBJ_PATHS_SIZE: sizet = 65536 @@ -1053,18 +1006,18 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou if obj_paths is None: return 1 obj_paths[0] = '\0' obj_pos: sizet = 0 - # main_obj_path: 存放定义了用户 main 的 .obj 路径(链接时放在最前面, - # 避免链接器选中其他模块的 wrapper main) + # main_obj_path: the .obj that defines the user main (placed first at link time so the linker + # does not pick another module's wrapper main) main_obj_path: bytes = stdlib.malloc(512) if main_obj_path is None: return 1 main_obj_path[0] = '\0' compiled_count: int = 0 - # IR 缓冲区大小(16MB,容纳在线 FULL 产物 {sha1}.full.ll;FULL = stub + text) + # IR buffer size (16MB, to hold the in-memory FULL artifact {sha1}.full.ll; FULL = stub + text) COMBINED_IR_SIZE: sizet = 16777216 - # llc 任务表(stdlib.malloc,跨线程存活):连续 LlcTask 记录数组, - # 循环内只写 .ll + 构造任务,循环后 run_llc_parallel 分批并行跑 llc + # llc task table (stdlib.malloc, outlives threads): a contiguous array of LlcTask records; the loop + # only writes .ll + builds tasks, then run_llc_parallel runs llc in parallel batches. llc_tasks: bytes = stdlib.malloc(sizet(file_count) * BuildPipeline.LlcTask.__sizeof__()) if llc_tasks is None: VLogger.error("llc 任务表分配失败", "PhaseB") @@ -1075,9 +1028,9 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou ea: uint64 = uint64(entries) + i * entry_size ent: SrcFileEntry* = (SrcFileEntry*)(ea) if ent is None or ent.Path is None or ent.Sha1 is None: continue - # 单文件三步计时起点(GetTickCount64,ms) + # Start of this file's three-step timing (GetTickCount64, ms) t_pb0: uint64 = w32.win32base.GetTickCount64() - # 读在线 FULL 产物 {sha1}.full.ll → 完整 IR(取代 stub + 依赖 stub/text 合并) + # Read the in-memory FULL artifact {sha1}.full.ll -> complete IR (replaces merging stub + dep stub/text) combined_ir: bytes = stdlib.malloc(COMBINED_IR_SIZE) if combined_ir is None: VLogger.error(f"combined_ir 分配失败: {ent.Path}", "PhaseB") @@ -1087,19 +1040,19 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou VLogger.error(f"读 full.ll 失败: {ent.Path}", "PhaseB") stdlib.free(combined_ir) continue - # 计时点 1:读 full.ll 完成 + # Timing point 1: full.ll read done t_pb1: uint64 = w32.win32base.GetTickCount64() - # 写 .ll(llc 线程只读此文件);llc 本身改为循环后批量并行 + # Write .ll (llc threads only read this file); llc itself runs parallel in batches after the loop ret_w: int = BuildPipeline.write_ir_to_file(combined_ir, combined_len, temp_dir, ent.Sha1) stdlib.free(combined_ir) if ret_w != 0: VLogger.error(f"写 .ll 失败: {ent.Path}", "PhaseB") sys.exit(1) - # 计时点 2:写 .ll 完成 + # Timing point 2: .ll write done t_pb2: uint64 = w32.win32base.GetTickCount64() - # 构造 llc 任务(命令串 stdlib.malloc,线程运行期不被 reset 回收) + # Build the llc task (command string is stdlib.malloc'd so threads' lifetimes are not reset-reclaimed) llc_ir_path: str = BuildPipeline.build_sliced_path(temp_dir, ent.Sha1, "ll") if llc_ir_path is None: VLogger.error("llc 输入路径构造失败", "PhaseB") @@ -1108,29 +1061,29 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou if BuildPipeline.init_llc_task(tslot, llc_ir_path, output_dir, ent.Sha1, cc_cmd, cc_flags) != 0: VLogger.error(f"llc 任务构造失败: {ent.Path}", "PhaseB") sys.exit(1) - # 计时点 3:llc 任务构造完成 + # Timing point 3: llc task construction done t_pb3: uint64 = w32.win32base.GetTickCount64() llc_task_count += 1 compiled_count += 1 - # 显示已提交 llc 的文件(含三步计时:合并 stub / 写 .ll / 构造任务) + # Show files submitted to llc (with the three timings: merge stub / write .ll / build task) VLogger.info(f"[{i+1}/{file_count}] llc 已提交: {ent.Path} (merge={t_pb1-t_pb0} ms, write={t_pb2-t_pb1} ms, task={t_pb3-t_pb2} ms, total={t_pb3-t_pb0} ms)", "PhaseB") - # 构造 .obj 路径,检测是否是 main 模块(test_main.vp 或 main.vp,不再识别 .py) + # Build the .obj path and detect the main module (test_main.vp or main.vp; .py no longer recognized) od_len: sizet = string.strlen(output_dir) is_main_mod: int = 0 if string.strstr(ent.Path, "test_main.vp") is not None: is_main_mod = 1 elif string.strstr(ent.Path, "main.vp") is not None: is_main_mod = 1 - # 切片路径: output_dir/{sha1前缀}/{sha1}.obj + # Sliced path: output_dir/{sha1 prefix}/{sha1}.obj obj_path_sliced: str = StubMergerCollect._sliced_path(output_dir, od_len, ent.Sha1, "obj") if obj_path_sliced is not None: op_sliced_len: sizet = string.strlen(obj_path_sliced) if is_main_mod != 0: - # main 模块: 复制到 main_obj_path(确保链接时 main 在最前) + # main module: copy to main_obj_path (so main is first at link time) if op_sliced_len < 512: string.strcpy(main_obj_path, obj_path_sliced) else: VLogger.error("main_obj_path 缓冲区不足,终止编译", "PhaseB") else: - # 其他模块: 追加到 obj_paths + # Other modules: append to obj_paths if obj_pos + op_sliced_len + 2 < OBJ_PATHS_SIZE: if obj_pos > 0: obj_paths[obj_pos] = ' ' @@ -1141,13 +1094,13 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou else: VLogger.error(".obj 路径缓冲区不足", "PhaseB") stdlib.free(obj_path_sliced) - # 并行执行全部 llc(分批 LLC_MAX_THREADS 线程) + # Run all llc jobs in parallel (batched at LLC_MAX_THREADS threads) ret_p: int = BuildPipeline.run_llc_parallel(llc_tasks, llc_task_count, BuildPipeline.LLC_MAX_THREADS) if ret_p != 0: VLogger.error("llc 编译失败,终止编译", "PhaseB") sys.exit(1) - # 释放 llc 任务(命令串 + 记录) + # Free the llc tasks (command strings + records) ti: int = 0 while ti < llc_task_count: tfree: rawptr[BuildPipeline.LlcTask] = rawptr[BuildPipeline.LlcTask](uint64(llc_tasks) + sizet(ti) * BuildPipeline.LlcTask.__sizeof__()) @@ -1155,8 +1108,8 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou ti += 1 stdlib.free(llc_tasks) - # 汇编 .s 文件:llvm-mc → .obj(isr.s 等,符号为裸名,与 .vp 的 - # CExport/State stub 声明匹配)。obj 加入 obj_paths 参与 Phase C 链接。 + # Assemble .s files: llvm-mc -> .obj (isr.s etc., bare symbols matching the .vp CExport/State stubs). + # The obj is added to obj_paths for Phase C linking. od_len_as: sizet = string.strlen(output_dir) asm_compiled: int = 0 for ai_as in range(asm_file_count): @@ -1189,25 +1142,23 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou stdlib.free(asm_entries) return 1 - # === 3.5 编译缺失的 includes 文件(按依赖图按需编译)=== - # 只编译被 App 源文件直接/间接引用的 includes 文件(可达集合), - # 而非遍历整个 includes 目录。通过 _BuildReachableSha1Set 构建依赖图。 + # === 3.5 Compile missing includes (on demand via the dependency graph) === + # Only compile includes reached directly/indirectly from App sources (via _BuildReachableSha1Set). if includes_dir is not None and includes_binary_dir is not None: inc_compiled: int = 0 td_len_mi: sizet = string.strlen(temp_dir) - # 诊断日志:确认 Phase B+ 入口和 Sha1Store 状态 + # Diagnostic log: confirm the Phase B+ entry and Sha1Store state VLogger.info(f"PhaseB+ 入口: store_count={StubMergerStore.GetSha1StoreCount()} includes_binary_dir={includes_binary_dir}", "PhaseB+") - # 创建 includes_binary_dir 目录(确保 .obj 能写入和 collect_obj_files 能扫描) + # Create includes_binary_dir (so .obj files can be written and collect_obj_files can scan them) BuildPipeline.ensure_dir(includes_binary_dir) - # declare_only 预处理已移至 Phase A 之前(Phase A-pre-inc), - # 确保 Phase A 翻译 App 源文件时 includes struct 已注册。 - # PopulateIncludesDeps 已在 Phase A-pre-inc 中填充。 + # declare_only preprocessing was moved before Phase A (Phase A-pre-inc) so includes structs are + # registered when Phase A translates App sources. PopulateIncludesDeps is filled there too. - # 构建可达 SHA1 集合(依赖图遍历) - # app_deps_buf 为 None 时回退到扫描 source_dir(非递归) + # Build the reachable SHA1 set (dependency graph traversal). + # When app_deps_buf is None, fall back to scanning source_dir (non-recursive). REACHABLE_SET_SIZE: sizet = sizet(StubMerger.MAX_INCLUDES_SHA1) * 17 reachable_set: bytes = stdlib.malloc(REACHABLE_SET_SIZE) reachable_count: int = 0 @@ -1215,7 +1166,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou string.memset(reachable_set, 0, REACHABLE_SET_SIZE) reachable_count = StubMergerBuild._BuildReachableSha1Set(mb, source_dir, temp_dir, reachable_set, app_deps_buf, includes_dir) VLogger.info(f"可达 includes SHA1: {reachable_count} 个", "PhaseB+") - # fast-fail: 可达集合构建失败(依赖未找到),永不回退,直接终止 + # fast-fail: reachable-set construction failed (deps not found); never fall back, abort if reachable_count < 0: VLogger.error("可达集合构建失败(fast-fail),终止编译", "PhaseB+") return 1 @@ -1223,13 +1174,13 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou VLogger.error("reachable_set 分配失败,终止编译", "PhaseB+") return 1 - # 释放 app_deps_buf(已构建完 reachable_set,不再需要) + # Free app_deps_buf (reachable_set is built, no longer needed) if app_deps_buf is not None: stdlib.free(app_deps_buf) app_deps_buf = None - # 从全局存储器获取 SHA1/模块名 数组:复制局部副本(bytes),避免 store 联合返回值(bytes*)跨函数传参/索引的地址异常。 - # rel/incdir 仅本函数内按索引读取(不跨函数传参),保留 store 指针。 + # Get the SHA1/module-name arrays from the global store as local copies (bytes) to avoid address + # issues from passing the store's union return (bytes*) across functions; rel/incdir keep pointers. td_len_pb: sizet = string.strlen(temp_dir) store_sha1_arr_mi: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES * 17) store_mod_arr_mi: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES * 64) @@ -1240,8 +1191,8 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou store_incdir_arr_mi: bytes = StubMergerStore.GetSha1StoreIncdirArrPtr() if store_count_mi > 0: - # 拓扑排序可达 includes:被依赖模块先完整翻译。跨模块 vtable 生成 - # 需要依赖模块的函数 TypeStr 已注册到全局表(如 astaux 继承 base 的 AST) + # Topologically sort reachable includes: dependencies are fully translated first. Cross-module + # vtable generation needs dependency functions' TypeStr registered globally (e.g. astaux/base AST). topo_idx: sizet* = (sizet*)(stdlib.malloc(8 * (store_count_mi + 1))) topo_count: int = 0 if topo_idx is None: @@ -1251,13 +1202,13 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou topo_count = StubMergerTopo.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): - # 当前条目(拓扑序索引,仅含可达模块) + # Current entry (topological index; reachable modules only) ti: int = topo_idx[si_mi] - # 获取当前条目的 SHA1 和 rel_path + # Get the current entry's SHA1 and rel_path inc_sha1_mi: str = store_sha1_arr_mi + sizet(ti) * 17 inc_rel_mi: str = store_rel_arr_mi + sizet(ti) * StubMerger.MAX_REL_PATH_LEN - # 跳过 App 源文件:App 文件可能同时被 includes 扫描且在可达集合中,若 Phase B+ 再编译一份 .obj 到 includes_binary, - # 会与 Phase A/B 编译到 output/ 的 .obj 同 sha1 同符号 → 链接 duplicate symbol。 + # Skip App sources: an App file may be scanned as an include and be reachable; compiling + # another .obj into includes_binary would duplicate the output/ .obj's symbols. app_skip_mi: int = 0 for ai_mi in range(file_count): ea_ap: uint64 = uint64(entries) + ai_mi * entry_size @@ -1267,7 +1218,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou app_skip_mi = 1 break if app_skip_mi != 0: continue - # 检查 .obj 是否已存在于 includes.binary(切片子目录) + # Check whether the .obj already exists in includes.binary (sliced subdir) ibd_len_mi: sizet = string.strlen(includes_binary_dir) check_pat_mi: str = StubMergerCollect._sliced_path(includes_binary_dir, ibd_len_mi, inc_sha1_mi, "obj") check_fd_mi: w32.win32file.WIN32_FIND_DATAA* = stdlib.malloc(w32.win32file.WIN32_FIND_DATAA.__sizeof__()) @@ -1283,8 +1234,8 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou stdlib.free(check_fd_mi) continue - # .obj 不存在,需要编译 - # 构造源文件路径: {所属 includes 根目录}/{rel_path}(多目录逐条取 IncDir,空串回退 includes_dir) + # The .obj does not exist; compile it. + # Build the source path: {includes root}/{rel_path} (per-entry IncDir; empty falls back to includes_dir) rel_path_len_mi: sizet = string.strlen(inc_rel_mi) incdir_mi: str = store_incdir_arr_mi + sizet(ti) * StubMerger.MAX_INC_DIR_LEN if incdir_mi is None or incdir_mi[0] == '\0': incdir_mi = includes_dir @@ -1296,38 +1247,36 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou VLogger.error("includes 源路径分配失败,终止编译", "PhaseB+") viperlib.snprintf(src_fp_mi, inc_dir_len_mi + 1 + rel_path_len_mi + 1, "%s/%s", incdir_mi, inc_rel_mi) - # 检查是否为声明文件(读 {sha1}.meta 的结构化定义清单:无 FUNC ... DEF 即纯声明) + # Check whether it is a declaration-only file (read {sha1}.meta: no FUNC ... DEF means pure declaration) is_decl_mi: int = StubMergerIR.IsDeclOnlyModule(temp_dir, td_len_mi, inc_sha1_mi) - # is_decl_mi == -1: meta 不存在(Phase1 未翻译此文件) - # is_decl_mi == 1: 声明文件(仅 declare 无 define) - # is_decl_mi == 0: 实现文件(有 define) - # 只跳过声明文件(is_decl_mi == 1);meta 不存在时继续,让后续翻译逻辑处理 + # is_decl_mi: -1 = no meta (Phase1 did not translate it), 1 = declaration-only, 0 = impl. + # Skip only declaration-only (is_decl_mi == 1); -1 and 0 continue to the translation logic. if is_decl_mi == 1: stdlib.free(check_pat_mi) if check_fd_mi is not None: stdlib.free(check_fd_mi) stdlib.free(src_fp_mi) continue - # 尝试读在线 FULL 产物 {sha1}.full.ll(应已由 Phase1/PhaseA 生成) + # Try to read the in-memory FULL artifact {sha1}.full.ll (should be produced by Phase1/PhaseA) inc_combined_mi: bytes = stdlib.malloc(COMBINED_IR_SIZE) inc_combined_len_mi: sizet = 0 if inc_combined_mi is not None: inc_combined_len_mi = StubMergerBuild.LoadFullIR(temp_dir, inc_sha1_mi, inc_combined_mi, COMBINED_IR_SIZE) - # 如果 full.ll 不存在,翻译源文件并保存 stub/text/meta/full,然后重试 + # If full.ll is missing, translate the source, save stub/text/meta/full, then retry if inc_combined_len_mi == 0 and inc_combined_mi is not None: - # 计算 includes 文件的包名(相对 includes_dir 的目录部分) + # Compute the includes file's package name (the directory part relative to includes_dir) inc_pkg_mi: str = None if string.strlen(src_fp_mi) > inc_dir_len_mi + 1: inc_rel_mi2: str = src_fp_mi + inc_dir_len_mi + 1 inc_pkg_mi = GenApi.compute_package_from_relpath(mb, inc_rel_mi2) tr_mi: rawptr[HandlesTranslator.Translator] = BuildPipeline.TranslateFileGetTrans(mb, src_fp_mi, inc_sha1_mi, inc_pkg_mi) if tr_mi is not None: - # PBP IR 缓冲:同 PHASE_A_IR_SIZE(parser.vp text.ll >1MB 截断根因) + # PBP IR buffer: same as PHASE_A_IR_SIZE (parser.vp's text.ll >1MB truncation root cause) PBP_IR_SIZE: sizet = 8388608 - # FULL dump = stub + text,留 2× 余量 + # FULL dump = stub + text, with 2x headroom PBP_FULL_SIZE: sizet = 16777216 - # 保存 stub.ll (切片路径) + # Save stub.ll (sliced path) inc_stub_buf: bytes = stdlib.malloc(PBP_IR_SIZE) if inc_stub_buf is not None: tr_mi.dump_ir(inc_stub_buf, PBP_IR_SIZE, mlirlite.OUTPUT_STUB) @@ -1340,7 +1289,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou isf.close() stdlib.free(inc_stub_path) stdlib.free(inc_stub_buf) - # 保存 text.ll (切片路径) + # Save text.ll (sliced path) inc_text_buf: bytes = stdlib.malloc(PBP_IR_SIZE) if inc_text_buf is not None: tr_mi.dump_ir(inc_text_buf, PBP_IR_SIZE, mlirlite.OUTPUT_TEXT) @@ -1353,9 +1302,9 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou itf.close() stdlib.free(inc_text_path) stdlib.free(inc_text_buf) - # save {sha1}.meta(结构化 sidecar:函数清单/结构体布局/虚表) + # Save {sha1}.meta (structured sidecar: function list / struct layout / vtable) StubMergerIR.WriteModuleMeta(tr_mi.Module, temp_dir, td_len_mi, inc_sha1_mi) - # save {sha1}.full.ll(在线 FULL 产物) + # Save {sha1}.full.ll (in-memory FULL artifact) inc_full_buf: bytes = stdlib.malloc(PBP_FULL_SIZE) if inc_full_buf is None: VLogger.error("full.ll 缓冲区分配失败", "PhaseB+") tr_mi.dump_ir(inc_full_buf, PBP_FULL_SIZE, mlirlite.OUTPUT_FULL) @@ -1368,20 +1317,20 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou ifff.close() stdlib.free(inc_full_path) stdlib.free(inc_full_buf) - # --traceback: 取出本模块的调试调用点记录并写 .vpcall。 + # --traceback: take this module's debug call-site records and write .vpcall. if BuildPipeline._traceback_mode != 0: vpcall_text_mi: str = mlirlite.DebugTakeText() if vpcall_text_mi is not None: BuildPipeline.WriteCallSidecar(temp_dir, inc_sha1_mi, vpcall_text_mi) stdlib.free(vpcall_text_mi) - # 重试读 full.ll + # Retry reading full.ll inc_combined_len_mi = StubMergerBuild.LoadFullIR(temp_dir, inc_sha1_mi, inc_combined_mi, COMBINED_IR_SIZE) - # OOM 清理:stub/text/meta/full 已写盘,回收本 includes 文件翻译瞬时数据 - # (AST/tokens/lexer/IR/Handler);读取只碰文件,不受影响 + # OOM cleanup: stub/text/meta/full are on disk, free this includes file's transient + # translation data (AST/tokens/lexer/IR/Handler); reads only touch files. mb.reset_to(mark2) if inc_combined_len_mi == 0: - # 诊断:检查 full.ll/stub.ll/text.ll 是否存在 + # Diagnostic: check whether full.ll/stub.ll/text.ll exist diag_full: str = StubMergerCollect._sliced_path(temp_dir, td_len_mi, inc_sha1_mi, "full.ll") diag_stub: str = StubMergerCollect._sliced_path(temp_dir, td_len_mi, inc_sha1_mi, "stub.ll") diag_text: str = StubMergerCollect._sliced_path(temp_dir, td_len_mi, inc_sha1_mi, "text.ll") @@ -1413,7 +1362,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou stdlib.free(src_fp_mi) continue - # 编译为 .obj(输出到 includes_binary_dir,与存在性检查和链接收集一致) + # Compile to .obj (output to includes_binary_dir, consistent with the existence check and link collection) inc_cret_mi: int = BuildPipeline.compile_module_to_obj(inc_combined_mi, inc_combined_len_mi, temp_dir, includes_binary_dir, inc_sha1_mi, cc_cmd, cc_flags) stdlib.free(inc_combined_mi) @@ -1422,10 +1371,10 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou sys.exit(1) inc_compiled += 1 - # 显示编译完成的 includes 文件 + # Show the compiled includes file VLogger.info(f"[includes {inc_compiled}] 编译完成: {src_fp_mi}", "PhaseB+") - # 添加到 obj_paths (切片路径,从 includes_binary_dir 取) + # Add to obj_paths (sliced path, taken from includes_binary_dir) ibd_len_mi2: sizet = string.strlen(includes_binary_dir) inc_obj_sliced: str = StubMergerCollect._sliced_path(includes_binary_dir, ibd_len_mi2, inc_sha1_mi, "obj") @@ -1441,26 +1390,26 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou else: VLogger.error("includes .obj 路径缓冲区溢出,终止编译", "PhaseB+") stdlib.free(inc_obj_sliced) - # 释放本次迭代的临时内存 + # Free this iteration's temporary memory if check_pat_mi is not None: stdlib.free(check_pat_mi) if check_fd_mi is not None: stdlib.free(check_fd_mi) stdlib.free(src_fp_mi) - # 释放拓扑排序索引与局部映射副本 + # Free the topological indices and local map copies if topo_idx is not None: stdlib.free(topo_idx) if store_sha1_arr_mi is not None: stdlib.free(store_sha1_arr_mi) if store_mod_arr_mi is not None: stdlib.free(store_mod_arr_mi) VLogger.info(f"编译缺失 includes: {inc_compiled} 个", "PhaseB+") - # 释放 app_deps_buf(Phase B+ 块未执行时此处兜底释放) + # Free app_deps_buf (fallback when the Phase B+ block did not run) if app_deps_buf is not None: stdlib.free(app_deps_buf) app_deps_buf = None - # === 4. Phase C: 链接所有 .obj → .exe === + # === 4. Phase C: link all .obj -> .exe === log.banner("Phase C: 链接") od_len2: sizet = string.strlen(output_dir) @@ -1471,23 +1420,21 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou exe_path = linker_output VLogger.error("exe_path 分配失败,终止编译", "PhaseC") - # 构造最终 .obj 路径列表:main_obj_path 在前,reachable .obj 在后 + # Build the final .obj path list: main_obj_path first, reachable .objs after final_obj_paths: bytes = stdlib.malloc(OBJ_PATHS_SIZE + 512) if final_obj_paths is None: stdlib.free(entries) return 1 final_obj_paths[0] = '\0' fop_pos: sizet = 0 - # main_obj_path 放在最前面(确保链接器优先选中用户 main) + # main_obj_path goes first (so the linker prefers the user main) if main_obj_path[0] != '\0': mlen: sizet = string.strlen(main_obj_path) string.strcpy(final_obj_paths, main_obj_path) fop_pos = mlen - # 合并 Phase B/B+ 编译的 App 非 main 模块 .obj(obj_paths,空格分隔)。 - # final_obj_paths 需含 main_obj + reachable includes + App 依赖文件的 .obj,否则 App import 的模块不入链 → undefined symbol。 - # obj_paths 同时含 Phase B+ 本次新编译的 includes obj(与下方 reachable 收集重复, - # 仅本次编译的少量文件;链接前按路径去重,见 BuildPipeline.link_objs_to_exe)。 + # Merge the App non-main modules' .obj (obj_paths, space-separated) compiled by Phase B/B+. + # final_obj_paths must include main_obj + reachable includes + App deps, else linking hits undefined symbols. if obj_pos > 0: if fop_pos + obj_pos + 1 < OBJ_PATHS_SIZE + 512: if fop_pos > 0: @@ -1497,8 +1444,8 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou fop_pos += obj_pos final_obj_paths[fop_pos] = '\0' - # 按图链接:遍历 reachable_set,只收集可达 includes 的 .obj - # 不再全量收集 includes.binary 下的所有 .obj + # Link by graph: iterate reachable_set and collect only reachable includes' .obj, + # no longer collecting every .obj under includes.binary. if reachable_set is not None and reachable_count > 0: ibd_len_pc: sizet = string.strlen(includes_binary_dir) td_len_pc: sizet = string.strlen(temp_dir) @@ -1506,7 +1453,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou for ri_pc in range(reachable_count): rsha1_pc: str = reachable_set + sizet(ri_pc) * 17 if rsha1_pc[0] == '\0': continue - # 跳过 App 源文件:其 .obj 已入 final_obj_paths,若 includes_binary 残留同 sha1 的 .obj 被收集进来会与 output obj 同符号 → 链接 duplicate symbol。 + # Skip App sources: their .obj is already in final_obj_paths; collecting a same-sha1 .obj from includes_binary would duplicate symbols. app_skip_pc: int = 0 for ai_pc in range(file_count): ea_pc: uint64 = uint64(entries) + ai_pc * entry_size @@ -1518,11 +1465,11 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou if app_skip_pc != 0: continue obj_path_pc: str = StubMergerCollect._sliced_path(includes_binary_dir, ibd_len_pc, rsha1_pc, "obj") if obj_path_pc is not None: - # 检查 .obj 文件是否存在 + # Check whether the .obj file exists cf_pc: rawptr[fileio.File] = fileio.File(obj_path_pc, fileio.MODE.R) if not cf_pc.closed: cf_pc.close() - # .obj 存在,追加到 final_obj_paths + # The .obj exists; append to final_obj_paths op_len_pc: sizet = string.strlen(obj_path_pc) if fop_pos + op_len_pc + 2 < OBJ_PATHS_SIZE + 512: if fop_pos > 0: @@ -1532,10 +1479,8 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou fop_pos += op_len_pc final_obj_paths[fop_pos] = '\0' else: - # .obj 缺失:纯声明模块(text.ll 无 define 行,如 stdio.vp 全 pass) - # 无需 .obj,静默跳过;实现文件缺失才是编译产物异常(fail-fast) - # phase2command 模式:.obj 尚未生成(由手工执行 bat 产生), - # 缺失属预期——路径照常加入链接命令,跳过缺失报错。 + # .obj missing: a declaration-only module (no define lines) needs no .obj, so skip silently; + # under phase2command mode the missing .obj is expected and the path is still added. if BuildPipeline._phase2_cmd_path is not None: op_len_p2: sizet = string.strlen(obj_path_pc) if fop_pos + op_len_p2 + 2 < OBJ_PATHS_SIZE + 512: @@ -1551,12 +1496,12 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou final_obj_paths[fop_pos] = '\0' obj_paths_len: sizet = fop_pos - # 释放 reachable_set(Phase C 已使用完毕) + # Free reachable_set (Phase C is done with it) if reachable_set is not None: stdlib.free(reachable_set) reachable_set = None - # 按图链接:不传 includes_binary_dir(传 None),避免全量收集 + # Link by graph: pass None for includes_binary_dir to avoid collecting everything lret: int = BuildPipeline.link_objs_to_exe(final_obj_paths, obj_paths_len, linker_cmd, linker_flags, exe_path, None, linker_static_libs, linker_dynamic_libs) if lret == 0: @@ -1565,7 +1510,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou # where nothing was actually linked). if BuildPipeline._traceback_mode != 0 and BuildPipeline._phase2_cmd_path is None: BuildPipeline.WriteStackMap(mb, exe_path) if args.get_bool("run") and BuildPipeline._phase2_cmd_path is not None: - # phase2command 模式:exe 尚未链接(命令在 bat 中),跳过 --run + # phase2command mode: the exe is not linked yet (the command is in the bat); skip --run VLogger.info("phase2command 模式:跳过 --run(exe 尚未链接)", "phase2cmd") elif args.get_bool("run"): VLogger.info(f"执行: {exe_path}", "run") diff --git a/App/lib/Projectrans/Config.vp b/App/lib/Projectrans/Config.vp index c6c0abb..967a09d 100644 --- a/App/lib/Projectrans/Config.vp +++ b/App/lib/Projectrans/Config.vp @@ -11,57 +11,42 @@ from json import JsonValue, JSON_OBJECT, JSON_INT import w32.fileio as fileio -# ============================================================ -# Config - 工程配置 -# ============================================================ +# Config - project configuration -# 全局 mbuddy 指针 +# Global mbuddy pointer _mbuddy: rawptr[memhub.MemManager] -# 配置值(从 project.vpj 加载);默认 None/0:未加载时字段为 null,调用方须 is not None 检查兜底(否则垃圾指针 → 崩溃) +# Config values (loaded from project.vpj); default None/0: callers must guard with `is not None` (else garbage pointer crash) SourceDir: str = None BuildDir: str = None -TempDir: str = None # = {BuildDir}/temp(自动计算) -OutputDir: str = None # = {BuildDir}/output(自动计算) +TempDir: str = None # = {BuildDir}/temp (auto-computed) +OutputDir: str = None # = {BuildDir}/output (auto-computed) ProjectName: str = None ProjectVersion: str = None CompilerCmd: str = None -CompilerFlags: str = None # 编译器参数(空格分隔,从 compiler.flags 数组拼接) +CompilerFlags: str = None # compiler flags (space-separated, joined from compiler.flags array) LinkerCmd: str = None -LinkerFlags: str = None # 链接器参数(空格分隔,从 linker.flags 数组拼接) +LinkerFlags: str = None # linker flags (space-separated, joined from linker.flags array) LinkerOutput: str = None -LinkerStaticLibs: str = None # 显式静态链接文件(空格分隔绝对路径,从 linker.static_libs 数组) -LinkerDynamicLibs: str = None # 显式动态链接文件(空格分隔绝对路径,从 linker.dynamic_libs 数组,.dll/.so) +LinkerStaticLibs: str = None # explicit static libs (space-separated abs paths, from linker.static_libs array) +LinkerDynamicLibs: str = None # explicit dynamic libs (space-separated abs paths, from linker.dynamic_libs array, .dll/.so) TargetTriple: str = None TargetDataLayout: str = None -ProjectDir: str = None # project.vpj 所在目录(resolve_paths 设置) -Sha1SliceLevel: int = 0 # SHA1 切片层数(目录分散等级) +ProjectDir: str = None # directory containing project.vpj (set by resolve_paths) +Sha1SliceLevel: int = 0 # SHA1 slice level (directory dispersion depth) StrictMode: int = 0 -IncludesDir: str = None # includes 目录路径(从 includes 数组首元素,兼容单目录场景) -IncludesDirs: str = None # 全部 includes 目录(空格分隔,多 includes 目录支持) +IncludesDir: str = None # includes dir path (first element of includes array, single-dir compat) +IncludesDirs: str = None # all includes dirs (space-separated, multi-dir support) -# includes 预编译缓存目录后缀(asan 模式用 .binary_asan 隔离 asan 插桩 obj,避免与非 asan obj 混链) +# includes prebuilt cache dir suffix (asan mode uses .binary_asan to isolate instrumented obj) _inc_bin_suffix: str = ".binary" -# ============================================================ -# 发射后端:**单端 MLIR**(2026-10-03 起) -# -# 全树发射器已由 includes/llvmlite 改为 includes/mlirlite(`import mlirlite`), -# 不再有 `--backend` 开关、不再有模块别名:mlirlite 发射的是 MLIR 文本, -# 故 BuildPipeline 恒在 opt/llc 之前插入 -# mlir-opt(校验)→ mlir-translate --mlir-to-llvmir(降级回 LLVM IR)。 -# ============================================================ +# Emission backend: single-track MLIR (since 2026-10-03). +# The tree now uses includes/mlirlite; BuildPipeline always runs mlir-opt -> mlir-translate to lower back to LLVM IR. def Load_project_config(path: str) -> int: - """加载工程配置文件(project.vpj) - - Args: - path: 配置文件路径 - - Returns: - 0 表示成功,非 0 表示失败 - """ + """Load the project config file (project.vpj); 0=success, non-zero=failure.""" global SourceDir, BuildDir, TempDir, OutputDir, ProjectName, ProjectVersion global CompilerCmd, CompilerFlags, LinkerCmd, LinkerFlags, LinkerOutput, LinkerStaticLibs, LinkerDynamicLibs global TargetTriple, TargetDataLayout, Sha1SliceLevel, StrictMode @@ -70,12 +55,12 @@ def Load_project_config(path: str) -> int: if path is None: return 1 if _mbuddy is None: return 1 - # 打开文件 + # Open file f: rawptr[fileio.File] = fileio.File(path, fileio.MODE.R) if f is None: return 1 if f.closed: return 1 - # 分配读取缓冲区 + # Allocate read buffer CFG_BUF_SIZE: sizet = 8192 buf: bytes = _mbuddy.alloc(CFG_BUF_SIZE) if buf is None: @@ -87,15 +72,15 @@ def Load_project_config(path: str) -> int: if bytes_read <= 0: return 1 - # 解析 JSON + # Parse JSON root: JsonValue* = json_parse(_mbuddy, buf) if root is None: return 1 if not root.is_object(): return 1 - # 读取顶层字段(使用显式 __getitem__ 调用,兼容旧编译器二进制) + # Read top-level fields (explicit __getitem__ calls for old compiler binaries) sd_val: JsonValue* = root["source_dir"] SourceDir = sd_val.as_string() if sd_val is not None else None - # build_dir: 统一构建目录(默认 ./.tpv_build),temp 和 output 作为其子目录 + # build_dir: unified build dir (default ./.tpv_build); temp and output are subdirs bd_val: JsonValue* = root["build_dir"] if bd_val is not None and bd_val.is_string(): BuildDir = bd_val.as_string() else: BuildDir = "./.tpv_build" @@ -104,35 +89,35 @@ def Load_project_config(path: str) -> int: ver_val: JsonValue* = root["version"] ProjectVersion = ver_val.as_string() if ver_val is not None else None - # 读取 compiler 子对象 + # Read compiler sub-object compiler: JsonValue* = root["compiler"] if compiler is not None and compiler.is_object(): cc_cmd_val: JsonValue* = compiler["cmd"] CompilerCmd = cc_cmd_val.as_string() if cc_cmd_val is not None else None - # 读取 compiler.flags 数组,拼接成空格分隔的字符串 + # Read compiler.flags array, join into a space-separated string cc_flags_val: JsonValue* = compiler["flags"] CompilerFlags = _join_flags_array(cc_flags_val) - # 读取 linker 子对象 + # Read linker sub-object linker: JsonValue* = root["linker"] if linker is not None and linker.is_object(): ld_cmd_val: JsonValue* = linker["cmd"] LinkerCmd = ld_cmd_val.as_string() if ld_cmd_val is not None else None ld_out_val: JsonValue* = linker["output"] LinkerOutput = ld_out_val.as_string() if ld_out_val is not None else None - # 读取 linker.flags 数组,拼接成空格分隔的字符串 + # Read linker.flags array, join into a space-separated string ld_flags_val: JsonValue* = linker["flags"] LinkerFlags = _join_flags_array(ld_flags_val) - # 读取 linker.static_libs 数组:显式静态链接文件(.a/.obj), - # 先拼接原样字符串,resolve_paths 中再基于 project_dir 解析绝对路径 + # Read linker.static_libs array: explicit static libs (.a/.obj); joined raw here, + # then resolved to abs paths against project_dir in resolve_paths. sl_val: JsonValue* = linker["static_libs"] LinkerStaticLibs = _join_flags_array(sl_val) - # 读取 linker.dynamic_libs 数组:显式动态链接文件(.dll/.so), - # 同样先拼接原样字符串,resolve_paths 中基于 project_dir 解析绝对路径 + # Read linker.dynamic_libs array: explicit dynamic libs (.dll/.so); joined raw here, + # then resolved to abs paths against project_dir in resolve_paths. dl_val: JsonValue* = linker["dynamic_libs"] LinkerDynamicLibs = _join_flags_array(dl_val) - # 读取 target 子对象 + # Read target sub-object target: JsonValue* = root["target"] if target is not None and target.is_object(): tgt_triple_val: JsonValue* = target["triple"] @@ -140,8 +125,8 @@ def Load_project_config(path: str) -> int: tgt_dl_val: JsonValue* = target["datalayout"] TargetDataLayout = tgt_dl_val.as_string() if tgt_dl_val is not None else None - # 读取 options 子对象 - # Sha1SliceLevel: SHA1 切片层数(默认 1,每层取 SHA1 前 2 字符作为子目录:0=不切片, 1=b7/, 2=b7/90/) + # Read options sub-object + # Sha1SliceLevel: SHA1 slice level (default 1; each level uses 2 SHA1 chars: 0=none, 1=b7/, 2=b7/90/) options: JsonValue* = root["options"] Sha1SliceLevel = 1 StrictMode = 1 @@ -151,7 +136,7 @@ def Load_project_config(path: str) -> int: sm: JsonValue* = options["strict_mode"] if sm is not None and sm.is_bool(): StrictMode = 1 if sm.as_bool() else 0 - # 读取 includes 数组(支持多目录:首元素存 IncludesDir,全部拼接存 IncludesDirs) + # Read includes array (multi-dir: first element -> IncludesDir, all joined -> IncludesDirs) includes_val: JsonValue* = root["includes"] if includes_val is not None and includes_val.is_array(): arr_len: sizet = len(includes_val) @@ -165,7 +150,7 @@ def Load_project_config(path: str) -> int: inc_str: str = inc_item.as_string() inc_slen: sizet = string.strlen(inc_str) if inc_i == 0: IncludesDir = inc_str - # 拼接进 IncludesDirs(空格分隔) + # Append into IncludesDirs (space-separated) if inc_pos + inc_slen + 1 < 2048: if inc_pos > 0: inc_buf[inc_pos] = ' ' @@ -180,24 +165,14 @@ def Load_project_config(path: str) -> int: def _join_flags_array(flags_val: JsonValue*) -> str: - """将 JSON 字符串数组拼接成空格分隔的字符串 - - 用于 linker.flags 和 compiler.flags 数组。 - 用 while + 索引遍历,避免 list 迭代器 bug。 - - Args: - flags_val: JsonValue,应为字符串数组 - - Returns: - 空格分隔的字符串,或 None 如果 flags_val 为 None 或非数组或空数组 - """ + """Join a JSON string array into a space-separated string; None if empty/invalid.""" if flags_val is None: return None if not flags_val.is_array(): return None arr_len: sizet = len(flags_val) if arr_len == 0: return None - # 第一遍:计算总长度(所有 flag 长度 + 空格分隔符) + # First pass: compute total length (all flags + space separators) total_len: sizet = 0 for i in range(arr_len): item: JsonValue* = flags_val.get_item(i) @@ -208,11 +183,11 @@ def _join_flags_array(flags_val: JsonValue*) -> str: if total_len == 0: return None - # 分配缓冲区(total_len 已含每个 flag 后的空格,最后一个是 '\0') + # Allocate buffer (total_len includes a trailing space per flag; last one becomes '\0') buf: str = _mbuddy.alloc(total_len + 1) if buf is None: return None - # 第二遍:拼接(flag 之间用空格分隔) + # Second pass: join (flags separated by spaces) pos: sizet = 0 for i in range(arr_len): item = flags_val.get_item(i) @@ -230,31 +205,15 @@ def _join_flags_array(flags_val: JsonValue*) -> str: def _join_path(rel: str, project_dir: str) -> str: - """将相对路径与 project_dir 拼接为绝对路径 - - - ./App + Test → Test/App - - ./temp + Test → Test/temp - - ../includes + Test → Test/../includes(OS 解析为 includes) - - 绝对路径保持不变 - - None 返回 None - - 拼接结果经 _normalize_path 规范化:./Test/../includes → ./includes, - 消除 .. 段,保证路径显示干净且相对寻址正确。 - - 含 %VAR% 的路径(如 repl.vpj 的 build_dir = %temp%/viperepl/.tpv_build) - 先经 fileio 路径层展开为实路径:TempDir/OutputDir 等由 BuildDir 派生, - 且后续消费者不止文件打开(ensure_dir 直接调 CreateDirectoryA、 - collect_obj_files 调 FindFirstFileA、命令串走 CreateProcessA), - 故展开必须发生在**配置路径设置层**而不是逐个调用点。 - """ + """Join a relative path with project_dir, expanding env vars and normalizing it.""" if rel is None: return None - # 环境变量展开:仅在含 '%' 时生效(fileio.ExpandEnvironmentPath 自带 - # "失败/无 '%' 返回 None" 契约),展开失败时保持原串不变 + # Env var expansion: only when '%' present (fileio.ExpandEnvironmentPath returns None on + # failure/no '%'); on failure keep the original string unchanged. ep: str = fileio.ExpandEnvironmentPath(rel) if ep is not None: rel = ep - # 绝对路径(以 / 或 \ 开头)保持不变 + # Absolute paths (starting with / or \) stay unchanged if rel[0] == '/' or rel[0] == '\\': return rel - # 路径已含 project_dir 前缀时不再拼接,避免双份路径(./TransPyV/TransPyV/.tpv_build2);边界取到目录分隔符或串尾 + # Skip joining when rel already has the project_dir prefix (avoids double paths); boundary at separator or end if project_dir is not None: pdl: sizet = string.strlen(project_dir) if pdl > 0 and string.strncmp(rel, project_dir, pdl) == 0: @@ -263,7 +222,7 @@ def _join_path(rel: str, project_dir: str) -> str: rel_len: sizet = string.strlen(rel) dir_len: sizet = string.strlen(project_dir) - # 处理 ../ 或 ..\\ 前缀:拼接 project_dir/../... + # Handle ../ or ..\ prefix: join project_dir/../... if rel_len >= 3 and rel[0] == '.' and rel[1] == '.': if rel[2] == '/' or rel[2] == '\\': if dir_len == 0: return rel @@ -273,42 +232,36 @@ def _join_path(rel: str, project_dir: str) -> str: viperlib.snprintf(buf, total_len, "%s/%s", project_dir, rel) return _normalize_path(buf) - # 处理 ./ 或 .\\ 前缀:拼接 project_dir + rel[2:] + # Handle ./ or .\ prefix: join project_dir + rel[2:] rel_off: sizet = 0 if rel_len >= 2 and rel[0] == '.': if rel[1] == '/' or rel[1] == '\\': rel_off = 2 else: - return rel # 不是 ./ 开头,保持不变 + return rel # not starting with ./: keep unchanged else: - return rel # 不是 . 开头,保持不变 + return rel # not starting with .: keep unchanged if dir_len == 0: - # project_dir 为空(当前目录),去掉 ./ 前缀即可 + # project_dir empty (current dir): just strip the ./ prefix return rel + rel_off - # 拼接: project_dir + "/" + rel[rel_off:] + # Join: project_dir + "/" + rel[rel_off:] total_len = dir_len + 1 + (rel_len - rel_off) + 1 buf = _mbuddy.alloc(total_len) if buf is None: - return rel # 分配失败,返回原路径 + return rel # allocation failed: return original path viperlib.snprintf(buf, total_len, "%s/%s", project_dir, rel + rel_off) return _normalize_path(buf) def _normalize_path(path: str) -> str: - """规范化路径:解析 . 和 .. 段,合并多余分隔符 - - 例: ./test/BuddyTest/../../includes → ./includes - ./test/BuddyTest/App → ./test/BuddyTest/App - 绝对路径(/ 或 \\ 开头,含盘符 C:)保持不变。 - 返回 _mbuddy 分配的字符串;失败时返回原路径。 - """ + """Normalize a path by resolving . and .. segments; absolute paths pass through.""" if path is None: return None plen: sizet = string.strlen(path) if plen == 0: return path - # 绝对路径(/ 或 \ 开头,或盘符如 C:)不规范化 + # Absolute paths (/ or \ start, or drive letter like C:) are not normalized if path[0] == '/' or path[0] == '\\': return path if plen >= 2 and path[1] == ':': return path - # 输出缓冲(最坏情况 = 输入长度 + 可能的前缀 ./) + # Output buffer (worst case = input length + possible ./ prefix) out: str = _mbuddy.alloc(plen + 3) if out is None: return path seg_start: sizet = 0 @@ -321,26 +274,26 @@ def _normalize_path(path: str) -> str: if ch == '/' or ch == '\\' or ch == '\0': seg_len: sizet = i - seg_start if seg_len > 0: - # 单段 ".":跳过 + # Single "." segment: skip if seg_len == 1 and path[seg_start] == '.': pass - # 单段 "..":弹出上一段;前导 ".." 无段可弹时必须保留(对齐 Python os.path.normpath) + # Single ".." segment: pop previous; leading ".." with nothing to pop must be kept (like Python normpath) elif seg_len == 2 and path[seg_start] == '.' and path[seg_start + 1] == '.': - # 末段是否已经是前导 ".."(独立一段:要么是开头,要么前面是分隔符) + # Whether the last segment is already a leading ".." (standalone: at start or preceded by a separator) last_is_dotdot: int = 0 if out_len >= 2 and out[out_len - 1] == '.' and out[out_len - 2] == '.': if out_len == 2: last_is_dotdot = 1 elif out[out_len - 3] == '/' or out[out_len - 3] == '\\': last_is_dotdot = 1 if out_len > 0 and last_is_dotdot == 0: - # 回退到上一个分隔符 + # Back up to the previous separator k: sizet = out_len while k > 0 and out[k - 1] != '/' and out[k - 1] != '\\': k -= 1 if k > 0: out_len = k - 1 else: out_len = 0 - # 仍有剩余段时保持分隔符逻辑 + # Keep separator logic when segments remain if out_len > 0: has_seg = 1 else: has_seg = 0 else: - # 没有普通段可弹:把 ".." 当普通段追加(前导 .. 累积保留) + # No normal segment to pop: append ".." as a normal segment (keep leading ..) if has_seg > 0: out[out_len] = '/' out_len += 1 @@ -349,7 +302,7 @@ def _normalize_path(path: str) -> str: out_len += 2 has_seg = 1 else: - # 普通段:追加分隔符 + 段 + # Normal segment: append separator + segment if has_seg > 0: out[out_len] = '/' out_len += 1 @@ -362,11 +315,11 @@ def _normalize_path(path: str) -> str: seg_start = i + 1 i += 1 if has_seg == 0: - # 全部段被消掉(如 ./..):返回当前目录 + # All segments consumed (e.g. ./..): return current dir return "./" - # 相对路径补回 ./ 前缀(与原路径风格一致;以 .. 开头的除外) + # Re-add ./ prefix for relative paths (matches original style; except those starting with ..) if out[0] != '.' and out[0] != '/': - # 右移 2 位留出 "./"(从后往前拷贝,避免覆盖源字符) + # Shift right by 2 to make room for "./" (copy back-to-front to avoid overwriting) mv: sizet = out_len while mv > 0: mv -= 1 @@ -379,17 +332,7 @@ def _normalize_path(path: str) -> str: def resolve_paths(project_dir: str) -> int: - """解析并规范化工程路径(src/build/includes) - - 将 project.vpj 中的相对路径(以 ./ 开头)转换为基于 project_dir 的路径。 - BuildDir 解析后,TempDir = {BuildDir}/temp,OutputDir = {BuildDir}/output。 - - Args: - project_dir: 工程根目录(project.vpj 所在目录) - - Returns: - 0 表示成功,非 0 表示失败 - """ + """Resolve and normalize project paths (src/build/includes); 0=success.""" global SourceDir, BuildDir, TempDir, OutputDir, IncludesDir, IncludesDirs global ProjectDir, LinkerStaticLibs, LinkerDynamicLibs if project_dir is None: return 1 @@ -400,7 +343,7 @@ def resolve_paths(project_dir: str) -> int: SourceDir = _join_path(SourceDir, project_dir) BuildDir = _join_path(BuildDir, project_dir) - # TempDir = {BuildDir}/temp(单端 MLIR 恒追加 _mlir 隔离后缀) + # TempDir = {BuildDir}/temp (single-track MLIR always appends the _mlir isolation suffix) if BuildDir is not None: bd_len: sizet = string.strlen(BuildDir) suf_dir: str = mlir_dir_suffix() @@ -415,8 +358,8 @@ def resolve_paths(project_dir: str) -> int: viperlib.snprintf(output_buf, bd_len + suf_dir_len + 8, "%s/output%s", BuildDir, suf_dir) OutputDir = output_buf - # includes 路径可能是 ../includes 形式,需要基于 project_dir 解析 - # IncludesDir(首元素)与 IncludesDirs(全部,空格分隔)逐项规范化 + # includes paths may be of the form ../includes, resolved against project_dir. + # Normalize IncludesDir (first element) and IncludesDirs (all, space-separated) item by item. if IncludesDirs is not None: id_total: sizet = string.strlen(IncludesDirs) id_out: str = _mbuddy.alloc(id_total * 3 + 64) @@ -446,7 +389,7 @@ def resolve_paths(project_dir: str) -> int: id_out[id_pos] = '\0' IncludesDirs = id_out if IncludesDir is not None: IncludesDir = _join_path(IncludesDir, project_dir) - # linker.static_libs 显式静态链接文件路径基于 project_dir 逐段解析为绝对路径 + # linker.static_libs explicit paths resolved segment-by-segment to abs paths against project_dir if LinkerStaticLibs is not None: sl_total: sizet = string.strlen(LinkerStaticLibs) sl_out: str = _mbuddy.alloc(sl_total * 3 + 64) @@ -475,8 +418,7 @@ def resolve_paths(project_dir: str) -> int: sl_pos += jl sl_out[sl_pos] = '\0' LinkerStaticLibs = sl_out - # linker.dynamic_libs 显式动态链接文件(.dll/.so)同样基于 project_dir - # 逐段解析为绝对路径 + # linker.dynamic_libs explicit files (.dll/.so) likewise resolved to abs paths against project_dir. if LinkerDynamicLibs is not None: dl_total: sizet = string.strlen(LinkerDynamicLibs) dl_out: str = _mbuddy.alloc(dl_total * 3 + 64) @@ -505,16 +447,13 @@ def resolve_paths(project_dir: str) -> int: dl_pos += jl dl_out[dl_pos] = '\0' LinkerDynamicLibs = dl_out - # linker.flags 中 -T <脚本> 的路径同样基于 project_dir 解析,否则 ld.lld 按进程 cwd 找不到脚本 + # -T