chore: 清理超长历史注释,并一并提交并行会话的代码改动

注释清理(65 文件 / 约 -2578 行 / 约 516 处):删除/压缩叙述式历史注释——多行根因叙事、工作代号段(D25/D30/N11/B5/S4/阶段E 等)、改前改后与先例叙述、逐条推导块,压成一行讲清作用信息;契约类注释(用途、参数与返回、关键约束与不变量、IR 结构说明)与被注释掉的代码一律保留

存疑 标记 6 处一字未动:StubMergerStore.vp、VpiReader.vp、VLogger.vp、AssignGenMixin.vp、CallGenLib.vp、CallGenLeafKit.vp

本次一并带入并行会话的代码改动(同批文件,按文件粒度无法拆分):fileio.File -> rowptr[fileio.File] 等

已知副作用/残留:① CallGenDispatch.vp 有 2 行非注释行的行尾被 LF->CRLF 规范化(纯字节变化,无文本改动);② core/Gen 区约 24 处压缩因并行会话整文件覆写被写回原样,尚未生效(StmtGenCifMixin/ClassGenVTable/FuncGenDef/ClassGenEnum/StructGenMixin/TypeSpec 等)

清单与复跑判据见 Debugger/comment_cleanup_summary.md(该目录不受版本控制)
This commit is contained in:
TermiNexus committed 2026-09-16 11:27:05 +08:00
1 parent b28f20cce7
commit 7cba4cbc95
65 files changed
+1233 -3292

No files matched your search

+23 -62
View File
@@ -54,22 +54,12 @@ SRC_BUF_SIZE: t.CDefine = 1048576 # 2^20 = 1MB
PYI_BUF_SIZE: sizet = 262144
# ============================================================
# TranslateFileGetTrans - 翻译单个文件,返回 Translator 对象
# TranslateFileGetTrans - 翻译单个文件,返回 Translator 对象(供调用者 dump_ir)
#
# 读取文件 → AST 解析 → .vpi 生成 → LLVM IR 翻译,返回 Translator 对象供
# 调用者 dump_ir。命名空间隔离:includes 文件宽松模式(全部可见),
# 用户文件严格模式(仅本地+import)。
# 读取文件 → 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 失败)
# 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 失败)
# ============================================================
def TranslateFileGetTrans(mb: rowptr[memhub.MemBuddy], file_path: str,
sha1_val: str,
@@ -83,7 +73,7 @@ def TranslateFileGetTrans(mb: rowptr[memhub.MemBuddy], file_path: str,
temp_dir: temp 目录路径(非 None 时在翻译前生成 .vpi 存根文件)
"""
if file_path is None: return None
f: fileio.File | t.CPtr = fileio.File(file_path, fileio.MODE.R)
f: rowptr[fileio.File] = fileio.File(file_path, fileio.MODE.R)
if f.closed: return None
# read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被**静默截断**;
# 这里先按文件大小拒绝, 否则拿截断后的源码去 parse 只会得到莫名其妙的语法错误
@@ -131,7 +121,7 @@ def TranslateFileGetTrans(mb: rowptr[memhub.MemBuddy], file_path: str,
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:
pf: fileio.File | t.CPtr = fileio.File(pyi_path, fileio.MODE.W)
pf: rowptr[fileio.File] = fileio.File(pyi_path, fileio.MODE.W)
if not pf.closed:
pf.write(pyi_buf, pyi_pos)
pf.close()
@@ -173,11 +163,8 @@ def TranslateFileGetTrans(mb: rowptr[memhub.MemBuddy], file_path: str,
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 读
# 0xffffffffffffffff 崩溃),docstring 提取与翻译状态无关,提前安全。
# 提取 docstring(预注册阶段所有 App/includes 文件都被翻译,保证跨模块 __doc__ 在正式翻译前已登记;查重幂等)
# 必须在 tr.translate 之前扫描:translate 期间 mb 池大量分配 IR 节点,AST 节点所在池块可能被复用覆盖(ASan 下遍历 tree 会崩溃)。
if gen is not None:
gen.ExprGenCls.extract_module_docstrings(tree, sha1_val)
ret: int = tr.translate(tree)
@@ -237,22 +224,12 @@ def ensure_dir(path: str) -> int:
# ============================================================
# build_sliced_path - 构建切片路径并确保目录存在
# 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}
# 按 Config.Sha1SliceLevel 分散到 SHA1 前缀子目录:level=0 → {base_dir}/{sha1}.{ext};level=1 → {base_dir}/b7/{sha1}.{ext};level=2 → {base_dir}/b7/90/{sha1}.{ext}
#
# 自动调用 ensure_dir 创建子目录。
#
# Args:
# base_dir: 基础目录(如 temp_dir)
# sha1: SHA1 字符串
# ext: 文件扩展名(如 "stub.ll")
#
# Returns:
# 完整文件路径(mbuddy 分配),None 失败
# Args: base_dir 基础目录(如 temp_dir);sha1 SHA1 字符串;ext 文件扩展名(如 "stub.ll")
# Returns: 完整文件路径(mbuddy 分配),None 失败
# ============================================================
def build_sliced_path(base_dir: str, sha1: str, ext: str) -> str:
"""构建切片路径并确保目录存在"""
@@ -305,7 +282,7 @@ def write_ir_to_file(ir_buf: bytes, ir_len: sizet, output_dir: str, module_name:
if path is None: return 1
# 打开文件写入
f: fileio.File | t.CPtr = fileio.File(path, fileio.MODE.W)
f: rowptr[fileio.File] = fileio.File(path, fileio.MODE.W)
if f.closed: return 1
written: int64 = f.write(ir_buf, ir_len)
@@ -400,12 +377,8 @@ def compile_ll_to_obj(ir_path: str, output_dir: str, module_name: str, cc_cmd: s
# llc 并行编译(线程异步,Phase B 多模块同时跑 llc)
#
# 线程安全约束:
# - LlcTask 及命令串一律 stdlib.malloc:主池 mb 会被 reset_to(mark2)
# 回收,线程运行期间字符串悬空 → 概率崩溃
# - worker 只调 _run_cmd_wait(内部仅 stdlib.malloc/CreateProcessA/
# GetLastError/CloseHandle;CreateProcessA 失败时加一条 VLogger.warning
# 诊断,只走 CRT printf/控制台着色),不做任何池分配(MemBuddy 分配
# 非线程安全)
# - 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 核)
@@ -494,7 +467,7 @@ def _phase2_write_cmd(cmd: str) -> None:
global _phase2_bat_init
if _phase2_cmd_path is None or cmd is None:
return
f: fileio.File | t.CPtr = fileio.File(_phase2_cmd_path, fileio.MODE.A)
f: rowptr[fileio.File] = fileio.File(_phase2_cmd_path, fileio.MODE.A)
if f.closed:
VLogger.error("phase2command: bat 打开失败", "phase2cmd")
return
@@ -608,11 +581,8 @@ def run_llc_parallel(tasks: bytes, task_count: int, max_threads: int) -> int:
return 0
if max_threads <= 0:
max_threads = 1
# 线程句柄数组:list[str](str=bytes=i8*,elem_size=8 显式)存句柄值。
# 此前 list[t.CPtr] 特化修复后 append 仍崩(pool 有效、elem=8 下读零页,
# list[ptr] append 的 store 与 t.CPtr 参数类型生成待查);list[str] 全库
# 验证稳定(append/get 8 字节指针)。start() 后立即取 handle 存入,
# join/close 直接对句柄操作,不依赖 Thread 对象存续。
# 线程句柄数组:list[str](str=bytes=i8*,elem_size=8 显式)存句柄值;list[t.CPtr] 特化下 append 会读零页崩溃(原因待查),list[str] 全库验证稳定。
# start() 后立即取 handle 存入,join/close 直接对句柄操作,不依赖 Thread 对象存续。
handles: list[str] | t.CPtr = list[str](_mbuddy, 8)
if handles is None:
VLogger.error("run_llc_parallel: 线程句柄数组分配失败", "PhaseB")
@@ -781,15 +751,9 @@ def link_obj_to_exe(output_dir: str, module_name: str, linker_cmd: str, linker_f
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 文件必须在 -l 库标志之前,否则链接器无法解析符号依赖
# linker_static_libs / linker_dynamic_libs(project.vpj 显式指定的 .a/.obj/.dll/.so)
# 同样位于 -l 之前;动态库作为输入文件由 GNU ld 提取导入符号
# ASan 模式:ASan 运行库**不再**由 TPV 硬编码。clang 没有给
# x86_64-w64-windows-gnu 出 ASan 运行库,只能借用 clang64(MSVC ABI) 的
# libclang_rt,但那是工程级选择(且会牵进 MSVC 专有的 .drectve 指令),
# 不该由所有工程无条件承担 —— 改由 project.vpj 的
# linker.static_libs / linker.dynamic_libs 显式声明,main.vp 在 --asan
# 下会用 Config.asan_runtime_declared 校验是否声明过。
# 注意: .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 校验。
use_flags: str = linker_flags
static_libs: str = linker_static_libs
if static_libs is None:
@@ -963,11 +927,8 @@ def link_objs_to_exe(obj_paths: str, obj_paths_len: sizet,
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 收集指向 includes.binary 中
# 同一文件)。App 项目 linker flags 含 --allow-multiple-definition 可
# 容忍;VKernel 无此 flag → ld.lld "duplicate symbol"。链接命令中同一
# 路径出现两次即重复符号,保留首次出现的路径,后续重复跳过。
# 去重 .obj 路径:同一模块的 obj 可能被多个收集环节加入(Phase B+ 新编译的 obj_paths 与 Phase C reachable 收集指向同一文件);
# 同一路径在链接命令中出现两次即重复符号(无 --allow-multiple-definition 的工程会 ld.lld 报错),故保留首次出现、后续跳过。
if obj_paths_len > 0:
dedup: bytes = _mbuddy.alloc(obj_paths_len + 1)
if dedup is not None:
+64 -147
View File
@@ -32,11 +32,8 @@ import lib.core.Gen.GenApi as GenApi
# ============================================================
# _InjectedCreatesCycle - 容器模块自动注入前的破环检查
#
# 注入 _list/_dict 到当前模块的 _imported_modules 前,检查被注入模块自身的
# 依赖是否已含当前模块。若命中(如 _list 显式 import memhub,而 memhub 因
# `__provides__: list[str]` 注解要注入 _list)→ memhub↔_list 循环依赖 →
# Phase A 多遍收敛死锁(gfx.vp "无法完成翻译" 根因)。命中则跳过注入:去掉
# 环边后,_list 已因当前模块被其依赖而处于闭包,list 类仍由 Phase1b 注册。
# 注入 _list/_dict 前检查被注入模块自身的依赖是否已含当前模块,命中(如 memhub↔_list)则跳过注入——
# 否则 Phase A 多遍收敛死锁("无法完成翻译");去掉环边后 _list 仍因当前模块被其依赖而处于闭包,list 类仍由 Phase1b 注册。
# ============================================================
def _InjectedCreatesCycle(entry: IncludesScanner.FileEntry | t.CPtr,
mod_name: str) -> int:
@@ -149,7 +146,7 @@ def _RewriteDepsTxt(temp_dir: str, sha1: str, deps_str: str) -> None:
dpath: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1, "deps.txt")
if dpath is None:
return
df: fileio.File | t.CPtr = fileio.File(dpath, fileio.MODE.W)
df: rowptr[fileio.File] = fileio.File(dpath, fileio.MODE.W)
if not df.closed:
dl: sizet = string.strlen(deps_str)
if dl > 0:
@@ -230,18 +227,12 @@ def _BreakContainerCyclesInDeps(temp_dir: str) -> int:
# ============================================================
# _BuildReachableResult - 从 Sha1Store 构建可达 FileEntry 列表
#
# 依赖图按需扫描(StubMergerBuild._BuildReachableSha1Set)只登记
# "可达 includes + App"到全局 Sha1Store;本函数把 incdir 非空条目
# (includes)重建为 Phase1 1a/1b-pre/1b 需要的 FileEntry 列表。
# App 条目(incdir 空,仅登记不做可达文件处理)不进入 result——
# Phase B 已编译其 .obj,加入会导致 Phase B+ 重复编译 → 重复符号。
# 依赖图按需扫描(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
# Args: mb 内存池;includes_dir includes 根目录(仅用于失败诊断,路径以 store 为准)
# Returns: ScanResult 指针(stdlib 分配,调用方负责释放);失败返回 None
# ============================================================
def _BuildReachableResult(mb: rowptr[memhub.MemBuddy],
includes_dir: str) -> IncludesScanner.ScanResult | t.CPtr:
@@ -314,26 +305,18 @@ def _BuildReachableResult(mb: rowptr[memhub.MemBuddy],
return result
# ============================================================
# Phase1Main - Phase1 拆分(阶段 E):Phase1Main
# Phase1Main - Phase1 主流程(扫描 → 预注册 → 1a/1b 翻译 → 生成 stub)
#
# 直接移动模式:函数迁至此模块,原 Phase1.vp 仅保留常量/_mbuddy;
# 调用方经本模块前缀访问。全局符号经 Phase1._xxx 跨模块访问。
# 函数实现直接位于本模块,调用方经本模块前缀访问;全局符号经 Phase1._xxx 跨模块访问。
# ============================================================
# ============================================================
# RunPhase1 - Phase1: 扫描 includes 目录,按需翻译并生成 stub
#
# 对每个 .vp/.py 文件检查 stub 是否已存在,若不存在则翻译并分离 stub。
# "按需翻译":仅对 stub 不存在的文件执行翻译,避免重复工作。
# 对每个 .vp/.py 文件检查 stub 是否已存在,不存在才翻译并分离 stub(避免重复工作)。
#
# Args:
# mb: 内存池
# includes_dir: includes 目录路径
# temp_dir: 临时目录(保存 stub)
# log: 日志器
#
# Returns:
# 0 成功,非 0 失败
# Args: mb 内存池;includes_dir includes 目录路径;temp_dir 临时目录(保存 stub);log 日志器
# Returns: 0 成功,非 0 失败
# ============================================================
def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
log: VLogger.Logger | t.CPtr) -> int:
@@ -345,9 +328,7 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
# 设置全局 temp_dir(供跨模块 CDefine 查找使用)
GenApi.set_temp_dir(temp_dir)
# 同步设置 HandlesExprCall 的 temp_dir(供 vpi stub 返回注解缓存按需加载)。
# 必须在 includes 翻译前设置:Phase1 翻译 json 等 includes 时,"同模块方法
# 调用(定义在后)"(如 json.__setitem__ 调 _pool_alloc)需要读
# {temp_dir}/{sha1[0:2]}/{sha1}.vpi 兜底,而 Phase2 的 SetTempDir 太晚。
# 必须在 includes 翻译前设置:Phase1 翻译 includes 时「同模块方法调用(定义在后)」需读 {temp_dir}/{sha1[0:2]}/{sha1}.vpi 兜底,Phase2 的 SetTempDir 太晚。
GenApi.SetTempDir(temp_dir)
# 确保 temp 目录存在(build_dir/temp 可能尚未创建)
@@ -356,17 +337,12 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
log.banner("Phase1: 扫描 includes(按需翻译)")
# 初始化 AST 表(只需一次):必须在依赖图 BFS 扫描之前调用。
# _init_tables 懒分配关键字/操作符表(全局 _kw_head 等),若首次触发
# 发生在 BFS 的某个文件级 mark 之后,reset_to 会回收表节点 → 全局
# 悬空 → 后续 tokenize 的 _kw_lookup strcmp 访问 0x4 崩溃(1.log ASan)。
# _init_tables 懒分配的关键字/操作符表(全局 _kw_head 等)若首次触发在 BFS 的文件级 mark 之后,reset_to 会回收表节点 → 全局悬空 → 后续 tokenize 崩溃。
ast._init_tables(mb)
# 依赖图按需扫描(App 优先模型):
# 1) 先扫 App 源文件(Config.SourceDir)的 import 依赖 → 初始队列
# 2) BFS 沿队列按文件系统解析 includes 文件,只对可达 includes
# 读文件+算 SHA1+解析 import,补充依赖图并登记 Sha1Store/
# deps 存储器/写 deps.txt
# 3) 未引用的 includes 从不读取(根本不扫描)
# 依赖图按需扫描(App 优先模型):先扫 App 源文件(Config.SourceDir)的 import 依赖作为初始队列,
# BFS 按文件系统解析 includes 文件(只对可达者读文件+算 SHA1+解析 import)并登记 Sha1Store/deps/写 deps.txt;
# 未引用的 includes 从不读取。
t_scan0: uint64 = w32.win32base.GetTickCount64()
reachable_set: str = None
reachable_count: int = 0
@@ -425,26 +401,16 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
# ============================================================
# Phase 1a: 注册可达 includes 文件的 struct/enum/union
#
# 解决字母序依赖问题:按字母顺序翻译时,后面的 struct 未注册
# 前面就需要用。Phase 1a 先注册所有 struct/enum/union 到全局表,
# Phase 1b 全量翻译时 struct 已注册,走 existing 路径只翻译方法体。
# 只处理可达文件,避免翻译不需要的 includes(如 Test 不依赖 ast 模块)。
#
# 拓扑排序:预扫描所有文件收集类继承关系,按依赖顺序处理文件,
# 确保父类所在文件先于子类所在文件处理(如 base.vp 先于 astaux.vp)。
# 解决字母序依赖问题:1a 先把所有 struct/enum/union 注册到全局表,1b 全量翻译时 struct 已注册、走 existing 路径只翻译方法体;
# 只处理可达文件;处理前按类继承关系拓扑排序,确保父类所在文件先于子类所在文件。
# ============================================================
p1a_reg: int = 0
p1a_skp: int = 0
p1a_fl: int = 0
# ============================================================
# [临时取证打点] Phase1 早期扁平缓冲分配失败现场快照(定位后清理)。
# 现场:class_names_buf/parent_names_buf 均成功,def_sha1s_buf 却返回 NULL;
# 此时主池(1GiB)远未耗尽 → 非 OOM。本打点仅只读 mb 状态 + 打印,
# 绝不改变任何分配行为与逻辑。三维度判定:
# ① OOM:dump_stats 显示 used≈total、free≈0;
# ② mb 损坏:usable 基址/大小荒谬、self_check != 0、max_order 异常;
# ③ size/order 异常:本次请求 size 算出的 order > max_order。
# [临时取证打点] 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) 掩盖后续打点)。
# ============================================================
if mb is not None:
@@ -486,7 +452,7 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
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"(fl==0 只说明该阶空、仍可由更高阶拆分;若 alloc 失败而 dump_stats 的 free>0 才是元数据损坏铁证,"
f"任一 order>max_order={mb.max_order} 则是 size 异常)",
"Phase1")
@@ -504,13 +470,9 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
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 使用,
# 不属 _ScanClassInheritance 契约,故不并入此处终止逻辑;其有效性由
# _TopoSortFiles 入口自身校验(out_order/out_count 为 NULL 即返回 -1 并终止)。
# 三块扁平缓冲是 Phase1Scan._ScanClassInheritance 的契约前置缓冲:该函数入口对 NULL fail-fast(静默跳过整段会退化为字母序并掩盖 OOM 等内存故障),
# 故分配失败时逐块显式报错终止(VLogger.error 内部 sys.exit(1)),绝不静默回退。
# 注:topo_order/topo_count_box 仅供 Phase1Topo._TopoSortFiles 使用,有效性由其入口自身校验(NULL 即返回 -1 并终止),不并入此处终止逻辑。
if class_names_buf is None:
VLogger.error("类继承预扫描扁平缓冲分配失败:class_names_buf 为 NULL,终止编译", "Phase1")
if parent_names_buf is None:
@@ -559,14 +521,8 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
# ============================================================
# Phase 1a-pre-generic-all: mark1 前全量预注册 includes 泛型模板
#
# 1a-pre-generic 只覆盖"可达"泛型文件;非可达泛型文件(如 FuncTest
# 场景的 linkedlist/llvmlite/_list)的模板要等 Phase2 Phase A-pre-inc
# (mark2 之后)才注册 → reset 回收表/节点 → _find_generic_template
# 此处对全部 includes 轻量预注册,使模板表与节点都落在 mark1 之前。
# result 只是"可达子集",非可达的 _list/_dict/linkedlist/llvmlite 等泛型文件
# 模板不会注册 → list[int]/dict[...] 特化失败(ast/base.vp _dump_op_list 根因)。
# _PreRegisterAllGenericTemplates 内有 generic marker 文本预筛,全量扫描
# 不 parse 全部 AST,仅对含 marker 的文件轻量处理,OOM 风险可控。
# 非可达泛型文件(_list/_dict/linkedlist/llvmlite 等)的模板若在 Phase2(mark2 之后)才注册会被 reset 回收 → list[int]/dict[...] 特化失败。
# 故对全部 includes 轻量预注册,使模板表与节点都落在 mark1 之前(_PreRegisterAllGenericTemplates 内有 generic marker 文本预筛,不 parse 全部 AST,OOM 风险可控)。
full_scan_g: IncludesScanner.ScanResult | t.CPtr = IncludesScanner.scan_includes(mb, includes_dir)
if full_scan_g is not None:
Phase1Scan._PreRegisterAllGenericTemplates(mb, full_scan_g)
@@ -576,14 +532,9 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
# ============================================================
# Phase 1a: 注册 struct/enum/union(mark1 前,全量 includes)
#
# OOM 治理:1a 对全部 includes parse 完整 AST(主 pool 分配),若
# 驻留到 mark1(循环后)会累积 98 文件 × 每文件 AST 树 → 1GB 主池
# 耗尽(NewAstTest alloc 失败 0xBE 根因)。现改为:
# 1) StdlibAlloc=1:1a 注册的跨文件持久数据(StructTable 表、
# Fields/Name/ModuleSha1、struct_ty 深拷贝、枚举/Renum/泛型
# 模板等)全部落在 perm_pool(永不 reset),不占主 pool;
# 2) 每文件翻译完 reset_to(文件级 mark):仅回收该文件的 AST/
# lexer/token/IR 等瞬时数据,注册数据不受影响。
# 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) 语义不变。
# ============================================================
GenApi.set_stdlib_alloc(1)
@@ -630,7 +581,7 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
# 读取文件内容
file_path_r: str = entry_r.Path
f_r: fileio.File | t.CPtr = fileio.File(file_path_r, fileio.MODE.R)
f_r: rowptr[fileio.File] = fileio.File(file_path_r, fileio.MODE.R)
if f_r.closed:
p1a_fl += 1
continue
@@ -707,19 +658,10 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
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
# (includes/memhub.vp reset_to L540 / 截断循环 L558-559),之后再查同一
# mark 时 from_mark >= chk_depth 直接 return 0(includes/memhub.vp
# check_alive_blocks L1234-1235)→ 窗口为空、必然漏报。
# p1a_mark 在循环体首行捕获(本文件 L597),故窗口恰为"本文件
# parse + translate(declare=1)"期间分配的块。mark 语义与既有 3 处哨兵
# (L1109/L1135/L1226 均传 mark1 = 各自窗口起点)一致。
# 仅 --detection-deep(mb.deep_detect != 0)时执行:本哨兵每文件一次、
# 每次遍历整窗口活块(O(n)),默认全程不进入,回到上一轮之前的开销。
# 读法同本文件既有 mb.detection(L470):直接读 MemBuddy 实例的公开
# int 字段(mb: rowptr[memhub.MemBuddy])。
# [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 字段。
if mb.deep_detect != 0:
chk_rc_1a: int = mb.check_alive_blocks(p1a_mark)
if chk_rc_1a != 0:
@@ -754,11 +696,8 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
# ============================================================
# Phase 1b: 全量翻译(struct 已注册,走 existing 路径翻译方法体)
# ============================================================
# OOM 回收:1a-pre/泛型预注册/1a 全部结束后打 mark1,
# 所有跨文件持久表(_struct_table/_enum_table/_generic_class_nodes/
# _renum_*/关键字表等)都落在 mark1 之前。
# 1b 每文件翻译完后 reset_to(mark1) 回收该文件的 AST/IR/Handler/dump 临时,
# 避免 90 文件累积导致 1GB 池 OOM。
# 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。
mark1: sizet = mb.mark()
# 1b 阶段新注册的 struct/enum 条目改 stdlib 分配(跨文件永久存活,不被 reset 回收)
@@ -767,11 +706,8 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
# ============================================================
# Phase 1b-pre: 预生成所有待翻译文件的 .vpi stub
#
# 依赖模块的 .vpi 必须在翻译前可用:1b 主循环按扫描顺序翻译(非拓扑),
# 若 argparse 先于 hashtable 翻译,hashtable.vpi 尚未生成,
# LoadDepVpiIntoModule 读不到 → declare 未注入 → 跨模块调用
# 返回类型推断错误(如 HashTable.__getitem__ 被推断为 Ptr(HashTable)
# 而 define 返回 i8*)→ 运行时返回值截断崩溃。
# 依赖模块的 .vpi 必须在翻译前可用:1b 主循环按扫描顺序(非拓扑)翻译,若依赖模块后翻译则 LoadDepVpiIntoModule 读不到 .vpi
# → declare 未注入 → 跨模块调用的返回类型推断错误(如误推为 Ptr 而 define 返回 i8*)→ 运行时返回值截断崩溃。
# ============================================================
p1bpre_failed: int = 0
dir_len_pv: sizet = string.strlen(temp_dir)
@@ -797,7 +733,7 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
p1bpre_failed += 1
VLogger.error(f"[Phase 1b-pre] {entry_pv.Path} | vpi 路径生成失败", "Phase1")
continue
vf_pv: fileio.File | t.CPtr = fileio.File(vpi_path_pv, fileio.MODE.R)
vf_pv: rowptr[fileio.File] = fileio.File(vpi_path_pv, fileio.MODE.R)
if not vf_pv.closed:
vf_pv.close()
stdlib.free(vpi_path_pv)
@@ -805,7 +741,7 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
stdlib.free(vpi_path_pv)
# 读取文件、解析 AST、生成 vpi
file_path_pv: str = entry_pv.Path
f_pv: fileio.File | t.CPtr = fileio.File(file_path_pv, fileio.MODE.R)
f_pv: rowptr[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")
@@ -854,7 +790,7 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
if pyi_pos_pv > 0:
pyi_path_pv: str = StubMergerCollect._sliced_path(temp_dir, dir_len_pv, sha1_pv, "vpi")
if pyi_path_pv is not None:
pf_pv: fileio.File | t.CPtr = fileio.File(pyi_path_pv, fileio.MODE.W)
pf_pv: rowptr[fileio.File] = fileio.File(pyi_path_pv, fileio.MODE.W)
if not pf_pv.closed:
pf_pv.write(pyi_buf_pv, pyi_pos_pv)
pf_pv.close()
@@ -864,12 +800,8 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
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) 之前(否则窗口被截断为空,见 memhub.vp
# reset_to L558-559 与 check_alive_blocks L1234-1235)。mark1 在 1b 段前
# 捕获(本文件 L756),是 1b/1b-pre 的统一窗口起点;本循环无文件级 mark,
# 故与既有 L1109/L1135/L1226 同传 mark1。
# [TEMP-DIAG] 1b-pre 每文件哨兵(同上):校验本文件窗口 [mark1, chk_depth) 内所有存活块的块尾 canary。
# 同样必须在 reset_to(mark1) 之前(否则窗口被截断为空)。mark1 在 1b 段前捕获(本文件 L756),是 1b/1b-pre 的统一窗口起点。
# 仅 --detection-deep(mb.deep_detect != 0)时执行,读法同上方 1a 哨兵。
if mb.deep_detect != 0:
chk_rc_pv: int = mb.check_alive_blocks(mark1)
@@ -939,16 +871,12 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
continue
# 检查 stub 是否已存在(按需翻译:跳过已存在的)
# 例外:含 class 定义的模块必须翻译——类注册(register_struct)
# 只在翻译时发生;增量编译时 stub/text 已存在被跳过 → 跨模块类构造
# find_struct_by_module 落空 → 返回类型 fail-fast
# (fltk.Fl_Window(...) → "4049e14d73c7e091.Fl_Window 无法精确获取",
# 仅 --clean 全量翻译时可用)。扫描 1b-pre 生成的 vpi 是否有
# "class " 行(vpi 为解析后存根,精确无注释/字符串误判)。
# 例外:含 class 定义的模块必须翻译——类注册只在翻译时发生,增量编译跳过会导致跨模块类构造 find_struct_by_module 落空 → 返回类型 fail-fast。
# 判据:扫描 1b-pre 生成的 vpi 是否有 "class " 行(vpi 为解析后存根,精确无注释/字符串误判)。
force_translate: int = 0
vpi_path_1b: str = StubMergerCollect._sliced_path(temp_dir, dir_len, sha1, "vpi")
if vpi_path_1b is not None:
vf_1b: fileio.File | t.CPtr = fileio.File(vpi_path_1b, fileio.MODE.R)
vf_1b: rowptr[fileio.File] = fileio.File(vpi_path_1b, fileio.MODE.R)
if not vf_1b.closed:
vbuf_1b: bytes = stdlib.malloc(1048576)
if vbuf_1b is not None:
@@ -982,13 +910,13 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
VLogger.info(f"含 class 定义,强制翻译: {rp}(sha1={sha1})", "Phase1")
else:
# 检查 stub 是否已存在(按需翻译:跳过已存在的)
sf: fileio.File | t.CPtr = fileio.File(stub_path, fileio.MODE.R)
sf: rowptr[fileio.File] = fileio.File(stub_path, fileio.MODE.R)
if not sf.closed:
sf.close()
# 检查 text.ll 是否也存在(虚表扫描需要 text.ll)
text_path: str = StubMergerCollect._sliced_path(temp_dir, dir_len, sha1, "text.ll")
if text_path is not None:
tf: fileio.File | t.CPtr = fileio.File(text_path, fileio.MODE.R)
tf: rowptr[fileio.File] = fileio.File(text_path, fileio.MODE.R)
if not tf.closed:
tf.close()
stdlib.free(text_path)
@@ -1010,7 +938,7 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
# 读取文件内容
file_path: str = entry.Path
f: fileio.File | t.CPtr = fileio.File(file_path, fileio.MODE.R)
f: rowptr[fileio.File] = fileio.File(file_path, fileio.MODE.R)
if f is None:
VLogger.error(f"无法打开(f=None): {file_path}", "Phase1")
failed += 1
@@ -1061,10 +989,8 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
# 计时点 1:读文件 + lexer/tokenize/parse 完成
t_f1: uint64 = w32.win32base.GetTickCount64()
# 文本扫描注册 CTypedef(stdint.vp 崩溃根因修复):AST 字符串字段
# (attr/id)token 零拷贝指向 src_buf,跨阶段内容不稳定(悬空),
# ScanCTypedefDeclarations 读 attr 会 0xC0000005。改用源码文本逐行
# 匹配 "NAME: t.CTypedef = TYPE",在 src_buf 有效期内注册。
# 文本扫描注册 CTypedef:AST 字符串字段(attr/id)token 零拷贝指向 src_buf,跨阶段内容不稳定(悬空),读 attr 会 0xC0000005;
# 故改用源码文本逐行匹配 "NAME: t.CTypedef = TYPE",在 src_buf 有效期内注册。
GenApi.RegisterCTypedefFromSource(mb, src_buf)
# 生成 .vpi 存根文件(直接遍历 AST,不依赖 PythonToStubConverter)
@@ -1072,7 +998,7 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
# (消除 1b-pre 与 1b 对同一文件的重复 _GeneratePyiFromAst 工作)
vpi_path_1b: str = StubMergerCollect._sliced_path(temp_dir, dir_len, sha1, "vpi")
if vpi_path_1b is not None:
vf_1b: fileio.File | t.CPtr = fileio.File(vpi_path_1b, fileio.MODE.R)
vf_1b: rowptr[fileio.File] = fileio.File(vpi_path_1b, fileio.MODE.R)
vpi_exists_1b: int = 0
if not vf_1b.closed:
vf_1b.close()
@@ -1083,7 +1009,7 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
pyi_pos: sizet = StubConverter._GeneratePyiFromAst(
mb, tree, entry.RelPath, pyi_buf, Phase1.PYI_BUF_SIZE)
if pyi_pos > 0:
pf_1b: fileio.File | t.CPtr = fileio.File(vpi_path_1b, fileio.MODE.W)
pf_1b: rowptr[fileio.File] = fileio.File(vpi_path_1b, fileio.MODE.W)
if not pf_1b.closed:
pf_1b.write(pyi_buf, pyi_pos)
pf_1b.close()
@@ -1130,10 +1056,8 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
failed += 1
continue
# 诊断(builtins.vp dump 卡死根因排查):pre_rc 哨兵在 translate 前,
# 覆盖不到 translate 期间的 AST/IR 写穿。dump 前立即检查存活块
# header/canary(rc=6 块尾 canary 损坏=越界写穿数据区),把"dump
# 时链表成环卡死"变成"定位到 translate 期间写穿发生的文件"。
# 诊断:pre_rc 哨兵在 translate 前、覆盖不到 translate 期间的 AST/IR 写穿,故 dump 前立即检查存活块 header/canary
# (rc=6 块尾 canary 损坏 = 越界写穿数据区),把「dump 时链表成环卡死」变成「定位到 translate 期间写穿发生的文件」。
post_rc: int = mb.check_alive_blocks(mark1)
if post_rc != 0:
VLogger.error(f"内存损坏(translate后存活块, rc={post_rc}): {file_path}", "Phase1")
@@ -1158,7 +1082,7 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
dir_len_p1: sizet = string.strlen(temp_dir)
stub_path_p1: str = StubMergerCollect._sliced_path(temp_dir, dir_len_p1, sha1, "stub.ll")
if stub_path_p1 is not None:
sf_p1: fileio.File | t.CPtr = fileio.File(stub_path_p1, fileio.MODE.W)
sf_p1: rowptr[fileio.File] = fileio.File(stub_path_p1, fileio.MODE.W)
if not sf_p1.closed:
sf_p1.write(stub_buf, stub_len)
sf_p1.close()
@@ -1181,7 +1105,7 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
# save text.ll
text_path_p1: str = StubMergerCollect._sliced_path(temp_dir, dir_len_p1, sha1, "text.ll")
if text_path_p1 is not None:
tf_p1: fileio.File | t.CPtr = fileio.File(text_path_p1, fileio.MODE.W)
tf_p1: rowptr[fileio.File] = fileio.File(text_path_p1, fileio.MODE.W)
if not tf_p1.closed:
tf_p1.write(text_buf, text_len)
tf_p1.close()
@@ -1195,7 +1119,7 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
# save dependencies(_imported_modules) for Phase B
deps_path_p1: str = StubMergerCollect._sliced_path(temp_dir, dir_len_p1, sha1, "deps.txt")
if deps_path_p1 is not None:
df_p1: fileio.File | t.CPtr = fileio.File(deps_path_p1, fileio.MODE.W)
df_p1: rowptr[fileio.File] = fileio.File(deps_path_p1, fileio.MODE.W)
if not df_p1.closed:
if tr._imported_modules is not None:
dl: sizet = string.strlen(tr._imported_modules)
@@ -1214,23 +1138,16 @@ def RunPhase1(mb: rowptr[memhub.MemBuddy], includes_dir: str, temp_dir: str,
translated += 1
# OOM 回收:回收本文件翻译产生的瞬时数据(AST/tokens/lexer/IR/Handler/dump 临时)
# 持久表(struct/enum/REnum/spec 缓存/全局函数名等)已迁出 reset 区间(stdlib),
# 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 损坏(越界写穿数据区——事发地定位)
# 内存哨兵(崩溃点漂移排查):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 成环/超步数
chk_rc: int = mb.check_alive_blocks(mark1)
if chk_rc != 0:
VLogger.error(f"内存损坏(存活块 header, rc={chk_rc}): {file_path}", "Phase1")
# A/B 实验结论(已确认):禁用 reset_to 后 stdarg 仍崩 → 越界写是根因,
# 与回收/UAF 无关。恢复 reset_to,改用块尾 canary(check_alive_blocks 校验)
# 定位越界写事发地到具体文件。
# 越界写是崩溃根因(与回收/UAF 无关,已由 A/B 实验确认)——恢复 reset_to,改用块尾 canary(check_alive_blocks 校验)把越界写事发地定位到具体文件。
mb.reset_to(mark1)
sc_rc: int = mb.self_check()
if sc_rc != 0:
+26 -68
View File
@@ -32,10 +32,9 @@ import lib.core.Gen.GenApi as GenApi
# ============================================================
# Phase1Scan - Phase1 拆分(阶段 E):Phase1Scan
# Phase1Scan - 扫描类继承关系、泛型类标记与容器使用
#
# 直接移动模式:函数迁至此模块,原 Phase1.vp 仅保留常量/_mbuddy;
# 调用方经本模块前缀访问。全局符号经 Phase1._xxx 跨模块访问。
# 函数实现直接位于本模块,调用方经本模块前缀访问;全局符号经 Phase1._xxx 跨模块访问。
# ============================================================
# 拓扑排序使用扁平字节数组存储类信息和文件依赖,
@@ -47,23 +46,12 @@ import lib.core.Gen.GenApi as GenApi
# ============================================================
# _ScanClassInheritance - 预扫描 includes 文件,收集类继承关系
#
# 遍历所有可达 includes 文件,解析 AST 收集 ClassDef 的类名和父类名。
# 结果存入扁平字节数组,返回收集到的类数量。
# 遍历所有可达 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 失败
# 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 失败
# ============================================================
def _ScanClassInheritance(mb: rowptr[memhub.MemBuddy],
result: IncludesScanner.ScanResult | t.CPtr,
@@ -78,11 +66,8 @@ def _ScanClassInheritance(mb: rowptr[memhub.MemBuddy],
"""预扫描 includes 文件,收集类继承关系"""
if result is None:
return -1
# class_names_buf/parent_names_buf/def_sha1s_buf 由调用方 mb.alloc 分配
# (Phase1Main 预扫描前),本函数将其地址直接作为 strcpy 的目的缓冲
# (name_dst/parent_dst/sha1_dst)。分配失败返回 NULL 时目的指针为近零地址
# → strcpy 写零页(ASan access-violation)。按 fail-fast 规则显式报错终止
# (VLogger.error 内部 sys.exit(1)),不静默返回 -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。
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")
@@ -90,10 +75,8 @@ def _ScanClassInheritance(mb: rowptr[memhub.MemBuddy],
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 在调用前分配,
# 位于本 mark 之前,reset 不影响;generic_file_indices 同理)
# 文件级 mark:收集完类继承信息后立即回收本文件 AST(可达 includes 全量 parse 的 AST 若驻留到 mark1 会耗尽 1GB 主池 OOM);
# class_names_buf/parent_names_buf/def_sha1s_buf 与 generic_file_indices 在调用前分配、位于本 mark 之前,reset 不影响。
scan_mark: sizet = mb.mark()
entry_addr: uint64 = uint64(result.Entries) + i * entry_size
entry: IncludesScanner.FileEntry | t.CPtr = (IncludesScanner.FileEntry | t.CPtr)(t.CVoid(entry_addr, t.CPtr))
@@ -114,7 +97,7 @@ def _ScanClassInheritance(mb: rowptr[memhub.MemBuddy],
# 读取文件
file_path: str = entry.Path
f: fileio.File | t.CPtr = fileio.File(file_path, fileio.MODE.R)
f: rowptr[fileio.File] = fileio.File(file_path, fileio.MODE.R)
if f.closed:
continue
@@ -220,15 +203,9 @@ def _ScanClassInheritance(mb: rowptr[memhub.MemBuddy],
# ============================================================
# _BufHasGenericClassMarker - 文本扫描检测文件是否含泛型类标记
#
# 泛型类(PEP 695)语法: class Name[T]:(类名后紧跟 '[')。
# 逐字节扫描找词边界 'class' 关键字,其后标识符后非空白是 '[' → 命中。
# 用于 _PreRegisterAllGenericTemplates 预注册前筛选,避免 parse 全部
# 98 个 includes 导致 AST 驻留 mark1 前把 pool 推到 OOM
# (NewAstTest [52/68] pool 耗尽根因:1a-pre 已 parse 全部驻留,
# 预注册再全量 parse 等于翻倍)。
# 误报(字符串/注释里的 "class X[")无害(多 parse 一个文件);
# 漏报有害(泛型模板未预注册 → 悬空复发),模式保守匹配行内单行形式
# (TPV 源码泛型类均为单行 `class X[T]:`,grep 确认)。
# 泛型类(PEP 695)语法: class Name[T]:(类名后紧跟 '[')。逐字节扫描找词边界 'class' 关键字,其后标识符后非空白是 '[' → 命中。
# 用于 _PreRegisterAllGenericTemplates 预注册前筛选,避免 parse 全部 98 个 includes 致 AST 驻留 mark1 前把 pool 推到 OOM。
# 误报(字符串/注释里的 "class X[")无害(多 parse 一个文件);漏报有害(泛型模板未预注册 → 悬空复发),故保守只匹配单行形式。
# ============================================================
def _BufHasGenericClassMarker(buf: bytes, blen: sizet) -> int:
"""文本扫描:buf 中是否有 'class 标识符[' 泛型类标记,返回 1/0"""
@@ -270,18 +247,10 @@ def _BufHasGenericClassMarker(buf: bytes, blen: sizet) -> int:
# ============================================================
# _PreRegisterAllGenericTemplates - 预注册 includes 泛型类模板(mark1 前)
#
# 背景:泛型模板表(GenericClassNames/GenericClassNodes/GenericClassSha1s/
# GenericClassFiles + 模板节点)是懒分配的跨文件持久表,首次注册必须落在
# mark1 之前才能跨 Phase1 1b(reset_to(mark1))与 Phase2(reset_to(mark2),
# mark2 > mark1)存活。现有 1a-pre-generic 只注册"可达"泛型文件;FuncTest
# 这类可达集合无泛型的项目,模板要等 Phase2 Phase A-pre-inc(mark2 之后)
# 翻译全部 includes 时才注册 → reset 回收表与节点 → _find_generic_template
# 遍历悬空内存 strcmp 崩溃。
# 本函数先文本扫描(_BufHasGenericClassMarker,不 parse、不驻留)筛选出
# 含泛型类的文件(全量 parse 会把 98 个 includes 的 AST 驻留 mark1 前 →
# NewAstTest [52/68] pool 耗尽 OOM),仅对命中的文件用**永久池(perm_pool)**
# 解析 → 模板节点与表直接进 perm_pool,跨 mark 永久存活、不占主 pool。
# 重复注册由 _register_generic_template 内部查重跳过。
# 泛型模板表(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 内部查重跳过。
# ============================================================
def _PreRegisterAllGenericTemplates(mb: rowptr[memhub.MemBuddy],
result: IncludesScanner.ScanResult | t.CPtr) -> int:
@@ -301,7 +270,7 @@ def _PreRegisterAllGenericTemplates(mb: rowptr[memhub.MemBuddy],
continue
# 读取文件
file_path_pg: str = entry_pg.Path
f_pg: fileio.File | t.CPtr = fileio.File(file_path_pg, fileio.MODE.R)
f_pg: rowptr[fileio.File] = fileio.File(file_path_pg, fileio.MODE.R)
if f_pg.closed:
continue
# read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被静默截断
@@ -361,14 +330,8 @@ def _PreRegisterAllGenericTemplates(mb: rowptr[memhub.MemBuddy],
# ============================================================
# _FindClassDefSha1 - 在扁平数组中查找类名对应的定义文件 SHA1
#
# Args:
# class_names_buf: 类名扁平数组
# def_sha1s_buf: 定义 SHA1 扁平数组
# class_count: 类数量
# class_name: 要查找的类名
#
# Returns:
# SHA1 字符串指针(指向 def_sha1s_buf 内部偏移),None 未找到
# Args: class_names_buf 类名扁平数组;def_sha1s_buf 定义 SHA1 扁平数组;class_count 类数量;class_name 要查找的类名
# Returns: SHA1 字符串指针(指向 def_sha1s_buf 内部偏移),None 未找到
# ============================================================
def _FindClassDefSha1(class_names_buf: bytes, def_sha1s_buf: bytes,
class_count: int, class_name: str) -> str:
@@ -383,11 +346,8 @@ def _FindClassDefSha1(class_names_buf: bytes, def_sha1s_buf: bytes,
# ============================================================
# _ScanTreeForContainers - 递归扫描 AST 检测 list/dict 容器使用
#
# TPV 的 AST 中,语句块挂在 children,而表达式/注解树全部以结构体
# 字段挂载(value/slice/func/args/annotation/targets/returns/bases/
# left/right 等)。只递归 children 会漏掉 list[...] | t.CPtr(BinOp
# 包裹)、list[...](...)(Call.func)、字段注解等一切非语句块位置。
# 因此每个节点先递归其表达式承载字段,再递归 children 语句块。
# TPV 的 AST 中语句块挂在 children,而表达式/注解树全部以结构体字段挂载(value/slice/func/args/annotation/targets/returns/bases/left/right 等);
# 只递归 children 会漏掉 list[...] | t.CPtr、list[...](...)、字段注解等非语句块位置,故每个节点先递归表达式承载字段、再递归 children。
# 命中即置 need_list/need_dict;误报无害(最多多编译两个空壳模块)。
# ============================================================
@@ -530,10 +490,8 @@ def _ScanTreeForContainers(node: ast.AST | t.CPtr,
i_v2: sizet
for i_v2 in range(nv_s):
_ScanTreeForContainers(bvs_s.get(i_v2), need_list_box, need_dict_box, depth + 1)
# [FIX 2026-08-30] `[...]`/`{}` 字面量不再无条件触发 _list/_dict 导入:
# 字面量广泛用于编译期常量列表(c.Asm 的 op/inp/out CLOBBER 参数、
# 数组数据初始化),并非 list 容器使用。list/dict 容器的真实使用由
# 类型位置检测覆盖(Name/Subscript 注解、Call.func 构造名)。
# `[...]`/`{}` 字面量不触发 _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:
+16 -45
View File
@@ -28,42 +28,22 @@ import lib.Phase.Phase1.Phase1Topo as Phase1Topo
import lib.Phase.Phase1 as Phase1
# ============================================================
# Phase1Topo - Phase1 拆分(阶段 E):Phase1Topo
# Phase1Topo - 按类继承与 import 依赖对源文件做拓扑排序
#
# 直接移动模式:函数迁至此模块,原 Phase1.vp 仅保留常量/_mbuddy;
# 调用方经本模块前缀访问。全局符号经 Phase1._xxx 跨模块访问。
# 函数实现直接位于本模块,调用方经本模块前缀访问;全局符号经 Phase1._xxx 跨模块访问。
# ============================================================
# ============================================================
# _TopoSortFiles - 根据类继承关系对文件进行拓扑排序
# _TopoSortFiles - 根据类继承关系对文件进行拓扑排序(Kahn)
#
# 算法(Kahn):
# 1. 从扁平类信息数组构建"文件 SHA1 → 依赖文件 SHA1 集合"映射
# 2. 计算每个文件的入度(依赖数量)
# 3. 入度为 0 的文件先处理,处理后减少依赖它的文件入度
# 4. 重复直到所有文件处理完成
# 步骤:1) 从扁平类信息数组构建「文件 SHA1 → 依赖文件 SHA1 集合」映射;2) 计算每个文件入度(DepCount,初始化 InDegree = DepCount);
# 3) 入度为 0 的先处理,处理后减少依赖它的文件入度;4) 重复直到全部处理完成。
# 依赖方向是「父类所在文件 → 子类所在文件」,即父类文件必须先处理。
#
# 注意:依赖方向是"父类所在文件 → 子类所在文件",
# 即父类文件必须先处理。Kahn 用 DepCount 作为剩余依赖计数,
# 依赖为 0 的先处理,初始化 InDegree = DepCount。
#
# 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 失败
# 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 失败
# ============================================================
def _TopoSortFiles(mb: rowptr[memhub.MemBuddy],
result: IncludesScanner.ScanResult | t.CPtr,
@@ -184,15 +164,9 @@ def _TopoSortFiles(mb: rowptr[memhub.MemBuddy],
# 2.5 补充 import 依赖边:被 import 的模块先完整翻译。
#
# 仅按类继承建边不够:跨模块全局变量 stub(lexer.vp from-import
# tokens.vp 的 _op_head)、跨模块 vtable TypeStr(astaux 继承 base 的
# AST)、泛型模板(base.vp 用 list[...])都要求依赖模块先翻译,而它们
# 之间可能无类继承关系。deps(_includes_deps_*)由 Phase 1a-pre 填充
# (含容器按需注入的 _list/_dict)。
#
# 用 _BuildIncludesSha1Map 复制局部 sha1→模块名 数组(与
# _BuildReachableSha1Set 的 BFS 一致),避免直接使用全局 store 返回值
# 跨函数传参(bytes|t.CPtr 联合转 str 参数)的地址异常。
# 仅按类继承建边不够:跨模块全局变量 stub、跨模块 vtable TypeStr、泛型模板都要求依赖模块先翻译,而它们之间可能无类继承关系。
# deps(_includes_deps_*)由 Phase 1a-pre 填充(含容器按需注入的 _list/_dict);用 _BuildIncludesSha1Map 复制局部 sha1→模块名 数组
# (与 _BuildReachableSha1Set 的 BFS 一致),避免直接跨函数传全局 store 返回值导致地址异常。
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
@@ -325,13 +299,10 @@ def _TopoSortFiles(mb: rowptr[memhub.MemBuddy],
ind_ptr5[0] = ind_ptr5[0] - 1
break
# 处理循环依赖:环内优先输出"被依赖者"(被其余剩余文件依赖数量多者先)。
# 处理循环依赖:环内优先输出被依赖者(被其余剩余文件依赖数量多者先)。
#
# 背景:容器按需注入(string 用 list 泛型 → 依赖 _list)与真实 import
# (_list import string)形成伪环,Kahn 无法完全归零。若按扫描顺序追加,
# 依赖者(astaux)会先于被依赖者(base)翻译 → 跨模块 vtable TypeStr
# 未注册 → 误杀。被依赖者先翻译,其 stub/TypeStr/模板先就绪,依赖者
# 翻译时可引用(函数/全局声明由 1a-pre 的 vpi 兜底)。
# 容器按需注入(string 用 list 泛型 → 依赖 _list)与真实 import 会形成伪环,Kahn 无法完全归零;若按扫描顺序追加,
# 依赖者会先于被依赖者翻译 → 跨模块 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)
+58 -126
View File
@@ -35,9 +35,7 @@ _mbuddy: rowptr[memhub.MemBuddy]
# 源代码缓冲区大小(1MB)
SRC_BUF_SIZE: t.CDefine = 1048576
# 最大源文件数(递归扫描子目录后文件数增多:App 现有 65 个 .vp,
# 2026-08-14 CompilerFactory.vp 加入后超出 64 被截断 → 链接 undefined symbol
# CompilerFactory.factory.PermPool。增大到 128 留余量)
# 最大源文件数(递归扫描子目录后文件数增多;过小会截断文件列表 → 链接 undefined symbol,故留余量)
MAX_SRC_FILES: t.CDefine = 128
@t.NoVTable
@@ -47,22 +45,10 @@ class SrcFileEntry:
Sha1: str # SHA1 前16字符
# ============================================================
# _ScanDirForPyFiles - 递归扫描目录,收集 .vp/.py 文件到 entries
# _ScanDirForPyFiles - 递归扫描目录,收集 .vp/.py 文件到 entries(遇目录递归,遇文件读取内容、计算 SHA1、加入 entries)
#
# 替代旧的非递归 FindFirstFileA(dir/*.py) 扫描,支持子目录
# (如 App/lib/core/Handles/)。遇到目录递归扫描,遇到 .vp/.py
# 文件读取内容、计算 SHA1、加入 entries。
#
# Args:
# mb: 内存池
# dir_path: 当前扫描目录
# entries: SrcFileEntry 数组
# entry_size: 单个条目大小(SrcFileEntry.__sizeof__())
# file_count: 当前已收集的文件数
# max_files: 最大文件数
#
# Returns:
# 新的 file_count
# Args: mb 内存池;dir_path 当前扫描目录;entries SrcFileEntry 数组;entry_size 单个条目大小(SrcFileEntry.__sizeof__());file_count 当前已收集的文件数;max_files 最大文件数
# Returns: 新的 file_count
# ============================================================
def _ScanDirForPyFiles(mb: rowptr[memhub.MemBuddy],
dir_path: str,
@@ -139,10 +125,8 @@ def _ScanDirForPyFiles(mb: rowptr[memhub.MemBuddy],
if w32.win32file.FindNextFileA(handle, find_data) == 0:
break
continue
# 统一用 IncludesScanner.compute_file_sha1(CRLF→LF 归一化),
# 与 StubMerger.vp:1510 对同一批 App 源文件的口径一致;
# 此前用 Utils.compute_sha1(原始字节)会导致 stub/define
# 符号前缀不一致 → 链接报 undefined reference。
# 统一用 IncludesScanner.compute_file_sha1(CRLF→LF 归一化),与 StubMerger 对同一批 App 源文件的口径一致;
# 用原始字节口径会导致 stub/define 符号前缀不一致 → 链接 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
@@ -268,11 +252,9 @@ def _ScanDirForAsmFiles(mb: rowptr[memhub.MemBuddy],
# ============================================================
# _PreRegisterAppGenericTemplates - mark2 前预注册 App 源文件泛型类模板
#
# 与 Phase1 的 includes 全量预注册同理:泛型模板表(GenericClassNames 等
# 4 张 + 模板节点)是 pool 懒分配的跨文件持久表,若在 mark2 之后
# (Phase A-pre 翻译 App 文件时)才首次注册,reset_to(mark2) 会回收表与
# 节点 → 后续查重/特化遍历悬空内存崩溃。App 文件数量少,mark2 前预注册
# 产生的 AST 驻留内存可忽略。重复注册由 _register_generic_template 查重跳过。
# 泛型模板表(GenericClassNames 等 4 张 + 模板节点)是 pool 懒分配的跨文件持久表,若在 mark2 之后(Phase A-pre 翻译 App 文件时)
# 才首次注册,reset_to(mark2) 会回收表与节点 → 后续查重/特化遍历悬空内存崩溃;App 文件数量少,mark2 前预注册的 AST 驻留内存可忽略。
# 重复注册由 _register_generic_template 查重跳过。
# ============================================================
def _PreRegisterAppGenericTemplates(mb: rowptr[memhub.MemBuddy],
entries: SrcFileEntry | t.CPtr,
@@ -288,7 +270,7 @@ def _PreRegisterAppGenericTemplates(mb: rowptr[memhub.MemBuddy],
if ent_pa is None or ent_pa.Path is None or ent_pa.Sha1 is None:
continue
# 读取文件
f_pa: fileio.File | t.CPtr = fileio.File(ent_pa.Path, fileio.MODE.R)
f_pa: rowptr[fileio.File] = fileio.File(ent_pa.Path, fileio.MODE.R)
if f_pa.closed:
continue
# read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被静默截断
@@ -347,11 +329,9 @@ def _PreRegisterAppGenericTemplates(mb: rowptr[memhub.MemBuddy],
# ============================================================
# Phase A 收敛失败依赖图诊断
#
# 多遍收敛失败时,原 ERROR 只报 "无法完成翻译: <路径>",对"直接依赖均已
# 生成但缺失在传递依赖/MRO/vpi 侧"(如 gfx.vp 经 list 模板 → _list → memhub)
# 无从定位。这里在翻译过程中把每个待译文件的依赖模块名与缺失明细持久化到
# stdlib 全局表(跨 mb.reset_to(mark2) 存活),收敛失败时按文件输出依赖树,
# 用 [MISS]/[OK] 标出缺 text.ll 的节点,并回显 pending 明细。
# 多遍收敛失败时仅报 "无法完成翻译: <路径>" 无法定位「直接依赖均已生成但缺失在传递依赖/MRO/vpi 侧」。
# 故翻译过程中把每个待译文件的依赖模块名与缺失明细持久化到 stdlib 全局表(跨 mb.reset_to(mark2) 存活),
# 收敛失败时按文件输出依赖树,用 [MISS]/[OK] 标出缺 text.ll 的节点,并回显 pending 明细。
# ============================================================
PA_DEPS_MAX_STR: sizet = 1024
PA_MISS_MAX_STR: sizet = 768
@@ -470,7 +450,7 @@ def _PhaseATextLLExists(temp_dir: str, td_len: sizet, sha1: str) -> int:
tpath: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1, "text.ll")
if tpath is None:
return 0
tf: fileio.File | t.CPtr = fileio.File(tpath, fileio.MODE.R)
tf: rowptr[fileio.File] = fileio.File(tpath, fileio.MODE.R)
exists: int = 0
if not tf.closed:
tf.close()
@@ -499,16 +479,14 @@ def _PhaseADumpNode(temp_dir: str, td_len: sizet, sha1: str,
# 回退:读取 {temp_dir}/{前缀}/{sha1}.deps.txt(Phase1 已为 includes 写盘)
dpath: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1, "deps.txt")
if dpath is not None:
df: fileio.File | t.CPtr = fileio.File(dpath, fileio.MODE.R)
df: rowptr[fileio.File] = fileio.File(dpath, fileio.MODE.R)
if not df.closed:
deps = stdlib.malloc(PA_DEPS_MAX_STR)
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, 在界内直接写
# 终止符即可(原来传 MAX-1 白扔一个字节, 且还有个死分支)
# read_all 的容量参数即缓冲区容量(含终止符):传真实容量 MAX 时最多读 MAX-1 字节,故 rl 恒 <= MAX-1,在界内直接写终止符即可。
deps[rl] = 0
df.close()
stdlib.free(dpath)
@@ -679,20 +657,13 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
# 初始化 AST 表
ast._init_tables(mb)
# mark2 之前预注册 App 源文件的泛型类模板:泛型模板表/节点是 pool 懒分配,
# 必须在 mark2 之前注册才能跨 reset_to(mark2) 存活(与 Phase1 的 includes
# 全量预注册同理),否则 Phase A-pre 首次注册后 reset 回收 → 后续查重/特化
# 遍历悬空内存崩溃(1.log FuncTest ASAN access-violation 同类根因)。
# mark2 之前预注册 App 源文件的泛型类模板:模板表/节点是 pool 懒分配,必须在 mark2 之前注册才能跨 reset_to(mark2) 存活,
# 否则 Phase A-pre 首次注册后即被 reset 回收 → 后续查重/特化遍历悬空内存崩溃。
_PreRegisterAppGenericTemplates(mb, entries, file_count, entry_size)
# OOM 清理(对齐 Phase1 1b 的 mark1/reset_to 模式):
# Phase2 的 includes 预注册(Phase A-pre-inc)、App 源文件预注册(Phase A-pre)、
# App 全量翻译(Phase A)、缺失 includes 翻译(Phase B+)都会产生大量瞬时数据
# (AST/tokens/lexer/IR/Handler/dump 临时)。无回收时 98 个 includes 的 AST/IR
# 全量累积 pool → 1GB OOM(NewAstTest 依赖 ast 模块场景)。
# 此处打 mark2,各处翻译完成后 reset_to(mark2) 回收该文件瞬时数据;
# 跨文件持久表(struct/enum/docstring/CDefine/函数注册表等)已 stdlib 化
# 或落在 mark2 之前,reset 不受影响(与 Phase1 1b 同翻译路径,已验证安全)。
# 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 不受影响。
mark2: sizet = mb.mark()
# mark2 之后新注册的 struct/enum 条目改 stdlib 分配(跨 reset 存活,对齐 Phase1)
GenApi.set_stdlib_alloc(1)
@@ -728,7 +699,7 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
map_path_am: bytes = stdlib.malloc(td_len_am + 32)
if map_path_am is not None:
viperlib.snprintf(map_path_am, td_len_am + 32, "%s/_sha1_map.txt", temp_dir)
mf: fileio.File | t.CPtr = fileio.File(map_path_am, fileio.MODE.A)
mf: rowptr[fileio.File] = fileio.File(map_path_am, fileio.MODE.A)
line_am: bytes = stdlib.malloc(512)
# 先遍历一次填充内存存储器(不依赖文件 I/O),再写入 _sha1_map.txt
app_filled: int = 0
@@ -768,15 +739,9 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
# === Phase A-pre-vpi: 预生成所有 App 源文件的 .vpi stub ===
#
# 与 Phase1 1b-pre 同理:依赖模块的 .vpi 必须在依赖方翻译前可用。
# Phase A 主循环按扫描顺序翻译(非拓扑),若 StubMergerCollect/IR/Store
# 先于 __init__.vp 翻译,__init__.vpi 尚未生成 → LoadDepVpiIntoModule
# 找不到 vpi → 跨模块全局变量 declare 未注入 → ExprGenAttr fallback
# 以 i8* 创建带前缀 global(@b0b568._sha1_store_count = external global i8*)
# → llc "icmp slt 类型不匹配"(22fc/2e23/daad 根因,见 vpi 时间戳顺序)。
# 此处先为所有 App 源文件预生成 vpi,保证 Phase A 翻译任意模块时其依赖
# vpi 已存在(App 源文件自身的 vpi 随后由 TranslateFileGetTrans 覆盖重写,
# 内容一致,此处仅保证"先于依赖方")。
# 依赖模块的 .vpi 必须在依赖方翻译前可用:Phase A 主循环按扫描顺序(非拓扑)翻译,若依赖模块后翻译则 LoadDepVpiIntoModule 找不到 vpi
# → 跨模块全局变量 declare 未注入 → ExprGenAttr fallback 以 i8* 创建带前缀 global → llc "icmp slt 类型不匹配"。
# 此处先为所有 App 源文件预生成 vpi,保证 Phase A 翻译任意模块时其依赖 vpi 已存在(App 源文件自身 vpi 随后由 TranslateFileGetTrans 覆盖重写,内容一致)。
td_len_vpa: sizet = string.strlen(temp_dir)
PYI_BUF_SIZE_P2: sizet = 262144
vpi_failed_p2: int = 0
@@ -793,7 +758,7 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
vpi_failed_p2 += 1
VLogger.error(f"[Phase A-pre-vpi] {ent_vp2.Path} | vpi 路径生成失败", "PhaseA")
continue
vf_vp2: fileio.File | t.CPtr = fileio.File(vpi_path_vp2, fileio.MODE.R)
vf_vp2: rowptr[fileio.File] = fileio.File(vpi_path_vp2, fileio.MODE.R)
if not vf_vp2.closed:
vf_vp2.close()
stdlib.free(vpi_path_vp2)
@@ -801,7 +766,7 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
stdlib.free(vpi_path_vp2)
# 读取源文件 → 解析 AST → 生成 vpi
# (rel_path 传 None:与 TranslateFileGetTrans 后续覆盖写的内容一致)
f_vp2: fileio.File | t.CPtr = fileio.File(ent_vp2.Path, fileio.MODE.R)
f_vp2: rowptr[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")
@@ -851,7 +816,7 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
if pyi_pos_vp2 > 0:
pyi_path_vp2: str = StubMergerCollect._sliced_path(temp_dir, td_len_vpa, ent_vp2.Sha1, "vpi")
if pyi_path_vp2 is not None:
pf_vp2: fileio.File | t.CPtr = fileio.File(pyi_path_vp2, fileio.MODE.W)
pf_vp2: rowptr[fileio.File] = fileio.File(pyi_path_vp2, fileio.MODE.W)
if not pf_vp2.closed:
pf_vp2.write(pyi_buf_vp2, pyi_pos_vp2)
pf_vp2.close()
@@ -869,11 +834,9 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
# === 2. Phase A: 为每个文件生成 stub + text ===
# app_deps_buf: 收集所有 App 源文件的直接依赖模块名(空格分隔)
# 供 Phase B+ 的 _BuildReachableSha1Set 作为初始 worklist,实现按需编译
# 声明在 if 块外,确保 Phase B+ 和清理代码能访问
# 65536 与 StubMerger.REACH_WORKLIST_BUF 对齐:全量 App imports 拼接
# 超 8192(追加82 同源溢出),8192 会静默丢依赖致 Phase B+ 可达集合不全
# app_deps_buf: 收集所有 App 源文件的直接依赖模块名(空格分隔),供 Phase B+ 的 _BuildReachableSha1Set 作为初始 worklist;
# 声明在 if 块外确保 Phase B+ 和清理代码能访问。容量 65536 与 StubMerger.REACH_WORKLIST_BUF 对齐:全量 App imports 拼接超 8192,
# 过小会静默丢依赖致 Phase B+ 可达集合不全。
APP_DEPS_BUF_SIZE: sizet = 65536
app_deps_buf: bytes = None
app_deps_len: sizet = 0
@@ -883,11 +846,9 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
_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+ 的 _BuildReachableSha1Set 使用。
# 多遍扫描:解决跨文件继承问题(如 Constant(AST) 在 AST 未注册时被跳过)。
# 必须在 App 源文件预注册之前执行:App 源文件(strict_mode=1)可能继承或引用 includes 中的 struct(如 ast.AST),
# includes struct 未注册会使 App 翻译失败 → .obj 缺失 → undefined reference。同时填充 PopulateIncludesDeps 供 Phase B+ 使用。
# 多遍扫描解决跨文件继承问题(如 Constant(AST) 在 AST 未注册时被跳过)。
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:
@@ -896,18 +857,9 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
store_incdir_inc_pre: bytes = StubMergerStore.GetSha1StoreIncdirArrPtr()
deps_filled_pre: int = 0
PHASE_A_INC_MAX_PASSES: int = 3
# [修复] 记录预注册失败的 includes:多遍后仍失败 → 报错终止编译。
# 不允许静默降级——静默失败会让 App 源文件的跨模块类构造退化为
# 普通函数调用(返回类型默认推断成 i32),调用点报误导性的
# "跨模块调用 'parser.add_argument' 找不到模块 SHA1"
# (如 argparse.ArgumentParser 类未注册导致 parser 变量类型崩坏)。
# declare_only=1 时 import 的 SHA1 检查跳过(容忍依赖未注册),
# 因此预注册失败只可能是文件自身无法翻译(语法/解析/编译错误)。
# 注意:Sha1Store 因 AppendToSha1MapStore 追加了 App 源文件 SHA1
# (供跨模块 CDefine 查找),此处必须跳过 App 文件——App 文件的
# 预注册由 Phase A-pre(遍历 entries,declare_only=1)负责。
# App 文件是严格模式(仅本地+import 可见)且预注册阶段依赖未就绪,
# 若在此预注册必然失败,误报"预注册失败: main.vp"。
# 记录预注册失败的 includes:多遍后仍失败 → 报错终止编译,不允许静默降级(静默失败会让 App 的跨模块类构造退化为普通函数调用,
# 返回类型默认推断成 i32,调用点报误导性的「找不到模块 SHA1」)。declare_only=1 时 import 的 SHA1 检查跳过,故预注册失败只可能是文件自身无法翻译。
# 必须跳过 App 文件:Sha1Store 里含 App 源文件 SHA1,但其预注册由 Phase A-pre 负责(App 是严格模式且此阶段依赖未就绪,在此预注册必然失败误报)。
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)
@@ -974,10 +926,8 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
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),不翻译方法体
# 多遍扫描:解决跨文件继承的字母序问题(如 HandlesAnnAssign.vp/.py 在 HandlesBase.vp/.py 之前,
# AnnAssignHandle 继承 Mixin 时 Mixin 未注册)。每遍注册新类后,下一遍子类可继承。
# 解决循环引用问题(circ_a 翻译时需知道 circ_b.ClassB 的 struct 定义):仅注册 struct/enum/union(declare_only=1),不翻译方法体;
# 多遍扫描解决跨文件继承的字母序问题(如 AnnAssignHandle 继承 Mixin 时 Mixin 未注册),每遍注册新类后下一遍子类可继承。
VLogger.info("预注册 struct/enum/union...", "PhaseA")
src_dir_len_pre: sizet = string.strlen(source_dir)
PHASE_A_PRE_MAX_PASSES: int = 3
@@ -1062,10 +1012,8 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
# 收集 _imported_modules 到 app_deps_buf(供 Phase B+ 按需编译)。
# 依赖在翻译早期已填充。
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 溢出"。
# 逐模块去重拼接:多个 App 文件 import 同一模块(builtins/_list/_dict/stdlib/t/c 等公共依赖)会让 app_deps 无去重膨胀到接近 64KB,
# _BuildReachableSha1Set 的初始 worklist 即满 → BFS 展开必报「依赖 worklist 溢出」。
im_len_a: sizet = string.strlen(tr_a._imported_modules)
im_pos_a: sizet = 0
while im_pos_a < im_len_a:
@@ -1104,7 +1052,7 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
# save stub.ll (切片路径: temp_dir/{sha1前缀}/{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: fileio.File | t.CPtr = fileio.File(stub_path_a, fileio.MODE.W)
sf_a: rowptr[fileio.File] = fileio.File(stub_path_a, fileio.MODE.W)
if not sf_a.closed:
sf_a.write(stub_buf_a, stub_len_a)
sf_a.close()
@@ -1121,7 +1069,7 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
# save text.ll (切片路径)
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: fileio.File | t.CPtr = fileio.File(text_path_a, fileio.MODE.W)
tf_a: rowptr[fileio.File] = fileio.File(text_path_a, fileio.MODE.W)
if not tf_a.closed:
tf_a.write(text_buf_a, text_len_a)
tf_a.close()
@@ -1131,7 +1079,7 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
# save dependencies (_imported_modules) for Phase B (切片路径)
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: fileio.File | t.CPtr = fileio.File(deps_path_a, fileio.MODE.W)
df_a: rowptr[fileio.File] = fileio.File(deps_path_a, fileio.MODE.W)
if not df_a.closed:
if tr_a._imported_modules is not None:
dl_a: sizet = string.strlen(tr_a._imported_modules)
@@ -1157,11 +1105,8 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
if all_done != 0:
break
if completed_this_pass == 0:
# 无进展:剩余文件无法完成(真实编译错误),终止并列出未完成文件。
# 每个失败文件附带依赖图诊断(_PhaseADumpFailGraph):pending 明细 +
# 依赖树,[MISS] 标出缺 text.ll 的节点,缩小到具体缺失链。
# 注意:VLogger.error 内部调用 sys.exit(1),必须放在所有图形输出
# 之后——此前"ERROR 行后调图"导致进程在 ERROR 处即退出,图永远打不出。
# 无进展:剩余文件无法完成(真实编译错误),终止并列出未完成文件。每个失败文件附带依赖图诊断(_PhaseADumpFailGraph):pending 明细 + 依赖树。
# VLogger.error 内部 sys.exit(1),必须放在所有图形输出之后(否则进程在 ERROR 处即退出,图永远打不出)。
td_len_fail: sizet = string.strlen(temp_dir)
for i_err in range(file_count):
if done_flags[i_err] == 0:
@@ -1389,9 +1334,7 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
stdlib.free(app_deps_buf)
app_deps_buf = None
# 从全局存储器获取 SHA1/模块名 数组:复制局部副本(bytes),
# 避免 store 联合返回值(bytes|t.CPtr)跨函数传参/索引的地址异常
# (与 _BuildReachableSha1Set 的 BFS 一致)。
# 从全局存储器获取 SHA1/模块名 数组:复制局部副本(bytes),避免 store 联合返回值(bytes|t.CPtr)跨函数传参/索引的地址异常。
# rel/incdir 仅本函数内按索引读取(不跨函数传参),保留 store 指针。
td_len_pb: sizet = string.strlen(temp_dir)
store_sha1_arr_mi: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES * 17)
@@ -1433,12 +1376,8 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
# 获取当前条目的 SHA1 和 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 扫描(includes
# 目录含 source_dir 上层,如 ViperOS 含 Apps/),且被其他 App
# import(Scene3D 的 gfx.vp)→ 在可达集合中。若 Phase B+ 再编译
# 一份 .obj 到 includes_binary,与 Phase A/B 编译到 output/ 的
# .obj 同 sha1 同符号 → 链接 duplicate symbol
# (9aab6ff7475c1bbf.Vec3.__init__ 等,Scene3D 根因)。
# 跳过 App 源文件:App 文件可能同时被 includes 扫描且在可达集合中,若 Phase B+ 再编译一份 .obj 到 includes_binary,
# 会与 Phase A/B 编译到 output/ 的 .obj 同 sha1 同符号 → 链接 duplicate symbol。
app_skip_mi: int = 0
for ai_mi in range(file_count):
ea_ap: uint64 = uint64(entries) + ai_mi * entry_size
@@ -1490,7 +1429,7 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
is_decl_mi: int = -1
tpath_mi: str = StubMergerCollect._sliced_path(temp_dir, td_len_mi, inc_sha1_mi, "text.ll")
if tpath_mi is not None:
tf_mi: fileio.File | t.CPtr = fileio.File(tpath_mi, fileio.MODE.R)
tf_mi: rowptr[fileio.File] = fileio.File(tpath_mi, fileio.MODE.R)
if not tf_mi.closed:
is_decl_mi = 1
tbuf_mi: bytes = stdlib.malloc(StubMerger.STUB_READ_BUF_SIZE)
@@ -1556,7 +1495,7 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
inc_stub_len: sizet = string.strlen(inc_stub_buf)
inc_stub_path: str = StubMergerCollect._sliced_path(temp_dir, td_len_mi, inc_sha1_mi, "stub.ll")
if inc_stub_path is not None:
isf: fileio.File | t.CPtr = fileio.File(inc_stub_path, fileio.MODE.W)
isf: rowptr[fileio.File] = fileio.File(inc_stub_path, fileio.MODE.W)
if not isf.closed:
isf.write(inc_stub_buf, inc_stub_len)
isf.close()
@@ -1569,7 +1508,7 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
inc_text_len: sizet = string.strlen(inc_text_buf)
inc_text_path: str = StubMergerCollect._sliced_path(temp_dir, td_len_mi, inc_sha1_mi, "text.ll")
if inc_text_path is not None:
itf: fileio.File | t.CPtr = fileio.File(inc_text_path, fileio.MODE.W)
itf: rowptr[fileio.File] = fileio.File(inc_text_path, fileio.MODE.W)
if not itf.closed:
itf.write(inc_text_buf, inc_text_len)
itf.close()
@@ -1588,13 +1527,13 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
diag_stub_ex: int = 0
diag_text_ex: int = 0
if diag_stub is not None:
df_diag_s: fileio.File | t.CPtr = fileio.File(diag_stub, fileio.MODE.R)
df_diag_s: rowptr[fileio.File] = fileio.File(diag_stub, fileio.MODE.R)
if not df_diag_s.closed:
diag_stub_ex = 1
df_diag_s.close()
stdlib.free(diag_stub)
if diag_text is not None:
df_diag_t: fileio.File | t.CPtr = fileio.File(diag_text, fileio.MODE.R)
df_diag_t: rowptr[fileio.File] = fileio.File(diag_text, fileio.MODE.R)
if not df_diag_t.closed:
diag_text_ex = 1
df_diag_t.close()
@@ -1689,12 +1628,8 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
fop_pos = mlen
# 合并 Phase B/B+ 编译的 App 非 main 模块 .obj(obj_paths,空格分隔)。
# 此前 final_obj_paths 只含 main_obj + reachable includes,App 依赖文件
# (main import 的 syslibtest_impl/os_backend 等)的 .obj 从未入链——追加28
# 前靠 reachable 误含 App 时从 includes.binary 的重复 obj 兜底(undefined
# symbol: ae2038ced7b4634f.run 根因)。obj_paths 同时含 Phase B+ 本次
# 新编译的 includes obj(与下方 reachable 收集重复,--allow-multiple-
# definition 容忍,仅本次编译的少量文件)。
# final_obj_paths 需含 main_obj + reachable includes + App 依赖文件的 .obj,否则 App import 的模块不入链 → undefined symbol。
# obj_paths 同时含 Phase B+ 本次新编译的 includes obj(与下方 reachable 收集重复,--allow-multiple-definition 容忍,仅本次编译的少量文件)。
if obj_pos > 0:
if fop_pos + obj_pos + 1 < OBJ_PATHS_SIZE + 512:
if fop_pos > 0:
@@ -1714,10 +1649,7 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
rsha1_pc: str = reachable_set + sizet(ri_pc) * 17
if rsha1_pc[0] == '\0':
continue
# 跳过 App 源文件:其 .obj 已在 output/(Phase A/B 编译,obj_paths
# 已入 final_obj_paths),若 includes_binary 残留同 sha1 的 .obj
# (上一轮/缓存),收集进来会与 output obj 同符号 → 链接
# duplicate symbol(Scene3D gfx 9aab 根因)。
# 跳过 App 源文件:其 .obj 已入 final_obj_paths,若 includes_binary 残留同 sha1 的 .obj 被收集进来会与 output obj 同符号 → 链接 duplicate symbol。
app_skip_pc: int = 0
for ai_pc in range(file_count):
ea_pc: uint64 = uint64(entries) + ai_pc * entry_size
@@ -1731,7 +1663,7 @@ def RunMultiFileProject(mb: rowptr[memhub.MemBuddy],
obj_path_pc: str = StubMergerCollect._sliced_path(includes_binary_dir, ibd_len_pc, rsha1_pc, "obj")
if obj_path_pc is not None:
# 检查 .obj 文件是否存在
cf_pc: fileio.File | t.CPtr = fileio.File(obj_path_pc, fileio.MODE.R)
cf_pc: rowptr[fileio.File] = fileio.File(obj_path_pc, fileio.MODE.R)
if not cf_pc.closed:
cf_pc.close()
# .obj 存在,追加到 final_obj_paths