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
+10 -39
View File
@@ -18,10 +18,7 @@ import w32.fileio as fileio
# 全局 mbuddy 指针
_mbuddy: memhub.MemManager | t.CPtr
# 配置值(从 project.vpj 加载)
# 默认 None/0:未加载 project.vpj 时字段为 null,调用方 is not None
# 检查才能正确兜底(否则垃圾指针通过检查 → 用非法路径 → 崩溃,
# 尤其非工作区执行无配置时)。
# 配置值(从 project.vpj 加载);默认 None/0:未加载时字段为 null,调用方须 is not None 检查兜底(否则垃圾指针 → 崩溃)
SourceDir: str = None
BuildDir: str = None
TempDir: str = None # = {BuildDir}/temp(自动计算)
@@ -43,9 +40,7 @@ StrictMode: int = 0
IncludesDir: str = None # includes 目录路径(从 includes 数组首元素,兼容单目录场景)
IncludesDirs: str = None # 全部 includes 目录(空格分隔,多 includes 目录支持)
# includes 预编译缓存目录后缀(asan 模式用 .binary_asan 隔离,避免
# asan 插桩 obj 与非 asan obj 混链:asan obj 引用 __asan_init 等符号,
# 非 asan 链接不提供 asan 库 → undefined reference)
# includes 预编译缓存目录后缀(asan 模式用 .binary_asan 隔离 asan 插桩 obj,避免与非 asan obj 混链)
_inc_bin_suffix: str = ".binary"
@@ -67,7 +62,7 @@ def Load_project_config(path: str) -> int:
if _mbuddy is None: return 1
# 打开文件
f: fileio.File | t.CPtr = fileio.File(path, fileio.MODE.R)
f: rowptr[fileio.File] = fileio.File(path, fileio.MODE.R)
if f is None: return 1
if f.closed: return 1
@@ -139,10 +134,7 @@ def Load_project_config(path: str) -> int:
TargetDataLayout = tgt_dl_val.as_string() if tgt_dl_val is not None else None
# 读取 options 子对象
# Sha1SliceLevel: SHA1 切片层数(默认 1,每层取 SHA1 前 2 字符作为子目录)
# level=0 → 不切片,文件直接放 base_dir
# level=1 → b7/{sha1}.ext
# level=2 → b7/90/{sha1}.ext
# Sha1SliceLevel: SHA1 切片层数(默认 1,每层取 SHA1 前 2 字符作为子目录:0=不切片, 1=b7/, 2=b7/90/)
options: JsonValue | t.CPtr = root["options"]
Sha1SliceLevel = 1
StrictMode = 1
@@ -255,9 +247,7 @@ def _join_path(rel: str, project_dir: str) -> str:
# 绝对路径(以 / 或 \ 开头)保持不变
if rel[0] == '/' or rel[0] == '\\':
return rel
# [FIX] 命令行 --build-dir 按进程 cwd 给出的路径(如 ./TransPyV/.tpv_build2)
# 已含 project_dir 前缀(./TransPyV)时不再拼接,避免双份路径
# (./TransPyV/TransPyV/.tpv_build2)。边界取到目录分隔符或串尾。
# 路径已含 project_dir 前缀时不再拼接,避免双份路径(./TransPyV/TransPyV/.tpv_build2);边界取到目录分隔符或串尾
if project_dir is not None:
pdl: sizet = string.strlen(project_dir)
if pdl > 0 and string.strncmp(rel, project_dir, pdl) == 0:
@@ -337,13 +327,7 @@ def _normalize_path(path: str) -> str:
# 单段 ".":跳过
if seg_len == 1 and path[seg_start] == '.':
pass
# 单段 "..":弹出上一段;但**前导 ".." 不可弹**,必须保留。
# 旧实现只判 out_len > 0 就回退,于是 "无段可弹时保留" 的那一个
# 前导 ".." 会被下一个 ".." 当成普通段吃掉:
# ./VKernel/../../../includes → 缓冲反复清空 → "./includes"
# (cwd 在项目目录时,语义应得 "../../includes")。这正是
# ViperOS 各子工程 import 找不到模块的根因。
# 与 Python os.path.normpath / TPC 的行为对齐。
# 单段 "..":弹出上一段;前导 ".." 无段可弹时必须保留(对齐 Python os.path.normpath)
elif seg_len == 2 and path[seg_start] == '.' and path[seg_start + 1] == '.':
# 末段是否已经是前导 ".."(独立一段:要么是开头,要么前面是分隔符)
last_is_dotdot: int = 0
@@ -542,9 +526,7 @@ def resolve_paths(project_dir: str) -> int:
dl_pos += jl
dl_out[dl_pos] = '\0'
LinkerDynamicLibs = dl_out
# linker.flags 中 `-T <脚本>` 的链接脚本路径同样基于 project_dir 解析,
# 否则 subprocess 按进程 cwd(TransPyC/)找 linker.ld → ld.lld 报
# "cannot find linker script linker.ld"
# linker.flags 中 -T <脚本> 的路径同样基于 project_dir 解析,否则 ld.lld 按进程 cwd 找不到脚本
_resolve_linker_t_paths(project_dir)
return 0
@@ -705,20 +687,9 @@ def print_config():
# ============================================================
# slice_subdir - 根据 SHA1 和切片层数返回子目录路径
#
# 每层取 SHA1 的 2 个字符作为子目录名:
# level=0 → None(不切片)
# level=1 → "b7"
# level=2 → "b7/90"
# level=3 → "b7/90/23"
#
# Args:
# sha1: SHA1 字符串(至少 2*level 字符)
# level: 切片层数
#
# Returns:
# 子目录路径字符串(mbuddy 分配,无需释放),level<=0 时返回 None
# slice_subdir - 每层取 SHA1 的 2 字符作为子目录名(level=0 不切片, 1→"b7", 2→"b7/90")
# Args: sha1(至少 2*level 字符)/ level(切片层数)
# Returns: 子目录路径字符串(mbuddy 分配,无需释放),level<=0 时返回 None
# ============================================================
def slice_subdir(sha1: str, level: int) -> str:
"""根据 SHA1 和切片层数返回子目录路径"""
+4 -13
View File
@@ -11,9 +11,7 @@ import w32.fileio as fileio
# ============================================================
# Utils - 工程工具函数
#
# 提供 SHA1 计算和目录清理等通用工具函数。
# Utils - 工程工具函数(SHA1 计算、目录清理等)
# ============================================================
# 全局 mbuddy 指针
@@ -63,10 +61,7 @@ def bytes_to_hex(pool: rowptr[memhub.MemBuddy],
# ============================================================
# compute_sha1 - 计算字符串的 SHA1,返回前 16 个十六进制字符
#
# 使用 includes/hashlib 库的 sha1 类计算摘要,转为十六进制字符串。
# 参考 Projectrans.py 的 compute_sha1(hashlib.sha1().hexdigest()[:16])。
# compute_sha1 - 计算字符串的 SHA1,返回前 16 个十六进制字符(与 Projectrans.py 一致)
# ============================================================
def compute_sha1(pool: rowptr[memhub.MemBuddy], content: str) -> str:
"""计算字符串的 SHA1,返回前 16 个十六进制字符(用于文件命名)"""
@@ -130,7 +125,7 @@ def read_file_to_buf(path: str, buf_size: sizet) -> bytes:
合并自 Phase2/StubMerger/IncludesScanner 的重复"打开+读取+终止"序列。
"""
if path is None or buf_size <= 0: return None
sf: fileio.File | t.CPtr = fileio.File(path, fileio.MODE.R)
sf: rowptr[fileio.File] = fileio.File(path, fileio.MODE.R)
if sf.closed: return None
buf: bytes = stdlib.malloc(buf_size)
if buf is None:
@@ -200,11 +195,7 @@ def PathToModuleName(path: str, strip_includes_prefix: int) -> str:
# ============================================================
# CleanDir - 递归删除目录中所有文件和子目录(保留 dir_path 本身)
#
# 用于 --clean 选项:清理 temp_dir 和 output_dir 中的旧文件。
# SHA1 分片目录结构(如 temp/1c/、temp/7a/)需要递归清理,
# 否则旧的截断文件(如 256KB text.ll)会被 Phase1 跳过逻辑复用。
# CleanDir - 递归删除目录中所有文件和子目录(保留 dir_path 本身;用于 --clean)
# ============================================================
def CleanDir(dir_path: str) -> int:
"""递归删除目录中所有文件和子目录,保留 dir_path 本身,返回删除的文件数,-1 表示错误"""
+15 -64
View File
@@ -113,20 +113,8 @@ def _eval_binop_int(node: ast.AST | t.CPtr) -> int64:
# 避免循环依赖,使用弱引用
_g_pyi_cdefine_found: int = 0
# 本文件 CDefine 值缓存(vpi 生成专用):
# Phase1 生成 .vpi(L1614)早于翻译阶段的 CDefine 注册(L1629+),此时
# HandlesType 全局表为空 → BinOp 内 Name 引用查表失败退化为 0(ZDEFLATE
# WINDOW_SIZE = 1<<0 = 1、MASK = 0-1 = -1,跨模块读回错误常量根因)。
# _GeneratePyiFromAst 开头按声明顺序注册到本缓存,Name 引用先查缓存。
# 缓存一律用 C 数组而非 list[T]:
# - 值缓存用 C 数组(int64*)而非 list[int]:TPC 对 list[int].get 会误解析
# 为 list[str].get(返回元素指针 i8*)→ ptrtoint 返回元素地址而非值 → MASK
# 变成 20323785277439(地址-1)。C 数组索引 [i] 直接返回 i64 值,可靠。
# - 名缓存用 C 数组(str|t.CPtr = i8**,字符串指针数组)而非 list[str]:
# list[str] | t.CPtr 联合类型接收者的 .get 会被 TPC/TPV 误分派为
# HashTable.get(self, key, default) —— 索引被 inttoptr 成 key 指针传给
# _fnv1a_hash → 解引用零页 access-violation(1.LOG ASan 崩溃根因)。
# 读写沿用 argparse.vp 遍历 argv 的可靠模式:t.CPtr(base + i*8) + e[0]。
# 本文件 CDefine 值缓存(vpi 生成专用):vpi 生成早于翻译阶段注册(全局表为空),_GeneratePyiFromAst 开头按声明顺序注册,Name 引用先查本缓存。
# 两个缓存必须是 C 数组(int64* / i8**)而非 list[T]——list[int].get 会误解析为 list[str].get(返回元素地址)、list[str] 联合接收者的 .get 会误分派为 HashTable.get(索引 inttoptr 成 key 指针 → 零页崩溃);索引一律用 t.CPtr(base + i*8) + e[0]。
_g_pyi_cdef_names: str | t.CPtr = None
_g_pyi_cdef_vals: int64 | t.CPtr = None
_g_pyi_cdef_count: int = 0
@@ -137,13 +125,7 @@ def _lookup_cdefine_for_pyi(name: str) -> int64:
global _g_pyi_cdefine_found
# 优先查本文件 CDefine 缓存(vpi 生成早于翻译阶段注册,全局表为空)
if _g_pyi_cdef_names is not None and _g_pyi_cdef_vals is not None:
# 循环上限用 _g_pyi_cdef_count 而非 len(_g_pyi_cdef_names):
# i8** 指针数组上 TPV 的 len() 回退 strlen(把基址指针当字符串算长度
# → 垃圾小值 → 靠后注册项查不到,ZDEFLATE_HASH_MASK = lookup(HASH_SIZE)
# 失败 = 0-1 = -1 根因;WINDOW_MASK 查位置 1 恰好落在 strlen 结果内所以
# 碰巧正确)。且绝不能对 _g_pyi_cdef_names 调 .get —— list 联合接收者
# 会被误分派为 HashTable.get,索引 inttoptr 成 key 指针读零页崩溃
# (1.LOG)。用 t.CPtr(base + i*8) + e[0] 取第 i 个名字指针(argparse 模式)。
# 循环上限用 _g_pyi_cdef_count,且绝不能用 len()/.get:i8** 指针数组上 len() 回退 strlen(基址当字符串算长度 → 垃圾小值 → 靠后注册项查不到),.get 会被误分派为 HashTable.get(索引 inttoptr 成 key 指针 → 零页崩溃);用 t.CPtr(base + i*8) + e[0] 依次取名。
pn: sizet = sizet(_g_pyi_cdef_count)
pi: sizet
for pi in range(pn):
@@ -343,10 +325,7 @@ def _PyiTypeStr(buf: bytes, buf_size: sizet, pos: sizet,
pos = _PyiEmit(buf, buf_size, pos, ")")
return pos
# [S4 fail-fast] 未知节点类型:不再静默回退 int
# 上面已覆盖 Name/Attribute/Subscript/BinOp/Constant/Tuple/Call;走到此处
# 说明 AST 损坏或出现了未支持的注解形态——静默写 "int" 会生成错误签名,
# 让下游按错误类型解析(真正的类型信息丢失)。
# 未知节点类型 fail-fast:走到此处说明 AST 损坏或出现未支持的注解形态,报错退出(静默写 "int" 会生成错误签名,让下游按错误类型解析)
GenApi.fatal_error(annot, "vpi 类型注解节点类型未知", "TypeError")
return _PyiEmit(buf, buf_size, pos, "int") # 不可达(fatal_error 已退出)
@@ -424,12 +403,8 @@ def _PyiDefaultStr(buf: bytes, buf_size: sizet, pos: sizet,
# ============================================================
# _PyiCreturnArgs - 从装饰器列表提取 @c.CReturn(...) 的实参列表
#
# N11:`@c.CReturn(a, b, c)` 在定义侧被 FuncGenBase._resolve_creturn_ret_ty
# 展开为聚合返回类型 `{a, b, c}`,但 vpi 里该函数只有装饰器行、没有 `->` 注解
# → stub declare 退化成 i32(mm.GetMemoryStats 定义 `{i64,i64,i64}` vs
# `declare i32`)。生成 vpi 时据此合成 `-> tuple[a, b, c]`
# (_resolve_vpi_ret_annot_str 的既有 tuple 分支同样打包成 Struct),
# 使 stub declare 与 define 的返回类型一致。
# 定义侧把 @c.CReturn(a, b, c) 展开为聚合返回类型 {a, b, c},vpi 里该函数却只有装饰器行、无 `->` 注解,stub declare 会退化成 i32;
# 据此合成 `-> tuple[a, b, c]`(tuple 分支同样打包成 Struct),使 stub declare 与 define 的返回类型一致。
# 返回 None = 装饰器列表里没有 CReturn。
# ============================================================
def _PyiCreturnArgs(decorators: list[ast.AST | t.CPtr] | t.CPtr) -> list[ast.AST | t.CPtr] | t.CPtr:
@@ -492,11 +467,8 @@ def _PyiFunction(buf: bytes, buf_size: sizet, pos: sizet,
args_args: list[ast.AST | t.CPtr] | t.CPtr = args.args
an: sizet = len(args_args)
# 默认值右对齐到 args 尾部:defaults[i] 对应 args[an - dn + i]。
# vpi stub 必须输出 '= <default>',否则 VpiReader 解析后默认参数
# 表无条目,跨模块缺参调用(如 depth=0)读到栈垃圾。
# 注意:arg_defaults 必须内联声明(list[ast.AST|CPtr] | t.CPtr 联合
# 接收者的 .get 会被 TPC/TPV 误分派——预声明 + 重赋值会解析成
# list[int].get 返回 i32 截断指针崩溃;内联声明走 list[ast.ASTptr].get)。
# 必须输出 '= <default>',否则 VpiReader 解析出的默认参数表无条目,跨模块缺参调用(如 depth=0)读到栈垃圾。
# arg_defaults 必须内联声明:联合接收者的 .get 会被误分派,预声明 + 重赋值会解析成 list[int].get 返回 i32 截断指针而崩溃。
arg_defaults: list[ast.AST | t.CPtr] | t.CPtr = args.defaults
dn: sizet = 0
if arg_defaults is not None:
@@ -701,20 +673,10 @@ def _PyiClass(buf: bytes, buf_size: sizet, pos: sizet,
# ============================================================
# _GeneratePyiFromAst - 从 AST 生成 .vpi 存根文件
# _GeneratePyiFromAst - 遍历 AST Module 节点,提取函数签名/类定义/变量声明/import 语句,生成 .vpi 存根文件内容到 buf
#
# 遍历 AST Module 节点,提取函数签名、类定义、变量声明、import 语句,
# 生成 Python 类型存根文件内容到 buf。
#
# Args:
# mb: 内存池(保留以符合调用约定,实际未使用)
# tree: AST Module 根节点
# rel_path: 相对路径(用于模块名,当前未使用)
# buf: 输出缓冲区
# buf_size: 缓冲区大小
#
# Returns:
# 写入的字节数(0 表示失败)
# Args: mb 内存池(保留以符合调用约定,实际未使用);tree AST Module 根节点;rel_path 相对路径(用于模块名,当前未使用);buf 输出缓冲区;buf_size 缓冲区大小
# Returns: 写入的字节数(0 表示失败)
# ============================================================
def _GeneratePyiFromAst(mb: rowptr[memhub.MemBuddy], tree: ast.AST | t.CPtr,
rel_path: str, buf: bytes,
@@ -725,12 +687,8 @@ def _GeneratePyiFromAst(mb: rowptr[memhub.MemBuddy], tree: ast.AST | t.CPtr,
pos: sizet = 0
# 预处理:按声明顺序注册本文件 CDefine 到 _g_pyi 缓存(供 BinOp 求值)
# vpi 生成(Phase1 L1614)早于翻译阶段的 CDefine 注册(L1629+),直接查
# HandlesType 全局表为空 → BinOp 内 Name 引用退化为 0(ZDEFLATE_WINDOW_SIZE
# = 1<<0 = 1、MASK = 0-1 = -1 → 跨模块读回错误常量 → 索引越界崩溃根因)。
# 必须 global 声明:TPV 语义下函数内对模块级变量赋值无 global 时创建局部
# 变量(_lookup_cdefine_for_pyi 读到的仍是模块级 None → 缓存永不命中,
# 预处理:按声明顺序注册本文件 CDefine 到 _g_pyi 缓存(vpi 生成早于翻译阶段注册,直接查全局表为空会使 BinOp 内 Name 引用退化为 0,跨模块读回错误常量)。
# 必须带 global 声明:TPV 语义下函数内对模块级变量赋值无 global 时会创建局部变量,_lookup_cdefine_for_pyi 读到的仍是模块级 None,缓存永不命中。
global _g_pyi_cdef_names, _g_pyi_cdef_vals, _g_pyi_cdef_count
if tree.children is not None:
pre_children: list[ast.AST | t.CPtr] | t.CPtr = tree.children
@@ -902,12 +860,7 @@ def _GeneratePyiFromAst(mb: rowptr[memhub.MemBuddy], tree: ast.AST | t.CPtr,
viperlib.snprintf(num_buf, 32, " = %lld", cv.int_val)
pos = _PyiEmit(buf, buf_size, pos, num_buf)
elif cv.const_kind == ast.CONST_FLOAT:
# 浮点 CDefine(numpy 的 pi/e 等):stub 必须输出值。
# 否则读回端"vpi 中任一 CDefine 带值 → 不读源文件"
# 的启发式会被同模块 int CDefine(如 MAX_NDIM=4)
# 挡住,无值的 pi 解析不到 → 退化 @pi 全局指针 →
# float<->ptr 报错(NumpyTest 根因)。%.17g 保证
# double 全精度往返(与读回端缓存格式一致)。
# 浮点 CDefine(numpy 的 pi/e 等)必须输出值:读回端「vpi 中任一 CDefine 带值 → 不读源文件」的启发式会被同模块 int CDefine 挡住,无值的 pi 解析不到会退化成 @pi 全局指针导致 float<->ptr 报错;%.17g 保证 double 全精度往返(与读回端缓存格式一致)。
fbuf_c: bytes = mb.alloc(64)
if fbuf_c is not None:
viperlib.snprintf(fbuf_c, 64, " = %.17g", cv.float_val)
@@ -918,9 +871,7 @@ def _GeneratePyiFromAst(mb: rowptr[memhub.MemBuddy], tree: ast.AST | t.CPtr,
else:
pos = _PyiEmit(buf, buf_size, pos, " = False")
elif cv.const_kind == ast.CONST_STR:
# 字符串 CDefine(pyzlib.ZLIB_VERSION = "1.3.2" 等):
# stub 必须输出值(同 CONST_FLOAT 追加44 教训——读回端
# 按目标名判定会回退源文件可工作,但 stub 应自包含)
# 字符串 CDefine(pyzlib.ZLIB_VERSION = "1.3.2" 等)必须输出值,让 stub 自包含(读回端按目标名判定会回退源文件)
sbuf_c: bytes = mb.alloc(string.strlen(cv.str_val) + 6)
if sbuf_c is not None:
viperlib.snprintf(sbuf_c, string.strlen(cv.str_val) + 6,
+108 -307
View File
@@ -21,15 +21,9 @@ import lib.Projectrans.Config as Config
# ============================================================
# 方法返回注解缓存(stdlib 持久)
#
# 背景:vpi tree 在"解析后立即"有效,但遍历处理到后续类的方法时,
# 深层 AST 节点(如 parse_args 的 returns 注解)可能已被覆盖损坏
# (kind() 读取异常),直接 resolve 得到垃圾类型(如 i8*)。
# 因此采用文本提取方案:ParseVpiIntoModule 读 .vpi stub 时,从
# vpi_buf 原始文本(在函数末尾才释放,始终有效)直接按行格式
# 提取 "def m(...) -> Ret:" 的返回注解字符串缓存到 stdlib(持久,
# 不受 pool 状态影响)。HandlesExprCall._lookup_method_ret_ty_via_vpi
# 按 sha1.类名.方法名 查表并用 _resolve_vpi_ret_annot_str 纯字符串
# 解析为 LLVMType,返回类型始终来自方法定义处,无白名单/无硬编码。
# vpi tree 仅在解析后立即有效:处理到后续类的方法时深层 AST 节点(如 returns 注解)可能已被覆盖损坏(kind() 异常)→ 直接 resolve 得到垃圾类型。
# 故改文本提取:ParseVpiIntoModule 读 .vpi stub 时从 vpi_buf 原始文本(函数末尾才释放,始终有效)按行提取 "def m(...) -> Ret:" 的返回注解字符串缓存到 stdlib(持久,不受 pool 状态影响);
# HandlesExprCall._lookup_method_ret_ty_via_vpi 按 sha1.类名.方法名 查表并用 _resolve_vpi_ret_annot_str 纯字符串解析为 LLVMType,返回类型始终来自方法定义处,无白名单/无硬编码。
# ============================================================
VPI_RET_MAX: t.CDefine = 4096
VPI_KEY_LEN: t.CDefine = 224
@@ -43,16 +37,9 @@ _g_vpi_ret_cap: int = 0
# ============================================================
# 方法参数注解缓存(stdlib 持久,仿返回注解缓存)
#
# 背景:vpi tree 在"解析后立即"有效,但遍历处理到后续类的方法时,
# 深层 AST 节点(含函数参数列表)可能被覆盖损坏 → _inject_method_declare
# /_inject_function_declare 读 args 列表被截断(带默认值的尾部参数丢失,
# 同一 vpi 不同模块注入时忽对忽错)→ declare 参数数不足 → 调用点按
# declare 缺参(fill 无法超 declare 补)→ 被调函数按 x64 ABI 读残留
# 寄存器/栈槽垃圾(如"跨模块 CDefine 查找递归过深 depth=160":depth
# 与 imported_modules 均为垃圾,导致 imported_modules 指针失控递归)。
# 与返回注解同源:从 vpi_buf 原始文本(ParseVpiIntoModule 末尾才释放,
# 始终有效)按行提取每个函数/方法的参数注解串缓存,注入 declare 时
# 若 tree 参数数不足,用缓存补全,保证 declare 与 define 参数数一致。
# vpi tree 深层 AST 节点(含函数参数列表)可能被覆盖损坏 → args 被截断(带默认值的尾部参数丢失,同一 vpi 不同模块注入时忽对忽错)→ declare 参数数不足 →
# 调用点缺参(fill 无法超 declare 补)→ 被调函数按 x64 ABI 读残留寄存器/栈槽垃圾。故与返回注解同源:从 vpi_buf 原始文本按行提取参数注解串缓存,
# 注入 declare 时若 tree 参数数不足则用缓存补全,保证 declare 与 define 参数数一致。
# ============================================================
VPI_PARAM_MAX: t.CDefine = 4096
VPI_PARAM_KEY_LEN: t.CDefine = 224
@@ -411,12 +398,8 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
# ============================================================
# _cache_vpi_param_annots_text - 从 vpi_buf 文本提取函数/方法参数注解串
#
# 与 _cache_vpi_return_annots_text 同构:vpi tree 深层节点在遍历中可能被
# 覆盖损坏(参数列表截断),故从原始文本按行提取每个函数/方法的参数注解,
# 以 '@' 分隔存储(含首参 self → 空串占位,保证"参数位置 == 段索引";
# 注解文法不含 '@',安全作分隔符)。
# 存储格式:key="sha1.ClassName.method"(类内)/"sha1.method"(模块级),
# val=各参数注解串 '@' 分隔(无注解的参数为空串,如 self)。
# vpi tree 深层节点在遍历中可能被覆盖损坏(参数列表截断),故从原始文本按行提取参数注解,以 '@' 分隔存储(含首参 self → 空串占位,保证「参数位置 == 段索引」;注解文法不含 '@')。
# 存储格式:key="sha1.ClassName.method"(类内)/"sha1.method"(模块级),val=各参数注解串 '@' 分隔(无注解的参数为空串)。
# 调用方:ParseVpiIntoModule(tree 解析后立即调用,文本始终有效)。
# ============================================================
def _cache_vpi_param_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
@@ -712,15 +695,9 @@ def _lookup_vpi_param_annots(src_sha1: str, cls_name: str, method_name: str) ->
# ============================================================
# _resolve_vpi_param_annot_seg_template - 泛型特化类的参数注解段解析
#
# 真源:_cache_vpi_param_annots_text 的类名键段取自 vpi 的 class 行文本
# (取到 ':' / 空格 / '(' 为止),`class list[T]:` ⇒ 键段 "list[T]";因此特化
# 类名(list[str]/list[int]/list[rowptr[AST]])的精确键必然遗漏,定义侧形参
# 注解取不到 → 调用点 declare 停在调用点实参宽度(elem_size 字面量 i32 把
# 定义侧 t.CSizeT(i64) 固化窄)。
# 与 _lookup_vpi_ret_annot 的"泛型特化归一化匹配"同源:按 {sha1}.{base}[T].
# {method} 形态在缓存中定位模板条目,把段内独立的类型形参 token 用特化实参
# (cls_name 的 [...] 内容)替换后再解析(先例 _subst_template_arg)。
# 未命中/多参模板(键段含空格被截断,如 "Pair[A,")返回 None,不做兜底推断。
# 缓存键段取自 vpi 的 class 行文本(`class list[T]:` ⇒ 键段 "list[T]"),故特化类名(list[str]/list[int])的精确键必然遗漏,定义侧形参注解取不到 →
# 调用点 declare 停在调用点实参宽度(如 elem_size 字面量 i32 把定义侧 sizet(i64) 固化窄)。本函数按 {sha1}.{base}[T].{method} 形态定位模板条目,
# 把段内独立的类型形参 token 用特化实参(cls_name 的 [...] 内容)替换后再解析;未命中/多参模板(键段含空格被截断)返回 None,不做兜底推断。
# ============================================================
def _resolve_vpi_param_annot_seg_template(pool: rowptr[memhub.MemBuddy],
src_sha1: str,
@@ -822,10 +799,8 @@ def _resolve_vpi_param_annot_seg_template(pool: rowptr[memhub.MemBuddy],
# ============================================================
# _repair_declare_params_from_text - 用文本缓存补全 declare 参数
#
# vpi tree 参数列表被覆盖损坏时(param_count 偏小,尾部默认参数丢失),
# 用 _cache_vpi_param_annots_text 的文本缓存补全 declare:参数位置 p
# 对应缓存段 p(含首参空串占位)。补全后 declare 参数数与 define 一致,
# 调用点默认参数填充才能生效,避免被调函数读栈垃圾。
# vpi tree 参数列表被覆盖损坏时(param_count 偏小,尾部默认参数丢失),用 _cache_vpi_param_annots_text 的文本缓存补全:参数位置 p 对应缓存段 p(含首参空串占位)。
# 补全后 declare 参数数与 define 一致,调用点默认参数填充才能生效,避免被调函数读栈垃圾。
# ============================================================
def _repair_declare_params_from_text(pool: rowptr[memhub.MemBuddy],
func: llvmlite.Function | t.CPtr,
@@ -881,13 +856,9 @@ def _repair_declare_params_from_text(pool: rowptr[memhub.MemBuddy],
# ============================================================
# _resolve_vpi_param_annot_seg - 按段索引解析 vpi 参数注解为 LLVMType
#
# 返回类型已用字符串注解缓存解析(绕开 vpi tree 深层节点 kind() 损坏,见
# _inject_method_declare L1673-1675 注释);参数此前仍走 AST
# resolve_annotation_type,同样会得垃圾类型(如 sizet → i32),跨模块
# declare 参数类型错 → 调用侧 trunc i64→i32 传参、callee 按 i64 读栈残留
# 垃圾高位 → can=巨值读未写槽位崩溃(SlotsBugTest 根因)。
# 此处与返回类型同源:从 @-joined 注解串取第 seg_idx 段(0=self 空段),
# 用 _resolve_vpi_ret_annot_str 字符串解析。空段/未命中返回 None(走 AST 兜底)。
# 与返回类型同源地用注解串解析(绕开 vpi tree 深层节点 kind() 损坏与 AST resolve_annotation_type 得到的垃圾类型,如 sizet → i32):
# 参数类型错会导致调用侧 trunc 传参、callee 按 i64 读栈残留高位 → can=巨值读未写槽位崩溃(SlotsBugTest 根因)。
# 从 @-joined 注解串取第 seg_idx 段(0=self 空段),用 _resolve_vpi_ret_annot_str 解析;空段/未命中返回 None(走 AST 兜底)。
# ============================================================
def _resolve_vpi_param_annot_seg(pool: rowptr[memhub.MemBuddy],
src_sha1: str,
@@ -931,9 +902,7 @@ def _resolve_vpi_param_annot_seg(pool: rowptr[memhub.MemBuddy],
# ============================================================
# _grow_vpi_ret_cache - 扩容方法返回注解缓存(容量翻倍)
#
# 背景:VPI_RET_MAX 初始 4096,但 includes 模块量大(~90 个 stub、
# 每个几十个方法),缓存写满后条目被静默丢弃,导致已缓存模块的
# 方法(如 JsonValue._pool_alloc)查不到而 fail-fast 误报。
# VPI_RET_MAX 初始 4096,但 includes 模块量大(~90 个 stub、每模块几十个方法),缓存写满后条目被静默丢弃会让已缓存模块的方法查不到而 fail-fast 误报。
# 用 stdlib.realloc 翻倍扩容(上限 1M 条目,防御异常增长)。
# ============================================================
def _grow_vpi_ret_cache() -> int:
@@ -961,14 +930,9 @@ def _grow_vpi_ret_cache() -> int:
# ============================================================
# _ensure_vpi_ret_annot_loaded - 按需加载模块的 vpi stub 返回注解缓存
#
# 背景:_cache_vpi_return_annots_text 仅在 stub 注入(LoadDepVpiIntoModule,
# 即"当前模块 import 了该模块")时填充缓存。但模块自身翻译时,自己的
# stub 尚未被任何依赖方读取,且自己的 text.ll 要翻译完成才落盘,导致
# "同模块方法调用(定义在后)"(如 json.__setitem__ 调用后定义的
# _pool_alloc)在 found_func/text.ll/vpi 缓存三类来源上全部未命中。
# 此函数在缓存未命中时直接读 {temp_dir}/{sha1[0:2]}/{sha1}.vpi
# (Converter 在 deps 阶段已生成全部 stub 文件),填充缓存后即可命中。
# 幂等:模块已缓存过则跳过,不重复读文件。
# _cache_vpi_return_annots_text 仅在 stub 注入(当前模块 import 了该模块)时填充;模块自身翻译时自己的 stub 尚未被依赖方读取、text.ll 也未落盘,
# 导致「同模块方法调用(定义在后)」在 found_func/text.ll/vpi 缓存三类来源上全部未命中。
# 故缓存未命中时直接读 {temp_dir}/{sha1[0:2]}/{sha1}.vpi(Converter 在 deps 阶段已生成全部 stub)填充;幂等,模块已缓存过则跳过。
# ============================================================
def _ensure_vpi_ret_annot_loaded(src_sha1: str, temp_dir: str) -> int:
"""按需加载模块 vpi stub 的返回注解缓存,返回 1=已就绪 / 0=失败"""
@@ -1002,7 +966,7 @@ def _ensure_vpi_ret_annot_loaded(src_sha1: str, temp_dir: str) -> int:
vpi_path[td_len + 3] = '/'
string.strcpy(vpi_path + td_len + 4, src_sha1)
string.strcpy(vpi_path + td_len + 4 + sha1_len, ".vpi")
f: fileio.File | t.CPtr = fileio.File(vpi_path, fileio.MODE.R)
f: rowptr[fileio.File] = fileio.File(vpi_path, fileio.MODE.R)
if f.closed:
stdlib.free(vpi_path)
return 0
@@ -1025,13 +989,9 @@ def _ensure_vpi_ret_annot_loaded(src_sha1: str, temp_dir: str) -> int:
# ============================================================
# _ensure_vpi_param_annot_loaded - 按需加载模块的 vpi stub 参数注解缓存
#
# 与 _ensure_vpi_ret_annot_loaded 同构:_cache_vpi_param_annots_text 仅在
# stub 注入(LoadDepVpiIntoModule,即"当前模块 import 了该模块")时填充。
# 但跨模块方法调用可能绕过 stub 注入——如 ExprGenAttr 经 Generator 属性
# (self.Gen.ExprGenCdef.method)调用依赖模块方法,依赖收集不加载其 vpi
# → 函数表无 ParamCount/默认参数 → 调用点缺参 → 被调函数读栈残留
# (IterTest 跨模块 CDefine 查找 depth=160 假递归根因)。
# 此函数在参数缓存未命中时直接读 vpi 文件填充,幂等跳过。
# 与 _ensure_vpi_ret_annot_loaded 同构(缓存仅在 stub 注入时填充);跨模块方法调用可能绕过 stub 注入——如经 Generator 属性(self.Gen.ExprGenCdef.method)
# 调用依赖模块方法时依赖收集不加载其 vpi → 函数表无 ParamCount/默认参数 → 调用点缺参 → 被调函数读栈残留(IterTest 假递归根因)。
# 故参数缓存未命中时直接读 vpi 文件填充,幂等跳过。
# ============================================================
def _ensure_vpi_param_annot_loaded(src_sha1: str, temp_dir: str) -> int:
"""按需加载模块 vpi stub 的参数注解缓存,返回 1=已就绪 / 0=失败"""
@@ -1065,7 +1025,7 @@ def _ensure_vpi_param_annot_loaded(src_sha1: str, temp_dir: str) -> int:
vpi_path[td_len + 3] = '/'
string.strcpy(vpi_path + td_len + 4, src_sha1)
string.strcpy(vpi_path + td_len + 4 + sha1_len, ".vpi")
f: fileio.File | t.CPtr = fileio.File(vpi_path, fileio.MODE.R)
f: rowptr[fileio.File] = fileio.File(vpi_path, fileio.MODE.R)
if f.closed:
stdlib.free(vpi_path)
return 0
@@ -1088,9 +1048,7 @@ def _ensure_vpi_param_annot_loaded(src_sha1: str, temp_dir: str) -> int:
# ============================================================
# _subst_template_arg - 替换注解字符串中独立的模板参数 token
#
# 泛型模板返回注解(如 list[T].get -> T)中的类型参数名(T)
# 需替换为实际特化实参(AST|t.CPtr)后才能解析。仅替换前后
# 非字母数字下划线边界的独立 token(避免误替换 sizet 等)。
# 泛型模板返回注解(如 list[T].get -> T)中的类型参数名需替换为实际特化实参后才能解析;仅替换前后为非字母数字下划线边界的独立 token(避免误替换 sizet 等)。
# 返回 stdlib 新分配字符串(调用者负责 free);失败返回 None。
# ============================================================
def _subst_template_arg(annot: str, tparam: str, targ: str) -> str:
@@ -1308,14 +1266,8 @@ def _resolve_single_vpi_type_str(pool: rowptr[memhub.MemBuddy],
if inner_ty_rp is not None:
return llvmlite.Ptr(pool, inner_ty_rp)
return None
# t.CPtr[T] / c.CPtr[T] 下标指针(含嵌套,如 t.CPtr[t.CPtr] → Ptr(Ptr(i8))):
# 与定义侧 TypeGenResolve.resolve_annotation_type 的 Subscript(CPtr) 分支
# (TypeGenResolve.vp L2769-2790)同口径 Ptr(resolve(T)),T 自身可含指针层级。
# 先例:上方 rowptr[T] 分支(L1187-1200)同形(前缀 + 尾 ']' 判定 + 递归 + Ptr 包装)。
# vpi 注解文本按源码原样缓存(如 `out: t.CUInt8T | t.CPtr[t.CPtr]`),本函数
# 此前对该形态一律返回 None → 段被丢弃 → 联合坍缩到首段(i8),与定义侧
# i8** 不一致(跨模块 declare/define ABI 错位;见 _resolve_vpi_ret_annot_str
# 段循环内的同源判据)。
# t.CPtr[T] / c.CPtr[T] 下标指针(含嵌套,如 t.CPtr[t.CPtr] → Ptr(Ptr(i8))):与定义侧 TypeGenResolve 的 Subscript(CPtr) 分支同口径 Ptr(resolve(T))。
# vpi 注解文本按源码原样缓存;本函数若对该形态返回 None 会让该段被丢弃 → 联合坍缩到首段(i8),与定义侧指针层级不一致(跨模块 declare/define ABI 错位)。
cptr_sub_pfx: sizet = 0
if string.strncmp(seg, "t.CPtr[", 7) == 0 or string.strncmp(seg, "c.CPtr[", 7) == 0:
cptr_sub_pfx = 7
@@ -1383,14 +1335,9 @@ def _resolve_single_vpi_type_str(pool: rowptr[memhub.MemBuddy],
# ============================================================
# _resolve_vpi_ret_annot_str - 直接解析 vpi 返回注解字符串为 LLVMType
#
# 背景:_parse_type_string 跨模块构造 ast.Name/ast.BinOp 在 TPV 中可能
# 退化为裸函数调用(__init__/vtable 未执行),节点 kind() 异常导致
# resolve 得到垃圾类型(如 Ptr(i8))。此处纯字符串解析避免该问题。
# 纯字符串解析,避免 _parse_type_string 跨模块构造 ast.Name/ast.BinOp 时节点 kind() 异常(可能退化为裸函数调用)导致 resolve 得到垃圾类型(如 Ptr(i8))。
#
# 支持: "str"/"bytes" → i8*; "int" → i32; "bool" → i1;
# "char" 等 → map_t_type 映射;
# "X | t.CPtr" → Ptr(Struct X); "t.CPtr" → i8*;
# "rowptr[memhub.MemBuddy]" → Ptr(Struct MemBuddy)。
# 支持: "str"/"bytes" → i8*; "int" → i32; "bool" → i1; "char" 等 → map_t_type 映射; "X | t.CPtr" → Ptr(Struct X); "t.CPtr" → i8*; "rowptr[memhub.MemBuddy]" → Ptr(Struct MemBuddy)。
# ============================================================
def _resolve_vpi_ret_annot_str(pool: rowptr[memhub.MemBuddy],
ret_annot: str,
@@ -1452,7 +1399,7 @@ def _resolve_vpi_ret_annot_str(pool: rowptr[memhub.MemBuddy],
has_cvoid: int = 0
first_ty: llvmlite.LLVMType | t.CPtr = None
# 产生 first_ty 那一段文本的属性(供末尾 has_cptr 分支复刻定义侧规则,N12):
# first_qual=1 段是限定名(含 '.',如 mod.Class / t.CInt64T)
# first_qual=1 段是限定名(含 '.',如 mod.Class / int64)
# first_wl =1 段是白名单裸标量名(int/char/str/bytes/bool/float/double)
# first_td =1 段是裸 typedef 别名且已成功解析出**非指针**类型(N13,
# 如 sizet/uint64 ⇒ i64):判据=“解析是否成功”,与定义侧
@@ -1461,14 +1408,9 @@ def _resolve_vpi_ret_annot_str(pool: rowptr[memhub.MemBuddy],
first_qual: int = 0
first_wl: int = 0
first_td: int = 0
# CPtr 下标段(t.CPtr[T]/c.CPtr[T],含嵌套)的联合合并参数:
# cptr_sub_lvl = 该段解析后的外层 Ptr 层数 N(如 t.CPtr[t.CPtr] ⇒ 2);
# cptr_sub_ty = 该段解析出的类型本身(无另一侧基类型时即结果)。
# 判据与定义侧 TypeGenResolve.vp 的 `X | t.CPtr[T]` 分支(L2596-2639)同口径:
# CPtr 侧贡献 N 层指针、另一侧提供基类型 ⇒ 结果 Ptr^N(基类型)。
# 与 has_cptr(裸 t.CPtr 段)分开记录:裸标记路径恒"追加 1 层"且有 struct
# 吸收特例(定义侧 L2540-2594),下标路径无吸收(定义侧 L2610-2639),
# 两者在定义侧分属互斥分支(_is_cptr_marker / _is_cptr_subscript)。
# CPtr 下标段(t.CPtr[T]/c.CPtr[T],含嵌套)的联合合并参数:cptr_sub_lvl = 该段解析后的外层 Ptr 层数 N(如 t.CPtr[t.CPtr] ⇒ 2);cptr_sub_ty = 该段解析出的类型本身。
# 与定义侧 `X | t.CPtr[T]` 分支同口径:CPtr 侧贡献 N 层指针、另一侧提供基类型 ⇒ Ptr^N(基类型)。
# 与 has_cptr(裸 t.CPtr 段)分开记录:裸标记恒「追加 1 层」且有 struct 吸收特例,下标路径无吸收,两者在定义侧分属互斥分支。
cptr_sub_lvl: int = 0
cptr_sub_ty: llvmlite.LLVMType | t.CPtr = None
@@ -1542,18 +1484,9 @@ def _resolve_vpi_ret_annot_str(pool: rowptr[memhub.MemBuddy],
string.strcmp(seg_buf, "float") == 0 or \
string.strcmp(seg_buf, "double") == 0:
first_wl = 1
# 裸名 typedef 别名保真(N13,与定义侧同源):sizet/uint64/INT/u32
# 等别名经上面 _resolve_single_vpi_type_str 已成功解析出标量
# (sizet ⇒ i64、INT/u32 ⇒ i32),只是"不是 struct 且不在白名单"
# 就被下方坍缩成 i8* → declare 侧 i8* 而定义侧(已同步改为
# "解析成功即保留")为 i64* → 跨模块同型收敛被破坏。
# 判据直接用与定义侧同一个 API GenApi.resolve_typedef_or_map
# (委托 TypeGenResolve.resolve_typedef_or_map:CTypedef 动态表 →
# builtins → stdint/w32.win32base 库源 → map_t_type),
# 命中即置位保留;不另立手写名字白名单。
# 仅对**非指针**解析结果置位:指针别名(BYTEPTR/HANDLE/VOIDPTR
# 解析为 Ptr(...))不置位 → 仍坍缩 i8*,与定义侧守卫
# (td_res_is_ptr != 0 时不套用 typedef 保真)对称。
# 裸名 typedef 别名保真(N13,与定义侧同源):sizet/uint64/INT/u32 等别名若被判「非 struct 且不在白名单」会被坍缩成 i8*,
# 而定义侧已改为「解析成功即保留」⇒ 跨模块同型收敛被破坏。判据直接用 GenApi.resolve_typedef_or_map(与定义侧同一 API),命中即置位保留;
# 仅对**非指针**解析结果置位(指针别名 BYTEPTR/HANDLE/VOIDPTR 不置位 → 仍坍缩 i8*,与定义侧守卫对称)。
if first_qual == 0 and first_wl == 0:
if first_ty.kind() != llvmlite.LLVMType.Ptr:
if GenApi.resolve_typedef_or_map(pool, seg_buf, None, None) is not None:
@@ -1565,25 +1498,10 @@ def _resolve_vpi_ret_annot_str(pool: rowptr[memhub.MemBuddy],
if has_cvoid != 0:
return i8_ptr_ty
if first_ty is not None:
# 复刻定义侧规则(N12/N13):限定名 / 白名单裸标量 / 已注册结构体 /
# 可解析的裸 typedef 别名(非指针)→ 保留 pointee;其余(解析失败、
# 裸枚举名、指针别名)→ 联合坍缩为 i8*,与 resolve_annotation_type 一致。
# 收敛依据(替换旧理由"避免定义侧 i8* 而 declare i64* 跨模块不收敛"):
# 定义侧同一分支已改为"解析成功即保留(非指针)",`sizet | t.CPtr` 定义侧
# ⇒ i64*(见 TypeGenResolve.vp BinOp(A | t.CPtr) 的 typedef 别名保真判定),
# 本侧经同一 GenApi.resolve_typedef_or_map 命中后亦 ⇒ Ptr(i64) = i64*;
# **两侧同型即收敛**,不再需要把 declare 侧坍缩成 i8* 去迁就定义侧。
# 仍坍缩 i8* 的两类与定义侧对称:解析失败兜底;裸枚举名(定义侧
# get_struct_type miss 同样落 i8*)。指针别名(first_td 不置位)
# 已按下段"追加 1 层"处理(与定义侧 td_res_is_ptr 口径同步)。
# 指针别名(first_td 不置位,如 VOIDPTR = CVoid|CPtr → i8*、
# HANDLE = VOIDPTR → i8*):`| t.CPtr` **追加 1 层** → i8**(不吸收)。
# 判据与定义侧 TypeGenResolve.vp BinOp(A | t.CPtr) 的指针层数口径
# 同源(TPC HandlesTypeMerge._MergeAllComponentTypes L940-946:
# 仅 IsStruct 时吸收 1 层):pointee 命中 find_struct_by_type
# ⇒ 类引用语义 ⇒ 吸收(返回 first_ty 即 Ptr(struct));
# 否则 ⇒ Ptr(first_ty)。此前无条件坍缩 i8* → 定义侧/注入侧虽同为
# i8* 但比 TPC 短一层(TPC `VOIDPTR | t.CPtr` = i8**)。
# 复刻定义侧规则(N12/N13):限定名 / 白名单裸标量 / 已注册结构体 / 可解析的非指针裸 typedef 别名 → 保留 pointee;
# 其余(解析失败、裸枚举名、指针别名)→ 联合坍缩为 i8*,与定义侧 resolve_annotation_type 对称(两侧同型即收敛)。
# 指针别名(first_td 不置位,如 VOIDPTR = CVoid|CPtr → i8*):`| t.CPtr` **追加 1 层** → i8**(不吸收);
# 仅当 pointee 命中 find_struct_by_type(类引用语义)时吸收 1 层,其余 ⇒ Ptr(first_ty)(与定义侧指针层数口径同源)。
if first_qual != 0 or first_wl != 0 or first_td != 0:
return llvmlite.Ptr(pool, first_ty)
if first_ty.kind() == llvmlite.LLVMType.Struct:
@@ -1598,14 +1516,8 @@ def _resolve_vpi_ret_annot_str(pool: rowptr[memhub.MemBuddy],
return llvmlite.Ptr(pool, first_ty)
return i8_ptr_ty
return i8_ptr_ty
# CPtr 下标段联合合并(定义侧 TypeGenResolve.vp L2596-2639 同口径):
# 有另一侧基类型 first_ty → 结果 = Ptr^N(first_ty)(N=cptr_sub_lvl)
# 仅 CPtr 下标段自身 → 结果 = 该段解析类型本身(等价定义侧
# Subscript(CPtr) 分支 L2769-2790 的 Ptr^N(inner))
# 下标段无 struct 吸收(定义侧该分支不吸收),故不与上方 has_cptr 分支的
# 吸收判据混用。has_cptr(裸 t.CPtr 段)在上一分支已全部返回,二者不叠加:
# 定义侧裸标记(_is_cptr_marker)与下标标记(_is_cptr_subscript)亦互斥,
# `X | t.CPtr | t.CPtr[...]` 这类混合写法本函数不建模(保持 has_cptr 旧行为)。
# CPtr 下标段联合合并(与定义侧同口径):有另一侧基类型 first_ty → 结果 = Ptr^N(first_ty)(N=cptr_sub_lvl);仅 CPtr 下标段自身 → 该段解析类型本身。
# 下标段无 struct 吸收,不与上方 has_cptr 分支的吸收判据混用;has_cptr 在上一分支已全部返回,二者不叠加。
if cptr_sub_lvl > 0:
if first_ty is None:
return cptr_sub_ty
@@ -1628,13 +1540,9 @@ def _resolve_vpi_ret_annot_str(pool: rowptr[memhub.MemBuddy],
# ============================================================
# VpiReader - .vpi AST 解析器
#
# 从 .vpi 存根文件读取函数签名和 CTypedef 定义,注入 declare
# 到当前模块。用于跨模块函数调用时补充缺失的函数声明。
#
# .vpi 文件由 StubGen.Converter._GeneratePyiFromAst 生成,
# 包含函数签名(无函数体)、类定义、变量声明、import 语句。
# VpiReader 解析 .vpi 为 AST,提取 FunctionDef 和 CTypedef
# AnnAssign,在当前模块创建对应的 declare 声明。
# 从 .vpi 存根文件读取函数签名与 CTypedef 定义,注入 declare 到当前模块,用于跨模块函数调用时补充缺失的函数声明。
# .vpi 由 StubGen.Converter._GeneratePyiFromAst 生成(含函数签名、类定义、变量声明、import 语句);
# 本模块将其解析为 AST,提取 FunctionDef 与 CTypedef AnnAssign 并创建对应的 declare 声明。
# ============================================================
# .vpi 读取缓冲区大小(256KB,与 PYI_BUF_SIZE 一致)
@@ -1642,26 +1550,18 @@ VPI_READ_BUF_SIZE: sizet = 262144
# SHA1 hex 字符数(16,仅文件名中的十六进制位,不含结尾 NUL)
#
# 注意:本常量原名 SHA1_LEN,与 lib.Phase.Phase1 的常量宏
# Phase1.SHA1_LEN: t.CDefine = 17(16 字符 + NUL 的存储步长)**同名但语义不同**。
# 同名跨模块符号会造成 CDefine 解析被遮蔽/串值(Phase1.MAX_CLASSES * Phase1.SHA1_LEN
# 被解析成 0 → mb.alloc(0) → 分配失败)。故改为唯一名 SHA1_HEX_LEN 消除冲突;
# 权威定义仍分别是:本处 16(hex 位数)、Phase1.SHA1_LEN 17(存储步长)。
# 注意:本常量与 lib.Phase.Phase1 的 Phase1.SHA1_LEN(17 = 16 字符 + NUL 的存储步长)同名但语义不同;
# 同名跨模块 CDefine 会造成解析被遮蔽/串值(mb.alloc(0) 分配失败),故改为唯一名 SHA1_HEX_LEN。
SHA1_HEX_LEN: sizet = 16
# ============================================================
# _extract_sha1_from_path - 从 .vpi 文件路径提取 SHA1
#
# .vpi 文件名格式为 {sha1}.vpi,sha1 是 16 位十六进制字符。
# 从路径中找到最后一个路径分隔符(/ 或 \),取后续文件名的前
# 16 个字符作为 SHA1。分配新缓冲区存储 SHA1(调用者负责 free)。
# .vpi 文件名为 {sha1}.vpi(sha1 为 16 位十六进制字符):取最后一个路径分隔符(/ 或 \)之后文件名的前 16 个字符,分配新缓冲存储(调用者负责 free)。
#
# Args:
# vpi_path: .vpi 文件路径
#
# Returns:
# SHA1 字符串(stdlib.malloc 分配,需 stdlib.free),None 失败
# Args: vpi_path .vpi 文件路径
# Returns: SHA1 字符串(stdlib.malloc 分配,需 stdlib.free),None 失败
# ============================================================
def _extract_sha1_from_path(vpi_path: str) -> str:
"""从 .vpi 文件路径提取 SHA1(文件名前16字符,malloc 分配)"""
@@ -1766,14 +1666,9 @@ def _stub_add_from(pool: rowptr[memhub.MemBuddy],
# ============================================================
# 递归 typedef 预注册(import 闭包)
#
# 背景:vpi stub 注入(_inject_function_declare)解析参数注解(如
# SetConsoleOutputCP(codepage: UINT))时,UINT 等 typedef 定义在 stdint.vp,
# 但 CTypedef 表是每文件隔离的——当前模块若未直接 import stdint,其 stub
# 未注入 → 表里没有 UINT → 参数类型解析失败退化为 i8_ptr_ty(指针)→
# 调用侧整数实参触发"整数隐式转指针已禁止"CastError 误报
# (testcheck.SetConsoleOutputCP(CP_UTF8) 根因)。
# 本函数在被读 stub 的 import 闭包上递归注册 CTypedef/CDefine(防环、
# 限深),使注解解析上下文完整,参数/返回类型不再静默退化。
# CTypedef 表是每文件隔离的:当前模块若未直接 import typedef 定义所在模块(如 UINT 在 stdint.vp),其 stub 未注入 → 表里没有该 typedef
# → 参数类型解析失败退化为 i8_ptr_ty → 调用侧整数实参触发「整数隐式转指针已禁止」CastError 误报。
# 本函数在被读 stub 的 import 闭包上递归注册 CTypedef/CDefine(防环、限深),使注解解析上下文完整,参数/返回类型不再静默退化。
# ============================================================
_TYPEDEF_VISIT_MAX: t.CDefine = 1024
@@ -1876,7 +1771,7 @@ def _vpi_register_typedef_closure(pool: rowptr[memhub.MemBuddy],
vpi_path_r[td_len + 3] = '/'
string.strcpy(vpi_path_r + td_len + 4, sha1_r)
string.strcpy(vpi_path_r + td_len + 4 + 16, ".vpi")
f_r: fileio.File | t.CPtr = fileio.File(vpi_path_r, fileio.MODE.R)
f_r: rowptr[fileio.File] = fileio.File(vpi_path_r, fileio.MODE.R)
if f_r.closed:
stdlib.free(vpi_path_r)
continue
@@ -1901,12 +1796,8 @@ def _vpi_register_typedef_closure(pool: rowptr[memhub.MemBuddy],
if tree_r is not None and tree_r.children is not None:
ch_r: list[ast.AST | t.CPtr] | t.CPtr = tree_r.children
cn_r: sizet = len(ch_r)
# 收集子模块 imports(递归闭包)
# [D7·截断纪律] 旧实现固定 4096 缓冲 + `sub_len + len + 2 < 4096`
# 守卫:装不下就**静默丢弃**该 import ⇒ 递归 typedef 预注册闭包
# 缺模块 ⇒ 该模块的 CTypedef/CDefine 不可见 ⇒ 注解解析退化
# (同一「定长缓冲静默截断」族)。改为**两遍**:先累计所需长度,
# 再按精确长度分配后填充——不存在长度上限,故无需丢弃。
# 收集子模块 imports(递归闭包):两遍处理——先累计所需长度,再按精确长度分配后填充,不存在长度上限,故无需丢弃。
# (定长缓冲 + "装不下就静默丢弃" 会让闭包缺模块 ⇒ 该模块的 CTypedef/CDefine 不可见 ⇒ 注解解析退化。)
# 计数阶段每项多算 1 字节分隔符(首项不用),偏保守,无害。
sub_need: sizet = 1 # 末尾 NUL
ci_c: sizet
@@ -2001,24 +1892,12 @@ def _vpi_register_typedef_closure(pool: rowptr[memhub.MemBuddy],
# ============================================================
# ParseVpiIntoModule - 解析 .vpi 文件,将函数声明注入到当前模块
#
# 读取 .vpi 文件 → AST 解析 → 遍历顶层节点 → 创建 declare 声明。
# 对每个 FunctionDef:
# 1. 提取函数名、参数、返回类型
# 2. 检测 CExport/CExtern/State 标记(决定是否用裸名)
# 3. 用 find_func_in_module 去重(已存在则跳过)
# 4. 用 create_declare + add_param 创建 declare 声明
# 5. 注册到函数表(用裸名,非 mangled_name)
# 对 AnnAssign with t.CTypedef:调用 register_ctypedef 注册
# ClassDef/Import/ImportFrom:跳过
# 读取 .vpi → AST 解析 → 遍历顶层节点创建 declare:对每个 FunctionDef 提取函数名/参数/返回类型,检测 CExport/CExtern/State 标记(决定是否用裸名),
# 用 find_func_in_module 去重,再用 create_declare + add_param 创建声明并以裸名(非 mangled_name)注册到函数表;
# 对 AnnAssign with t.CTypedef 调 register_ctypedef 注册;ClassDef/Import/ImportFrom 跳过。
#
# Args:
# pool: 内存池
# mod: 当前 LLVM 模块
# vpi_path: .vpi 文件路径
# trans: 翻译器对象
#
# Returns:
# 1=成功,0=失败
# Args: pool 内存池;mod 当前 LLVM 模块;vpi_path .vpi 文件路径;trans 翻译器对象
# Returns: 1=成功,0=失败
# ============================================================
def ParseVpiIntoModule(pool: rowptr[memhub.MemBuddy],
mod: llvmlite.LLVMModule | t.CPtr,
@@ -2048,7 +1927,7 @@ def _ParseVpiIntoModuleImpl(pool: rowptr[memhub.MemBuddy],
return 0
# 1. 读取 .vpi 文件
f: fileio.File | t.CPtr = fileio.File(vpi_path, fileio.MODE.R)
f: rowptr[fileio.File] = fileio.File(vpi_path, fileio.MODE.R)
if f.closed:
return 0
vpi_buf: bytes = stdlib.malloc(VPI_READ_BUF_SIZE)
@@ -2125,14 +2004,8 @@ def _ParseVpiIntoModuleImpl(pool: rowptr[memhub.MemBuddy],
elif sk == ast.ASTKind.ImportFrom:
sifr: ast.ImportFrom | t.CPtr = (ast.ImportFrom | t.CPtr)(schild)
sifr_mod: str = sifr.module
# [D26 相对导入补包前缀] vpi 里 `from .base import X` 只存 module="base"
# (前导点记在 level)。若 from-import 映射里写字面 "base",解析端按
# **精确** LookupSha1ByModName("base") 查不到 "ast.base" ⇒ 裸类名(AST 等)
# 解析失败 ⇒ rowptr[AST] 等注解 fail-fast(TPV→TPV 自举
# includes/ast/parser.vp `parse -> rowptr[AST]` 根因)。
# 这里按 stub 自身的相对路径还原包名(ast/parser.vp → "ast"),
# level==1 时拼成完整模块名 "ast.base" 再登记(level>1 保持原样,
# 避免在未确认路径形态时做过深上溯)。
# 相对导入补包前缀:vpi 里 `from .base import X` 只存 module="base"(前导点记在 level),若按字面 "base" 精确查找则查不到 "ast.base" ⇒ 裸类名(AST 等)解析失败 ⇒ rowptr[AST] 等注解 fail-fast。
# 故按 stub 自身相对路径还原包名(ast/parser.vp → "ast"),level==1 时拼成完整模块名 "ast.base" 再登记(level>1 保持原样,避免未确认路径形态时做过深上溯)。
sifr_mod_q: str = sifr_mod
if sifr_mod is not None and sifr_mod[0] != '\0' and sifr.level == 1:
rel_p_r: str = StubMergerStore.LookupSha1RelPath(src_sha1)
@@ -2269,10 +2142,8 @@ def _ParseVpiIntoModuleImpl(pool: rowptr[memhub.MemBuddy],
# ============================================================
# 第一遍 A:先注册**所有** ClassDef(字段 + 结构体),不处理方法。
# 此前单遍顺序处理,类定义位于全局变量之后时(如 devfs.vp:
# `directories: t.CArray[devfs_directory, MAX_DIRECTORIES]` 在
# `class devfs_directory` 之前),AnnAssign 全局变量解析裸类名时类
# 未注册 → CArray 元素解析失败 fatal(VKernel PhaseA 根因)。
# 否则类定义位于全局变量之后时(如 devfs.vp 的 `directories: t.CArray[devfs_directory, MAX]` 在 `class devfs_directory` 之前),
# AnnAssign 解析裸类名时类未注册 → CArray 元素解析失败 fatal。
# ============================================================
i0: sizet
for i0 in range(n):
@@ -2294,13 +2165,8 @@ def _ParseVpiIntoModuleImpl(pool: rowptr[memhub.MemBuddy],
src_sha1, imported_modules, from_imports)
# ============================================================
# 第一遍 B:**全部类注册完成之后**再处理方法 declare。
# 方法形参/返回值可能引用本文件里**更靠后定义**的类(类间前向引用),如
# llvmlite/__builder.vpi 的 IRBuilder.build_phi(incoming_head:
# rowptr[PhiIncoming]),PhiIncoming 定义在同文件更后面——若边注册边处理
# 本类方法,前向引用的类此刻尚未注册 → 形参注解裸名解析落空 → fail-fast
# (TPV→TPV 自举 "extern 方法 'IRBuilder.build_phi' 形参 'incoming_head'
# 类型注解解析失败" 根因)。
# 第一遍 B:**全部类注册完成之后**再处理方法 declare——方法形参/返回值可能引用本文件更靠后定义的类(类间前向引用),
# 若边注册边处理方法,前向引用的类此刻尚未注册 → 形参注解裸名解析落空 → fail-fast。
# ============================================================
i1: sizet
for i1 in range(n):
@@ -2417,21 +2283,13 @@ def _ParseVpiIntoModuleImpl(pool: rowptr[memhub.MemBuddy],
# ============================================================
# 依赖 vpi 按需注入(D26 根因:包 __init__ 重导出与子模块 stub 互相等待)
# 依赖 vpi 按需注入(包 __init__ 重导出与子模块 stub 互相等待问题)
#
# LoadDepVpiIntoModule 只按当前模块的 imported_modules 注入**一层**依赖 vpi,
# 不递归。包 __init__.vpi(如 ast/__init__.vpi)自身不含 class 定义,其类全部
# 来自 `from .sub import X` 重导出;而重导出注册(_inject_reexport_class_declare)
# 要求源模块 stub 已注入并以**定义文件 sha1** 注册过 entry。两者互相等待
# ⇒ 包内每个类都注册不上 ⇒ 包 stub 里引用这些类的注解(如
# ast/__init__.vpi 的 `parse -> rowptr[AST]`)在裸名解析时 struct 表落空
# → fail-fast(TPV→TPV 自举障碍)。
# LoadDepVpiIntoModule 只按 imported_modules 注入**一层**依赖 vpi、不递归;包 __init__.vpi 自身不含 class 定义(类全部来自 `from .sub import X` 重导出),
# 而重导出注册要求源模块 stub 已注入并以**定义文件 sha1** 注册过 entry ⇒ 两者互相等待 ⇒ 包内类都注册不上 ⇒ 包 stub 中引用这些类的注解裸名解析落空 → fail-fast。
#
# 这里在重导出注册发现"源 entry 为空"时,按需把 sub_sha1 的 vpi 注入当前
# 模块再重试注册。注入过程本身会递归触发同一逻辑(子模块 vpi 的重导出行
# 同样按需注入),依赖闭包自动补齐,与 vpi 加载顺序无关。
# 防重入表按 sha1 去重(环如 base → tokens → base 直接短路)。
# 失败不改语义:注入不来就照原逻辑 return 0,不把可恢复问题升级为 fatal。
# 故在重导出注册发现「源 entry 为空」时,按需把 sub_sha1 的 vpi 注入当前模块再重试注册(注入过程递归触发同一逻辑,依赖闭包自动补齐,与加载顺序无关)。
# 防重入表按 sha1 去重(环直接短路);注入失败不改语义,照原逻辑 return 0,不把可恢复问题升级为 fatal。
# ============================================================
VPI_INJ_MAX: t.CDefine = 1024
_vpi_inj_sha1s: bytes = None
@@ -2493,7 +2351,7 @@ def _ensure_dep_vpi_injected(pool: rowptr[memhub.MemBuddy],
viperlib.snprintf(path_i, path_len_i, "%s/%s/%s.vpi", temp_dir, sub_i, dep_sha1)
else:
viperlib.snprintf(path_i, path_len_i, "%s/%s.vpi", temp_dir, dep_sha1)
vf_i: fileio.File | t.CPtr = fileio.File(path_i, fileio.MODE.R)
vf_i: rowptr[fileio.File] = fileio.File(path_i, fileio.MODE.R)
if vf_i.closed:
stdlib.free(path_i)
return 0
@@ -2521,24 +2379,12 @@ def _ensure_dep_vpi_injected(pool: rowptr[memhub.MemBuddy],
# ============================================================
# _inject_reexport_class_declare - 包重导出类以包 sha1 注册别名 entry
#
# 场景:TPV 源码 `import ast` 后写 `ast.Attribute`(模块限定名)。查找端
# _resolve_module_sha1("ast") 经 "__init__ 包回退"解析为包 sha1,但 Attribute
# 定义在 ast/exprs.vp 并以定义文件 sha1 注册 → find_struct_by_module
# name+sha1 双精确匹配错位 fail-fast(lookup_field_by_class 'ast.Attribute'
# 未找到根因)。
# 场景:源码 `import ast` 后写 `ast.Attribute`(模块限定名),查找端按「__init__ 包回退」解析为包 sha1,但 Attribute 定义在 ast/exprs.vp 并以定义文件 sha1 注册
# → find_struct_by_module 的 name+sha1 双精确匹配错位 fail-fast。本函数在处理包 __init__.vpi 的 `from .exprs import Attribute` 重导出行时调用:
# 复用子模块已注册的 entry(Ty/Fields/父类/虚表)以包 sha1 追加别名注册,使 ast.Attribute 按包归属可解析;非类名 find_struct_by_module 查不到自然跳过。
# 仅包 __init__ 触发,避免把普通模块的跨模块 from-import 错误归属到当前模块 sha1。
#
# 本函数在 _ParseVpiIntoModuleImpl 处理包 __init__.vpi 的 `from .exprs import
# Attribute` 重导出行时调用:复用子模块已注册的 entry(Ty/Fields/父类/虚表),
# 以包 sha1 追加别名注册,使 ast.Attribute 按包归属可解析。非类名(函数/
# 常量)find_struct_by_module 查不到自然跳过。仅包 __init__ 触发,避免把
# 普通模块的跨模块 from-import 错误归属到当前模块 sha1。
#
# Args:
# src_name: 子模块中的原类名(如 Attribute)
# local_name: 包命名空间中的名字(如 Attribute,或 `as 别名` 的别名)
# sub_sha1: 子模块(定义文件)sha1
# pkg_sha1: 当前包 __init__ 的 sha1
# trans: 翻译器对象(源 entry 缺失时按需注入子模块 vpi 需要)
# Args: src_name 子模块中的原类名;local_name 包命名空间中的名字(或 as 别名);sub_sha1 子模块(定义文件)sha1;pkg_sha1 当前包 __init__ 的 sha1;trans 翻译器对象(源 entry 缺失时按需注入子模块 vpi 需要)
# ============================================================
def _inject_reexport_class_declare(pool: rowptr[memhub.MemBuddy],
mod: llvmlite.LLVMModule | t.CPtr,
@@ -2662,19 +2508,11 @@ def _vpi_class_is_renum(class_cd: ast.ClassDef | t.CPtr) -> int:
# ============================================================
# _inject_class_struct_declare - 从 vpi ClassDef 注册类为 struct 类型
#
# 背景:vpi stub 注入(ParseVpiIntoModule)此前只注入类内方法 declare,
# 不注册类本身 → 严格模式下(strict_mode:true)同一 vpi 内后续 AnnAssign
# 全局变量的裸类名注解(如 gdt.vp 的 `gdt: t.CArray[gdt_entry, 7]` 元素
# gdt_entry)is_struct_visible=0 → resolve_annotation_type 返回 None →
# TypeGenResolve CArray 元素解析失败 fatal(ViperOS VKernel apic.vp 编译
# 根因)。源模块编译时 ClassGenDef.translate_class_def 会 add_visible_struct
# + register_struct(含字段),vpi 注入缺同一注册 → 此处补齐:
# 1. 已注册(find_struct_by_module 按 name+sha1 命中,含源模块编译时注册)
# → 仅 add_visible_struct 标记当前文件可见
# 2. 未注册 → 解析字段类型 → 创建命名 StructType("sha1.ClassName") →
# register_struct + add_visible_struct
# 字段解析失败不 fatal(不注册该类,留给源模块编译注册;若 vpi 内后续
# 裸类名引用它则由调用侧 fail-fast 暴露)。
# vpi 注入若只注入类内方法 declare 而不注册类本身,则严格模式下同一 vpi 内后续 AnnAssign 的裸类名注解(如 CArray 的元素 gdt_entry)is_struct_visible=0
# → resolve_annotation_type 返回 None → CArray 元素解析失败 fatal。故此处补齐源模块编译时 ClassGenDef.translate_class_def 的注册动作:
# 1) 已注册(find_struct_by_module 按 name+sha1 命中)→ 仅 add_visible_struct 标记当前文件可见;
# 2) 未注册 → 解析字段类型 → 创建命名 StructType("sha1.ClassName") → register_struct + add_visible_struct。
# 字段解析失败不 fatal(不注册该类,留给源模块编译注册,后续裸类名引用由调用侧 fail-fast 暴露)。
# ============================================================
def _inject_class_struct_declare(pool: rowptr[memhub.MemBuddy],
mod: llvmlite.LLVMModule | t.CPtr,
@@ -2734,13 +2572,8 @@ def _inject_class_struct_declare(pool: rowptr[memhub.MemBuddy],
field_names_buf[field_count] = sizet(m_tgt_c.id)
field_types_buf[field_count] = sizet(field_ty_c)
field_count += 1
# 3. vpi 存根不含类字段(只序列化 class 名 + def 签名,见 _cache_vpi_return_
# annots_text 的 class/def 行解析)→ field_count 恒为 0。
# 注册空类(FieldCount=0)会让源模块编译时 find_struct_by_module 命中空类 →
# "结构体已注册但字段数为0" InternalCompilerError(win32base.SECURITY_ATTRIBUTES
# 根因:fileio/subprocess 先编译时注入空类,win32base 后编译命中)。
# 改为不注册:依赖模块已编译时命中真实类(上方 L1604-1608 已处理),未编译时
# 由 _deps_pending 多遍收敛(依赖编译后真实类注册,字段访问/裸类名引用走 pending)。
# 3. vpi 存根不含类字段(只序列化 class 名 + def 签名)→ field_count 恒为 0;注册空类会让源模块编译时 find_struct_by_module 命中空类
# → "结构体已注册但字段数为0" InternalCompilerError。故改为不注册:依赖模块已编译时命中真实类(上方已处理),未编译时由 _deps_pending 多遍收敛。
if field_count == 0:
return 0
# 4. 构建 ParamNode 链表
@@ -2801,12 +2634,8 @@ def _inject_class_struct_declare(pool: rowptr[memhub.MemBuddy],
# → 注入的跨模块 struct 非 packed(GEP 布局错位)。
if _vpi_class_has_packed(class_cd.decorator_list) != 0:
entry_c.IsPacked = 1
# [FIX] vpi 注入类时同步注册字段到 StructEntry 字段表(名字+类型)。
# 仅设置 Ty(LLVMType 含字段)而字段表空(FieldCount=0)时,使用端
# lookup_field / lookup_field_by_class 按名字查字段失败 → 属性访问走
# _deps_pending → Phase 1 includes 翻译无重译 → 失败版本落盘恒 0
# (fileio.vp File.size 的 li.QuadPart → ret i64 zeroinitializer →
# read_all=0 → 二代 Config 加载失败根因)。
# vpi 注入类时同步注册字段到 StructEntry 字段表(名字+类型):仅设 Ty 而字段表空(FieldCount=0)会让 lookup_field/lookup_field_by_class 按名查字段失败
# → 属性访问走 _deps_pending → includes 无重译 → 失败版本落盘恒 0(如 File.size 的 li.QuadPart 恒 0 → read_all=0)。
# add_field 内部按 StdlibAlloc 复制字段名到 perm_pool,跨 reset 存活。
afi_c: int = 0
for afi_c in range(field_count):
@@ -2819,14 +2648,11 @@ def _inject_class_struct_declare(pool: rowptr[memhub.MemBuddy],
# ============================================================
# _ensure_full_value_types - 跨模块"值类型"引用注册完整定义
# _ensure_full_value_types - 跨模块「值类型」引用注册完整定义
#
# opaque 不足以支撑 llc:值 struct global(@_per_cpu = global %per_cpu_data)、
# [N x opaque] 数组(@gdt = global [7 x %gdt_entry])、值 struct 返回类型
# (declare %Mat4 @Mat4_Identity())均报 "use of undefined type"(值类型大小
# 需已知)。vpi 注入类已 add_field 含字段(完整 Ty),在注册表可查时注册完整
# 定义。指针引用(Ptr(Struct))无需——_scan_type_for_cross_module_refs 的 Ptr
# 分支添加 opaque 即可。
# opaque 不足以支撑 llc:值 struct global(@_per_cpu = global %per_cpu_data)、[N x opaque] 数组(@gdt = global [7 x %gdt_entry])、值 struct 返回类型
# 均报 "use of undefined type"(值类型大小需已知)。vpi 注入类已 add_field 含字段(完整 Ty),在注册表可查时注册完整定义;
# 指针引用(Ptr(Struct))无需——_scan_type_for_cross_module_refs 的 Ptr 分支添加 opaque 即可。
# ============================================================
def _ensure_full_value_types(mod: llvmlite.LLVMModule | t.CPtr,
pool: rowptr[memhub.MemBuddy],
@@ -2855,32 +2681,16 @@ def _ensure_full_value_types(mod: llvmlite.LLVMModule | t.CPtr,
# ============================================================
# _AggregateParamTy - 裸结构体(聚合)形参类型统一判据
#
# 「裸结构体形参」(注解是结构体/类名、无 `| t.CPtr`)的 LLVM 形参类型判据,
# 三处必须同步(同一文字、同一结果):
# 1) 定义侧 FuncGenDef.forward_declare_functions L26x-28x 与
# translate_function_def 的 extern 声明分支 L54x-56x;
# 「裸结构体形参」(注解是结构体/类名、无 `| t.CPtr`)的 LLVM 形参类型判据,三处必须同步(同一文字、同一结果):
# 1) 定义侧 FuncGenDef.forward_declare_functions / translate_function_def 的 extern 声明分支;
# 2) declare 注入侧 _inject_function_declare / _inject_method_declare(本函数);
# 3) 调用侧:per-函数 found_func 走 ExprGenBase.coerce_to_type L491-495
# (指针 → 非指针值 build_load 解引用);跨模块名走
# CallGenLib._coerce_args_to_def_params(按本判据得出的形参类型 coerce)。
# 规则(对齐 TPC:TransPyC/lib/core/Handles/HandlesTypeMerge.py L940-946
# 「仅 IsStruct 吸收 1 层 CPtr」+ LLVMCG/TypeConvert.py L137-143
# 「CStruct → 具名结构体值」):
# * C ABI 外部声明(t.CExtern/t.State,本模块无 define 侧)→ 按值 %Struct:
# 真实被调方在 DLL/CRT,形参按 C ABI 传值(TPC 定义侧 stub 同为按值,见
# TransPyV/output/db613a1bd6a3ca26.stub.ll:346 `%COORD`)。
# 值参数要求具名类型有**完整定义**(llvm-as 实测:opaque/未定义具名类型
# 作值参数 ⇒ "invalid type for function argument";指针形式亦要求具名类型
# 存在 ⇒ "use of undefined type")。判据 = 结构体在注册表可查且非 opaque
# (find_struct_by_type 命中,先例 _ensure_full_value_types L2633;
# opaque 哨兵 fcount<0 见 includes/llvmlite/__module.vp L466-491):
# 命中 → 先注册完整定义(module_add_named_type,同一先例)再按值;
# 真 opaque / 未注册 → 降级 i64:不引用任何具名类型(恒合法),Windows x64
# ABI 小结构体(≤8 字节,如 COORD)按整数寄存器传、位兼容(同原 L2905-2908
# 依据),且与定义侧同样降级 ⇒ 同符号签名仍逐字一致。
# * TPV 内部函数/方法(存在 define 侧)→ Ptr(struct):与定义侧逐字一致
# (FuncGenDef L28x/L64x、ClassGenVTable L744-750「对象即指针」),
# 指针目标只需 opaque 声明(module_ensure_opaque_for_type 先例 L2850)。
# 3) 调用侧:per-函数 found_func 走 ExprGenBase.coerce_to_type(指针 → 非指针值 build_load 解引用);跨模块名走 CallGenLib._coerce_args_to_def_params(按本判据得出的形参类型 coerce)。
# 规则(对齐 TPC「仅 IsStruct 吸收 1 层 CPtr」+「CStruct → 具名结构体值」):
# * C ABI 外部声明(t.CExtern/t.State,本模块无 define 侧)→ 按值 %Struct:真实被调方在 DLL/CRT,形参按 C ABI 传值;
# 值参数要求具名类型有**完整定义**(opaque/未定义具名类型作值参数 ⇒ llc "invalid type for function argument")。判据 = find_struct_by_type 命中且非 opaque:
# 命中 → 先注册完整定义(module_add_named_type)再按值;真 opaque/未注册 → 降级 i64(不引用任何具名类型,恒合法;
# Windows x64 ABI 小结构体(≤8 字节)按整数寄存器传、位兼容,且与定义侧同样降级 ⇒ 同符号签名仍逐字一致)。
# * TPV 内部函数/方法(存在 define 侧)→ Ptr(struct):与定义侧逐字一致,指针目标只需 opaque 声明(module_ensure_opaque_for_type)。
# ============================================================
def _AggregateParamTy(mod: llvmlite.LLVMModule | t.CPtr,
pool: rowptr[memhub.MemBuddy],
@@ -2910,10 +2720,8 @@ def _AggregateParamTy(mod: llvmlite.LLVMModule | t.CPtr,
# ============================================================
# _inject_global_variable_declare - 从 vpi AnnAssign 创建模块级全局变量 declare
#
# 背景:vpi 里模块级全局变量(如 tokens.vp 的 _op_head: OpEntry | t.CPtr = None)
# 需在当前模块创建 external global 声明(短名 @name,COFF 按名链接),否则
# 跨模块 from-import 读取全局变量时 find_global_in_module 找不到 →
# "跨模块全局变量类型未知(stub 未注入)" 误杀(lexer.vp 读 tokens._op_head)。
# vpi 里模块级全局变量(如 tokens.vp 的 _op_head: OpEntry | t.CPtr = None)需在当前模块创建 external global 声明(短名 @name,COFF 按名链接),
# 否则跨模块 from-import 读取全局变量时 find_global_in_module 找不到 → 误杀「跨模块全局变量类型未知(stub 未注入)」。
# ============================================================
def _inject_global_variable_declare(pool: rowptr[memhub.MemBuddy],
mod: llvmlite.LLVMModule | t.CPtr,
@@ -2969,13 +2777,8 @@ def _inject_global_variable_declare(pool: rowptr[memhub.MemBuddy],
gv_ty_v: llvmlite.LLVMType | t.CPtr = resolved_v
match resolved_v:
case llvmlite.LLVMType.Struct(_, _, _):
# 对象类(@t.Object/@t.CVTable:对象即指针,HasNew/HasVTable/IsOOP)
# → Ptr(struct),与定义端 handle_module_level_var 一致;否则纯
# t.CStruct 值类(如 pci_device)保持值实例。此前一律转 Ptr 导致
# 跨模块字段访问把实例前 8 字节当指针解引用 → 垃圾地址 #GP
# (uhci.vp pci._uhci_dev.irq 根因);一律保持值则 @t.Object 类
# 模块级变量(如 devfs.vp serial0_file.open)按指针访问读错 vtable
# → 调用跳栈 #PF。
# 对象类(@t.Object/@t.CVTable:对象即指针,HasNew/HasVTable/IsOOP)→ Ptr(struct),与定义端 handle_module_level_var 一致;
# 纯 t.CStruct 值类(如 pci_device)保持值实例——一律转 Ptr 会把实例前 8 字节当指针解引用(#GP),一律保持值则 @t.Object 类模块级变量按指针访问读错 vtable(#PF)。
gv_entry_v: GenTypes.StructEntry | t.CPtr = GenApi.find_struct_by_type(resolved_v)
if gv_entry_v is not None \
and (gv_entry_v.HasNew != 0 or gv_entry_v.HasVTable != 0 or gv_entry_v.IsOOP != 0):
@@ -3234,10 +3037,8 @@ def _inject_function_declare(pool: rowptr[memhub.MemBuddy],
# ============================================================
# _inject_method_declare - 为 ClassDef 内的方法创建 declare
#
# 方法混淆名: {src_sha1}.{ClassName}.{method}
# 参数类型从 vpi 注解解析(self 无注解时默认为 i8*)。
# 注入正确签名避免调用时 _ensure_method_declare 用实参类型
# 创建错误 declare(如 size: sizet 被写成 i32 而非 i64)。
# 方法混淆名 {src_sha1}.{ClassName}.{method};参数类型从 vpi 注解解析(self 无注解时默认为 i8*)。
# 注入正确签名可避免调用时 _ensure_method_declare 用实参类型创建错误 declare(如 size: sizet 被写成 i32 而非 i64)。
# ============================================================
def _inject_method_declare(pool: rowptr[memhub.MemBuddy],
mod: llvmlite.LLVMModule | t.CPtr,
+49 -124
View File
@@ -28,23 +28,11 @@ import lib.Stub.StubMerger as StubMerger
# ============================================================
# 已见符号表(BuildCombinedIR 去重 O(n²)→O(1))
#
# Phase B 对每个源文件调用 BuildCombinedIR 组合 IR:原实现对每行
# declare/global/type 都在不断增长的 out_buf 全量扫描去重
# (_IsFuncDeclaredOrDefined 逐字节 strncmp、strstr 等),依赖最多时
# (CallGenCall)单文件 merge 达 12s。改用定长槽位开放寻址哈希表
# 记录 out_buf 中已活跃的行:去重查找 O(1)、注释行 O(1) 定位。
#
# slot 布局(步长 _SEEN_SLOT):
# {u64 hash} {u64 pos} {u8 flags} {name[_SEEN_NAME_MAX]} {u8 nul}
# 空槽判定:hash==0(memset 全零初始化;FNV-1a 保证 >=2)。
# flags:类型表 bit0=1 表示活跃行为完整定义(= type { / = type <),
# bit0=0 表示仅 opaque 声明;函数/全局表恒为 0。
# pos:活跃行在 out_buf 中的起始位置(注释操作直接 out_buf[pos]=';')。
# 注释行不从表中删除:注释后必然立即追加新行(更新 pos),表保持
# "有活跃行"不变量。超长名(>= _SEEN_NAME_MAX)不登记,防御性回退。
# 表满(count >= cap)fail-fast 报错:静默丢弃登记会致去重失效,
# 重复行追加 → llc redefinition(88410e59e3e5f98b .str 常量根因,
# 该模块合并 IR 实测 4486 个全局行 > 旧容量 4096,故全局表设 16384)。
# 用定长槽位开放寻址哈希表记录 out_buf 中已活跃的 declare/global/type 行,替代全量扫描去重(O(1) 查找与定位)。
# slot 布局(步长 _SEEN_SLOT):{u64 hash} {u64 pos} {u8 flags} {name[_SEEN_NAME_MAX]} {u8 nul};空槽判定 hash==0(memset 全零初始化)。
# flags(仅类型表)bit0=1 表示活跃行为完整定义(= type { / = type <),bit0=0 表示仅 opaque 声明;函数/全局表恒为 0。
# pos 为活跃行在 out_buf 中的起始位置(注释操作直接 out_buf[pos]=';');注释行不从表中删除——注释后必然立即追加新行(更新 pos),表保持「有活跃行」不变量。
# 超长名(>= _SEEN_NAME_MAX)不登记,防御性回退;表满(count >= cap)fail-fast 报错:静默丢弃登记会让去重失效,重复行追加 → llc redefinition。
# ============================================================
_SEEN_NAME_MAX: t.CDefine = 256
_SEEN_SLOT: t.CDefine = 274 # 8(hash)+8(pos)+1(flags)+256(name)+1(nul)
@@ -261,11 +249,9 @@ def _RegisterStubLines(buf: bytes, buf_len: sizet, base_pos: sizet):
continue
# ============================================================
# StubMergerBuild - StubMerger 拆分(阶段 E):StubMergerBuild
# StubMergerBuild - BuildCombinedIR:组合本地 stub + 依赖 stubs + 本地 text
#
# 直接移动模式:函数实现迁至此模块,原 StubMerger.vp 仅保留
# 常量/全局变量;调用方经本模块前缀访问。全局变量跨模块经
# StubMerger._xxx 访问(main.vp 先例)。
# 函数实现直接位于本模块,调用方经本模块前缀访问;全局变量跨模块经 StubMerger._xxx 访问。
# ============================================================
# ============================================================
@@ -285,7 +271,7 @@ def _LoadAndAppendStub(temp_dir: str, td_len: sizet, dep_sha1: str,
dep_path: str = StubMergerCollect._sliced_path(temp_dir, td_len, dep_sha1, "stub.ll")
if dep_path is None:
return out_pos
df_ls: fileio.File | t.CPtr = fileio.File(dep_path, fileio.MODE.R)
df_ls: rowptr[fileio.File] = fileio.File(dep_path, fileio.MODE.R)
if df_ls.closed:
stdlib.free(dep_path)
# 依赖 stub 缺失:includes 映射内的模块缺失属编译错误(fail-fast),
@@ -480,7 +466,7 @@ def _LoadAndAppendTextDeclares(temp_dir: str, td_len: sizet, dep_sha1: str,
text_path: str = StubMergerCollect._sliced_path(temp_dir, td_len, dep_sha1, "text.ll")
if text_path is None:
return out_pos
tf_lt: fileio.File | t.CPtr = fileio.File(text_path, fileio.MODE.R)
tf_lt: rowptr[fileio.File] = fileio.File(text_path, fileio.MODE.R)
if tf_lt.closed:
stdlib.free(text_path)
# 依赖 text 缺失:includes 映射内的模块缺失属编译错误(fail-fast),
@@ -563,13 +549,9 @@ def _LoadAndAppendTextDeclares(temp_dir: str, td_len: sizet, dep_sha1: str,
if dep_buf[decl_end - 1] == '{' and dep_buf[decl_end - 2] == ' ':
decl_end -= 2
# 输出: declare + content(skip "define " 7 chars, up to decl_end)
# 泛型特化方法 define 是 "define linkonce_odr ..."(FunctionPrint
# 对名含 '[' 的函数输出 linkonce_odr):若只跳过 "define " 会拼出
# "declare linkonce_odr ..." → llc 报 "invalid linkage for function
# declaration"(LLVM 只允许 define 用 linkonce_odr,declare 必须
# external)。对齐 TPC Phase2Translator 的 decl_line 剥 linkage 逻辑,
# 循环跳过所有 linkage 词。
# 输出: declare + content(跳过 "define " 及其后所有 linkage 词,至多到 decl_end)。
# 泛型特化方法 define 名为 "define linkonce_odr ..."(名含 '['):只跳 "define " 会拼出 "declare linkonce_odr ..." →
# llc 报 "invalid linkage for function declaration"(declare 只能 external),故循环剥掉全部 linkage 词。
c_start_lt: sizet = ls + 7
skipped_lt: int = 1
while skipped_lt != 0:
@@ -674,10 +656,8 @@ def _LoadAndAppendTextDeclares(temp_dir: str, td_len: sizet, dep_sha1: str,
saved_gv: char = dep_buf[eq_gv]
dep_buf[eq_gv] = '\0'
gh_gv: uint64 = _SeenHash(dep_buf + ls)
# 依赖方已有同名全局(stub 注入的 external 声明或先前依赖的定义)
# 时跳过:跨模块全局(如 _op_head、MemBuddy.vtable)只需一份
# 强定义(在定义模块的 text.ll 中),复制进每个依赖方会导致
# ld.lld duplicate symbol(NewAstTest _op_head/MemBuddy.vtable 根因)。
# 依赖方已有同名全局(stub 注入的 external 声明或先前依赖的定义)时跳过:跨模块全局(如 MemBuddy.vtable)
# 只需一份强定义(在定义模块的 text.ll 中),复制进每个依赖方会导致 ld.lld duplicate symbol。
# 仅当依赖方完全没有该全局(既无 external 也无定义)时才需要补充。
if _SeenPresent(_seen_globals, _SEEN_GLOBAL_CAP, dep_buf + ls, gh_gv) == 0:
# 追加完整定义(无同名行时注释旧行是 no-op)
@@ -722,18 +702,11 @@ def _IsAppModule(mod_name: str) -> int:
# ============================================================
# _AppendDepsToWorklist - 追加空格分隔模块名到 worklist(逐模块去重)
#
# worklist 是 BFS 队列(空格分隔)。deps_str 中的模块若已在 worklist/
# processed 中不重复追加,否则 BFS 沿共享依赖(builtins/stdlib 等)
# 反复累积到 REACH_WORKLIST_BUF 溢出("依赖 worklist 溢出"根因)。
# worklist 是 BFS 队列(空格分隔);deps_str 中的模块若已在 worklist/processed 中则不重复追加,
# 否则 BFS 沿共享依赖(builtins/stdlib 等)反复累积会致 REACH_WORKLIST_BUF 溢出。
#
# Args:
# worklist: 队列缓冲(原地修改)
# wl_len: 当前长度(值)
# deps_str: 要追加的模块名(空格分隔)
# processed: 已处理集合(空格分隔,查重)
#
# Returns:
# 追加后的新长度(>=0);溢出/参数错误返回 -1(调用方 fail-fast)
# Args: worklist 队列缓冲(原地修改);wl_len 当前长度(值);deps_str 要追加的模块名(空格分隔);processed 已处理集合(空格分隔,查重)
# Returns: 追加后的新长度(>=0);溢出/参数错误返回 -1(调用方 fail-fast)
# ============================================================
def _AppendDepsToWorklist(worklist: bytes, wl_len: sizet,
deps_str: str, processed: str) -> int:
@@ -780,32 +753,16 @@ def _AppendDepsToWorklist(worklist: bytes, wl_len: sizet,
stdlib.free(dmod)
return int(cur_len)
# ============================================================
# _BuildReachableSha1Set - 构建可达 SHA1 集合(依赖图按需扫描)
# _BuildReachableSha1Set - 构建可达 SHA1 集合(依赖图按需扫描;未引用的 includes 从不读取)
#
# App 优先模型:先扫 App 源文件的 import 依赖,从 app_deps 出发 BFS,
# 对每个模块名按文件系统解析到 includes 文件(ResolveModuleToPath),
# 只对可达 includes 读文件+算 SHA1+解析 import,补充依赖图并登记
# Sha1Store/deps 存储器/写 deps.txt。未引用的 includes 从不读取。
# Phase 1b/Phase B+ 只处理可达集合中的文件。
# App 优先模型:从 app_deps 出发 BFS,对每个模块名按文件系统解析到 includes 文件(ResolveModuleToPath),只对可达 includes
# 读文件 + 算 SHA1 + 解析 import,补充依赖图并登记 Sha1Store/deps 存储器/写 deps.txt;Phase 1b / Phase B+ 只处理可达集合中的文件。
#
# 算法(工作列表 BFS):
# 1. App 扫描(app_deps 为空时):递归扫 source_dir,declare_only=2 解析
# import(含相对导入 resolve 成绝对模块名)+ 容器注入(_list/_dict)
# → worklist 初始队列;App 文件登记 store(incdir 空)+ 填 deps
# 2. builtins 预加入队列(内置函数模块始终编译)
# 3. BFS:模块名 → ResolveModuleToPath 按文件系统解析 → 读+hash+parse
# import → 登记 reachable_set/store/deps → 追加依赖到队列,直到闭包
# 算法(工作列表 BFS):1) App 扫描(app_deps 为空时)递归扫 source_dir,declare_only=2 解析 import(相对导入 resolve 成绝对模块名)
# + 容器注入(_list/_dict)→ worklist 初始队列,App 文件登记 store(incdir 空)+ 填 deps;2) builtins 预加入队列;3) BFS 直到闭包。
#
# Args:
# mb: 内存池
# source_dir: App 源文件目录(Config.SourceDir)
# temp_dir: 临时目录(deps.txt 所在位置)
# reachable_set: 输出参数,可达 SHA1 集合缓冲区(MAX_INCLUDES_SHA1 * 17 字节)
# app_deps: App 源文件的直接依赖模块名(空格分隔),非空则跳过 App 扫描
# includes_dirs: includes 根目录(空格分隔),依赖图按需解析必须
#
# Returns:
# 可达 SHA1 数量(>0 成功;0 无 includes 依赖;<0 失败)
# Args: mb 内存池;source_dir App 源文件目录(Config.SourceDir);temp_dir 临时目录(deps.txt 所在位置);reachable_set 输出参数,可达 SHA1 集合缓冲区(MAX_INCLUDES_SHA1 * 17 字节);app_deps App 源文件直接依赖模块名(空格分隔),非空则跳过 App 扫描;includes_dirs includes 根目录(空格分隔)
# Returns: 可达 SHA1 数量(>0 成功;0 无 includes 依赖;<0 失败)
# ============================================================
def _BuildReachableSha1Set(mb: rowptr[memhub.MemBuddy], source_dir: str,
temp_dir: str,
@@ -1181,7 +1138,7 @@ def _BuildReachableSha1Set(mb: rowptr[memhub.MemBuddy], source_dir: str,
td_len_w: sizet = string.strlen(temp_dir)
deps_path_w: str = StubMergerCollect._sliced_path(temp_dir, td_len_w, sha1_r2, "deps.txt")
if deps_path_w is not None:
df_w: fileio.File | t.CPtr = fileio.File(deps_path_w, fileio.MODE.W)
df_w: rowptr[fileio.File] = fileio.File(deps_path_w, fileio.MODE.W)
if not df_w.closed:
dl_w: sizet = string.strlen(deps_r2)
if dl_w > 0:
@@ -1234,13 +1191,8 @@ def _BuildReachableSha1Set(mb: rowptr[memhub.MemBuddy], source_dir: str,
# ============================================================
# 类型定义前移(llc: "invalid type for function argument")
#
# 来源:TransPyC/lib/Projectrans/Phase2Translator.py:3056-3081(TPC 同语义实现,
# 注释原文即 llc 的该要求)。触发条件:by-value 结构体参数引用的具名类型必须在
# declare 点之前定义,否则 llc 报 invalid type for function argument。
#
# 与 TPC 的差异(唯一一处):TPC 额外做一次同名去重(seen_type_names_inc)。
# 本侧合并阶段 _seen_types(本文件 L1646-1667)已按类型名保证"活跃定义唯一",
# 重复行不会进入 out_buf,故本遍是纯置换(长度不变),天然幂等且行内容逐字不变。
# by-value 结构体参数引用的具名类型必须在 declare 点之前定义,否则 llc 报 invalid type for function argument。
# 本遍是纯置换(长度不变、行内容逐字不变):合并阶段 _seen_types(本文件 L1646-1667)已按类型名保证「活跃定义唯一」,重复行不会进入 out_buf。
# ============================================================
def _IsTypeDefLine(buf: bytes, ls: sizet, le: sizet) -> int:
"""buf[ls, le)(不含换行)是否为 `%name = type ...` 类型定义行
@@ -1417,7 +1369,7 @@ def BuildCombinedIR(temp_dir: str, local_sha1: str,
stub_path: str = StubMergerCollect._sliced_path(temp_dir, td_len, local_sha1, "stub.ll")
if stub_path is None:
return 0
sf: fileio.File | t.CPtr = fileio.File(stub_path, fileio.MODE.R)
sf: rowptr[fileio.File] = fileio.File(stub_path, fileio.MODE.R)
if sf.closed:
stdlib.free(stub_path)
return 0
@@ -1482,7 +1434,7 @@ def BuildCombinedIR(temp_dir: str, local_sha1: str,
deps_loaded: int = 0
deps_path: str = StubMergerCollect._sliced_path(temp_dir, td_len, local_sha1, "deps.txt")
if deps_path is not None:
df_deps: fileio.File | t.CPtr = fileio.File(deps_path, fileio.MODE.R)
df_deps: rowptr[fileio.File] = fileio.File(deps_path, fileio.MODE.R)
if not df_deps.closed:
deps_buf = stdlib.malloc(4096)
if deps_buf is not None:
@@ -1492,11 +1444,8 @@ def BuildCombinedIR(temp_dir: str, local_sha1: str,
deps_buf[deps_br] = '\0'
deps_loaded = 1
elif deps_br == 0:
# 空 deps.txt:无 import 的包标记文件(如 lib/core/Gen/__init__.vp、
# lib/StubGen/__init__.vp 纯注释)合法——deps_loaded 置 1 视为
# "已加载且无依赖",避免下方"deps.txt 缺失"误报(追加85 根因)。
# deps_buf 是 malloc 未初始化内存,必须显式置空串,
# 否则后续 strlen(deps_buf) 读到垃圾长度遍历越界。
# 空 deps.txt:无 import 的包标记文件(如纯注释的 lib/core/.../__init__.vp)合法——deps_loaded 置 1 视为「已加载且无依赖」,
# 避免下方「deps.txt 缺失」误报;deps_buf 是 malloc 未初始化内存,必须显式置空串,否则后续 strlen 读到垃圾长度遍历越界。
deps_buf[0] = '\0'
deps_loaded = 1
df_deps.close()
@@ -1564,21 +1513,13 @@ def BuildCombinedIR(temp_dir: str, local_sha1: str,
if GenApi.is_module_imported(deps_buf, dep_mod_name) != 0:
should_load = 1
elif StubMergerIR._is_submodule_of_imported(deps_buf, dep_mod_name) != 0:
# 传递性依赖:deps.txt 只记录直接导入(如 ast),
# 但包的 __init__.vp 内部 from .lexer import Lexer
# 会让 IR 引用 ast.lexer 子模块类型。
# 检查 dep_mod_name 是否是已导入包的子模块
# (如 ast.lexer 是 ast 的子模块)
# 传递性依赖:deps.txt 只记录直接导入(如 ast),但包的 __init__.vp 内部 from .lexer import Lexer
# 会让 IR 引用 ast.lexer 子模块类型,故还需检查 dep_mod_name 是否为已导入包的子模块。
should_load = 1
else:
# 基名匹配:store 键是全路径模块名
# (Kernel.drivers.fs.fat32.fat32_types),而 deps.txt
# 记录 import 短名(fat32_types)→ 精确/子模块均不命中
# → 依赖 stub/text 不加载 → 跨模块类完整定义缺失
# (vpi 注入失败的 fat32_types.dirobj 只有 opaque)→
# 值类型 alloca 报 llc "Cannot allocate unsized type"
# (VKernel example_fs L500 根因)。取最后一个 '.' 之后
# 的基名与 deps 词边界匹配(多加载无害,去重表兜底)。
# 基名匹配:store 键是全路径模块名(Kernel.drivers.fs.fat32.fat32_types),而 deps.txt 记录 import 短名(fat32_types)
# → 精确/子模块均不命中 → 依赖 stub/text 不加载 → 跨模块类完整定义缺失(值类型 alloca 报 llc "Cannot allocate unsized type")。
# 故取最后一个 '.' 之后的基名与 deps 词边界匹配(多加载无害,去重表兜底)。
last_dot_bm: str = string.strrchr(dep_mod_name, '.')
if last_dot_bm is not None and last_dot_bm[1] != '\0':
if GenApi.is_module_imported(deps_buf, last_dot_bm + 1) != 0:
@@ -1635,7 +1576,7 @@ def BuildCombinedIR(temp_dir: str, local_sha1: str,
# 检查 stub 文件是否存在
chk_path: str = StubMergerCollect._sliced_path(temp_dir, td_len, found_s, "stub.ll")
if chk_path is not None:
chk_f: fileio.File | t.CPtr = fileio.File(chk_path, fileio.MODE.R)
chk_f: rowptr[fileio.File] = fileio.File(chk_path, fileio.MODE.R)
if chk_f.closed:
VLogger.error(f"依赖模块 '{mod_nm}' (sha1={found_s}) 的 stub 文件不存在: {chk_path},立即终止编译", "BuildCombinedIR")
chk_f.close()
@@ -1646,7 +1587,7 @@ def BuildCombinedIR(temp_dir: str, local_sha1: str,
text_path: str = StubMergerCollect._sliced_path(temp_dir, td_len, local_sha1, "text.ll")
if text_path is None:
return out_pos
tf: fileio.File | t.CPtr = fileio.File(text_path, fileio.MODE.R)
tf: rowptr[fileio.File] = fileio.File(text_path, fileio.MODE.R)
if tf.closed:
# 本地 text.ll 缺失:合并 IR 无法生成定义块,fail-fast 终止
VLogger.error(f"本地 text.ll 缺失: {text_path}", "StubMerger")
@@ -1809,35 +1750,19 @@ def BuildCombinedIR(temp_dir: str, local_sha1: str,
if mod_arr is not None:
stdlib.free(mod_arr)
# 类型定义前移:llc 要求 by-value 结构体参数引用的具名类型在 declare 点之前
# 定义(否则 "invalid type for function argument")。TPC 侧同一遍见
# TransPyC/lib/Projectrans/Phase2Translator.py:3056-3081。
# 落点说明:Phase2 的 write_ir_to_file 是逐字节写本缓冲区(BuildPipeline.vp
# L289-314,无任何文本加工),故在此完成即覆盖 llc 的全部输入,无需在
# Phase2 再复制一份 4MB 缓冲区重排。
# 类型定义前移:llc 要求 by-value 结构体参数引用的具名类型在 declare 点之前定义(否则 "invalid type for function argument")。
# 落点:Phase2 的 write_ir_to_file 逐字节写本缓冲区(BuildPipeline.vp L289-314,无文本加工),故在此完成即覆盖 llc 的全部输入。
out_pos = HoistTypeDefsBeforeDeclares(out_buf, out_pos)
return out_pos
# ============================================================
# LoadDepVpiIntoModule - 加载依赖模块的 .vpi 文件,注入 declare 到当前模块
#
# 遍历 imported_modules(空格分隔的模块名),对每个模块:
# 1. 用 LookupSha1ByModName 获取 SHA1
# 2. 用 _sliced_path 构造 .vpi 文件路径
# 3. 调用 VpiReader.ParseVpiIntoModule 解析 .vpi 并注入 declare
# 遍历 imported_modules(空格分隔的模块名):LookupSha1ByModName 取 SHA1 → _sliced_path 构造 .vpi 路径 → VpiReader.ParseVpiIntoModule 解析并注入 declare。
#
# 在 _translate_module_level 中、Import 处理完成后、translate_children
# 之前调用,确保跨模块函数调用时 declare 已存在。
# 必须在 _translate_module_level 中、Import 处理完成后、translate_children 之前调用,确保跨模块函数调用时 declare 已存在。
#
# Args:
# pool: 内存池
# mod: 当前 LLVM 模块
# imported_modules: 已导入模块名字符串(空格分隔)
# from_imports: from-import 映射字符串("name:module name:module" 格式)
# temp_dir: temp 目录路径
# trans: 翻译器对象
#
# Returns:
# 成功加载的 .vpi 文件数
# Args: pool 内存池;mod 当前 LLVM 模块;imported_modules 已导入模块名字符串(空格分隔);from_imports from-import 映射字符串("name:module name:module" 格式);temp_dir temp 目录路径;trans 翻译器对象
# Returns: 成功加载的 .vpi 文件数
# ============================================================
def LoadDepVpiIntoModule(pool: rowptr[memhub.MemBuddy],
mod: llvmlite.LLVMModule | t.CPtr,
@@ -1855,9 +1780,9 @@ def LoadDepVpiIntoModule(pool: rowptr[memhub.MemBuddy],
# CTypedef 表每文件隔离(translate 前 clear),visited 若跨模块残留会
# 导致 stdint 等 typedef 只注册一次、后续模块注入再次解析时表里为空
# → 参数类型退化 i8_ptr_ty → 整数实参误报 CastError(FuncTest 根因)。
VpiReader.ResetVpiTypedefVisited()
# 按需注入防重入表同周期重置:注入目标 mod 每模块不同,跨模块沿用会让
# 后续模块命中缓存、拿不到本模块需要的 declare(包重导出按需注入用)。
VpiReader.ResetVpiTypedefVisited()
# 按需注入防重入表同周期重置:注入目标 mod 每模块不同,跨模块沿用会让
# 后续模块命中缓存、拿不到本模块需要的 declare(包重导出按需注入用)。
VpiReader.ResetVpiInjectVisited()
td_len: sizet = string.strlen(temp_dir)
@@ -1904,7 +1829,7 @@ def LoadDepVpiIntoModule(pool: rowptr[memhub.MemBuddy],
vpi_path: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1_val, "vpi")
if vpi_path is not None:
# 检查 .vpi 文件是否存在(可读)
vf: fileio.File | t.CPtr = fileio.File(vpi_path, fileio.MODE.R)
vf: rowptr[fileio.File] = fileio.File(vpi_path, fileio.MODE.R)
if not vf.closed:
vf.close()
# 调用 VpiReader 解析 .vpi 并注入 declare
+14 -44
View File
@@ -26,31 +26,18 @@ import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
import lib.Stub.StubMerger as StubMerger
# ============================================================
# StubMergerCollect - StubMerger 拆分(阶段 E):StubMergerCollect
# StubMergerCollect - 切片路径构建与 stub 文件 SHA1 收集
#
# 直接移动模式:函数实现迁至此模块,原 StubMerger.vp 仅保留
# 常量/全局变量;调用方经本模块前缀访问。全局变量跨模块经
# StubMerger._xxx 访问(main.vp 先例)。
# 函数实现直接位于本模块,调用方经本模块前缀访问;全局变量跨模块经 StubMerger._xxx 访问。
# ============================================================
# ============================================================
# _sliced_path - 构建切片路径(stdlib.malloc 分配,调用者负责 free)
# _sliced_path - 构建切片路径(stdlib.malloc 分配,调用者负责 free;level>0 时自动调 BuildPipeline.ensure_dir 建子目录)
#
# 根据 Config.Sha1SliceLevel 将文件分散到 SHA1 前缀子目录:
# level=0 → {temp_dir}/{sha1}.{ext}
# level=1 → {temp_dir}/b7/{sha1}.{ext}
# level=2 → {temp_dir}/b7/90/{sha1}.{ext}
# 按 Config.Sha1SliceLevel 分散到 SHA1 前缀子目录:level=0 → {temp_dir}/{sha1}.{ext};level=1 → {temp_dir}/b7/{sha1}.{ext};level=2 → {temp_dir}/b7/90/{sha1}.{ext}
#
# 自动调用 BuildPipeline.ensure_dir 创建子目录(level>0 时)。
#
# Args:
# temp_dir: 基础目录
# td_len: temp_dir 长度(避免重复 strlen)
# sha1: SHA1 字符串
# ext: 文件扩展名(如 "stub.ll")
#
# Returns:
# 完整路径(stdlib.malloc 分配,需 stdlib.free),None 失败
# Args: temp_dir 基础目录;td_len temp_dir 长度(避免重复 strlen);sha1 SHA1 字符串;ext 文件扩展名(如 "stub.ll")
# Returns: 完整路径(stdlib.malloc 分配,需 stdlib.free),None 失败
# ============================================================
def _sliced_path(temp_dir: str, td_len: sizet, sha1: str, ext: str) -> str:
"""构建切片路径(stdlib.malloc 分配,调用者负责 free)
@@ -94,22 +81,12 @@ def _sliced_path(temp_dir: str, td_len: sizet, sha1: str, ext: str) -> str:
return path
# ============================================================
# _collect_stub_sha1s - 递归扫描子目录,收集 .stub.ll 文件的 SHA1
# _collect_stub_sha1s - 递归扫描子目录,收集 .stub.ll 文件的 SHA1(从文件名提取)
#
# 根据 level 递归扫描子目录:
# level=0 → 扫描 {base_dir}/*.stub.ll,从文件名提取 SHA1
# level>0 → 扫描 {base_dir}/* 的子目录,对每个子目录递归调用
# level=0 → 扫描 {base_dir}/*.stub.ll;level>0 → 扫描 {base_dir}/* 的子目录并对每个子目录递归调用。
#
# Args:
# base_dir: 当前扫描目录
# bd_len: base_dir 长度
# level: 剩余递归层数
# out_arr: 输出数组(每个 SHA1 17 字节 = 16字符 + null)
# out_count: 当前已收集数量
# max_count: 最大数量
#
# Returns:
# 收集后的总数量
# Args: base_dir 当前扫描目录;bd_len base_dir 长度;level 剩余递归层数;out_arr 输出数组(每个 SHA1 17 字节 = 16字符 + null);out_count 当前已收集数量;max_count 最大数量
# Returns: 收集后的总数量
# ============================================================
def _collect_stub_sha1s(base_dir: str, bd_len: sizet, level: int,
out_arr: str, out_count: int, max_count: int) -> int:
@@ -222,18 +199,11 @@ def _is_in_sha1_set(sha1: str, sha1_set: str, set_count: int) -> int:
return 1
return 0
# ============================================================
# WriteIncludesSha1Map - 将 includes 扫描结果写入 _sha1_map.txt(人类可读输出)
# WriteIncludesSha1Map - 将 includes 扫描结果写入 _sha1_map.txt(仅人类可读的调试输出,程序内部不读取,
# 机器分析用 PopulateSha1MapStore 填充的全局内存存储器)
#
# _sha1_map.txt 仅作为人类可读的调试输出,程序内部不读取此文件。
# 机器分析使用 PopulateSha1MapStore 填充的全局内存存储器。
#
# Args:
# mb: 内存池
# temp_dir: 临时目录(_sha1_map.txt 写入位置)
# scan_result: IncludesScanner 扫描结果
#
# Returns:
# 0 成功,非 0 失败
# Args: mb 内存池;temp_dir 临时目录(_sha1_map.txt 写入位置);scan_result IncludesScanner 扫描结果
# Returns: 0 成功,非 0 失败
# ============================================================
def WriteIncludesSha1Map(mb: rowptr[memhub.MemBuddy], temp_dir: str,
scan_result: rowptr[IncludesScanner.ScanResult],
+16 -49
View File
@@ -27,19 +27,14 @@ import lib.Stub.StubMerger as StubMerger
# ============================================================
# StubMergerIR - StubMerger 拆分(阶段 E):StubMergerIR
# StubMergerIR - IR 文本去重/注释与模块名-SHA1 映射工具
# ============================================================
# ============================================================
# IsDeclOnlyModule - 判断模块是否为纯声明文件
# IsDeclOnlyModule - 判断模块是否为纯声明文件(读 {temp_dir}/{sha1}.text.ll 扫描是否含 "define " 行)
#
# 读取 {temp_dir}/{sha1}.text.ll,扫描是否含 "define " 行:
# 返回 1 = 纯声明(无函数体,如 stdio.vp 全 pass),无需 .obj
# 返回 0 = 实现文件(含 define 函数体)
# 返回 -1 = text.ll 不存在/无法判断
#
# 与 Phase B+(Phase2.vp 编译缺失 includes 时)的内联判断逻辑一致,
# 供 Phase C 链接时区分"声明文件无需 .obj"与"实现文件缺失"。
# 返回 1 = 纯声明(无函数体,如全 pass 的模块,无需 .obj);0 = 实现文件(含 define 函数体);-1 = text.ll 不存在/无法判断。
# 与 Phase B+ 编译缺失 includes 时的内联判断逻辑一致,供 Phase C 链接时区分「声明文件无需 .obj」与「实现文件缺失」。
# ============================================================
def IsDeclOnlyModule(temp_dir: str, td_len: sizet, sha1: str) -> int:
"""判断模块是否为纯声明文件(text.ll 无 define 行)"""
@@ -84,16 +79,11 @@ def IsDeclOnlyModule(temp_dir: str, td_len: sizet, sha1: str) -> int:
# ============================================================
# _IsFuncDeclaredOrDefined - 检查 out_buf 中是否已有函数的 declare/define
#
# 逐字节strncmp搜索 func_sig(如 @"sha1.func"(/ @printf( ),每次
# 找到匹配后检查行首是否以 "declare " 或 "define " 开头,跳过 call 语句。
# 避免 strstr 子串匹配误匹配 call ... @"func"(...) 中的 @"func"( 子串。
# 逐字节 strncmp 搜索 func_sig(如 @"sha1.func"(/ @printf(),命中后检查行首是否为 "declare "/"define ",跳过 call 语句
# (避免 strstr 子串匹配误命中 call ... @"func"(...)。
#
# Args:
# out_buf: 已有的 IR 文本缓冲区
# func_sig: 函数签名片段,如 @"sha1.func"(/ @printf(
#
# Returns:
# 1=已存在 declare/define, 0=不存在
# Args: out_buf 已有 IR 文本缓冲区;func_sig 函数签名片段,如 @"sha1.func"(/ @printf(
# Returns: 1=已存在 declare/define, 0=不存在
# ============================================================
def _IsFuncDeclaredOrDefined(out_buf: bytes, func_sig: str) -> int:
"""检查 out_buf 中是否已有函数的 declare 或 define 行"""
@@ -237,15 +227,8 @@ def _CommentOutOpaqueTypeInBuf(out_buf: bytes, type_name_prefix: str) -> int:
# ============================================================
# _is_submodule_of_imported - 检查模块名是否是已导入包的子模块
#
# deps.txt 只记录直接导入的模块名(如 "ast"),但包的 __init__.vp
# 内部 from .lexer import Lexer 会让 IR 引用 ast.lexer 子模块类型。
# 当模块名包含 '.' 时,检查其顶层包名是否在 deps_buf 中。
#
# 例如:
# deps_buf = "ast stdio string"
# mod_name = "ast.lexer" → 顶层包 "ast" 在 deps_buf 中 → 返回 1
# mod_name = "ast.token" → 顶层包 "ast" 在 deps_buf 中 → 返回 1
# mod_name = "foo.bar" → 顶层包 "foo" 不在 deps_buf 中 → 返回 0
# deps_buf 只记录直接导入的模块名(如 "ast"),但包的 __init__.vp 内部 from .lexer import Lexer 会让 IR 引用 ast.lexer
# 子模块类型;模块名含 '.' 时检查其顶层包名是否在 deps_buf 中(deps_buf = "ast stdio string" 时 "ast.lexer" → 1,"foo.bar" → 0)。
# ============================================================
def _is_submodule_of_imported(deps_buf: str, mod_name: str) -> int:
"""检查 mod_name 是否是 deps_buf 中已导入包的子模块"""
@@ -336,19 +319,10 @@ def _BuildIncludesSha1Map(temp_dir: str, td_len: sizet,
count += 1
return count
# ============================================================
# _FindSha1ByModName - 从模块名查找 SHA1(精确匹配)
# _FindSha1ByModName - 从模块名查找 SHA1(精确匹配;供依赖图分析「模块名 → SHA1」)
#
# 在 sha1_arr/mod_arr 映射中查找模块名对应的 SHA1。
# 用于依赖图分析:模块名 → SHA1。
#
# Args:
# sha1_arr: SHA1 数组(每个 17 字节)
# mod_arr: 模块名数组(每个 64 字节)
# count: 映射条目数
# mod_name: 要查找的模块名
#
# Returns:
# SHA1 字符串指针(指向 sha1_arr 内部,无需释放),未找到返回 None
# Args: sha1_arr SHA1 数组(每个 17 字节);mod_arr 模块名数组(每个 64 字节);count 映射条目数;mod_name 要查找的模块名
# Returns: SHA1 字符串指针(指向 sha1_arr 内部,无需释放),未找到返回 None
# ============================================================
def _FindSha1ByModName(sha1_arr: str, mod_arr: str,
count: int, mod_name: str) -> str:
@@ -383,17 +357,10 @@ def _FindSha1ByModName(sha1_arr: str, mod_arr: str,
stdlib.free(init_buf)
return None
# ============================================================
# _AddSha1ToSet - 将 SHA1 加入集合(去重)
# _AddSha1ToSet - 将 SHA1 加入集合(去重;已在集合中则 count 不变)
#
# 检查 SHA1 是否已在集合中,若不在则加入。返回新的 count。
#
# Args:
# set_buf: SHA1 集合缓冲区(每个 17 字节)
# count: 当前集合中的 SHA1 数量
# sha1: 要加入的 SHA1
#
# Returns:
# 新的 count(如果已存在则不变)
# Args: set_buf SHA1 集合缓冲区(每个 17 字节);count 当前集合中的 SHA1 数量;sha1 要加入的 SHA1
# Returns: 新的 count
# ============================================================
def _AddSha1ToSet(set_buf: str, count: int, sha1: str) -> int:
"""将 SHA1 加入集合,返回新 count"""
+30 -81
View File
@@ -26,21 +26,15 @@ import lib.Stub.StubMerger as StubMerger
# ============================================================
# StubMergerStore - StubMerger 拆分(阶段 E):StubMergerStore
# StubMergerStore - 全局 SHA1 映射存储器(sha1→模块名/相对路径)读写
# ============================================================
# ============================================================
# PopulateSha1MapStore - 从扫描结果填充全局 SHA1 映射存储器
# PopulateSha1MapStore - 遍历 ScanResult.Entries 构建 sha1→module_name 映射,填充全局存储器
#
# 遍历 ScanResult.Entries,为每个条目构建 sha1→module_name 映射。
# 模块名通过 _PathToModuleName(includes/rel_path) 计算。
# 内存用 stdlib.malloc 分配(全局存储器,不随 mbuddy 释放)。
#
# Args:
# scan_result: IncludesScanner 扫描结果
#
# Returns:
# 填充的条目数,-1 表示失败
# 模块名通过 _PathToModuleName(includes/rel_path) 计算;内存用 stdlib.malloc 分配(全局存储器,不随 mbuddy 释放)。
# Args: scan_result IncludesScanner 扫描结果
# Returns: 填充的条目数,-1 表示失败
# ============================================================
def PopulateSha1MapStore(scan_result: IncludesScanner.ScanResult | t.CPtr) -> int:
"""从扫描结果填充全局 SHA1 映射存储器
@@ -122,24 +116,12 @@ def PopulateSha1MapStore(scan_result: IncludesScanner.ScanResult | t.CPtr) -> in
VLogger.error(f"includes SHA1 存储有 {p_bad} 个条目失败", "StubMerger")
return StubMerger._sha1_store_count
# ============================================================
# AppendToSha1MapStore - 追加条目到全局 SHA1 映射存储器
# AppendToSha1MapStore - 追加 App 模块条目到全局 SHA1 映射存储器(Phase1 先用 PopulateSha1MapStore 填 includes,
# Phase2 扫描 App 源文件后调用本函数追加,否则编译 App 自身时跨模块 CDefine 查找命中不到 App 模块)
#
# Phase1 调用 PopulateSha1MapStore 填充 includes 文件,
# Phase2 扫描 App 源文件后调用此函数追加 App 模块条目,
# 确保编译 App 自身时跨模块 CDefine 查找能命中 App 模块
# (如 StubMerger.StubMerger.MAX_INCLUDES 在编译 Phase2 时通过
# _build_source_path_from_sha1 查找 StubMerger 的源路径)。
#
# 若存储器未初始化(PopulateSha1MapStore 未调用),自动初始化。
# 若 SHA1 已存在(去重),跳过。
#
# Args:
# sha1: SHA1 字符串(16 字符 + null)
# rel_path: 相对路径(如 "lib/core/StubMerger.vp",
# 不含 includes/ 前缀,与 _sha1_map.txt 格式一致)
#
# Returns:
# 0 成功(含去重跳过),1 失败
# 存储器未初始化时自动初始化;SHA1 已存在(去重)则跳过。
# Args: sha1 SHA1 字符串(16 字符 + null);rel_path 相对路径(如 "lib/core/StubMerger.vp",不含 includes/ 前缀,与 _sha1_map.txt 格式一致)
# Returns: 0 成功(含去重跳过),1 失败
# ============================================================
def AppendToSha1MapStore(sha1: str, rel_path: str) -> int:
"""追加条目到全局 SHA1 映射存储器(Phase2 用)"""
@@ -169,11 +151,8 @@ def AppendToSha1MapStore(sha1: str, rel_path: str) -> int:
stdlib.free(full_path_buf)
if mod_name is None:
return 1
# 去重:SHA1 与模块名都相同才跳过。同一 SHA1 可能被 includes 扫描先以
# 全路径模块名注册(如 App 源文件在 includes 目录下 → "Apps.Scene3D.gfx"),
# App 追加的短模块名("gfx")必须保留——否则 import gfx 的精确查找
# LookupSha1ByModName("gfx") 失败 → 后缀匹配劫持到 VKernel 同名 gfx
# (VKernel/Kernel/drivers/video/vesafb/gfx.vp,类 Scene3D 归属错乱根因)。
# 去重:SHA1 与模块名都相同才跳过——同一 SHA1 可能先以全路径模块名注册(App 源文件在 includes 目录下),
# App 追加的短模块名必须保留,否则 import 该模块的精确查找失败、后缀匹配会劫持到其它同名模块。
for i in range(StubMerger._sha1_store_count):
sidx_d: sizet = sizet(i) * 17
if string.strcmp(StubMerger._sha1_store_arr + sidx_d, sha1) == 0:
@@ -209,19 +188,10 @@ def AppendToSha1MapStore(sha1: str, rel_path: str) -> int:
# ============================================================
# AddIncludeToSha1MapStore - 单条登记 includes 条目到全局 SHA1 映射存储器
#
# 依赖图按需扫描(_BuildReachableSha1Set)解析到可达 includes 文件后
# 逐条登记,替代 PopulateSha1MapStore 的全量登记——未引用的 includes
# 不进 store。inc_dir 非空(所属 includes 根目录,多 includes 支持);
# App 源文件仍用 AppendToSha1MapStore(incdir 留空)。
#
# Args:
# sha1: SHA1 字符串(16 字符 + null)
# rel_path: 相对 includes 根目录的路径(如 "ast/parser.vp")
# inc_dir: 所属 includes 根目录
# mod_name: 模块名(如 "ast.parser")
#
# Returns:
# 0 成功(含去重跳过),1 失败
# 依赖图按需扫描(_BuildReachableSha1Set)解析到可达 includes 文件后逐条登记,未引用的 includes 不进 store;
# App 源文件仍用 AppendToSha1MapStore(inc_dir 留空)。
# Args: sha1 SHA1 字符串(16 字符 + null);rel_path 相对 includes 根目录的路径(如 "ast/parser.vp");inc_dir 所属 includes 根目录;mod_name 模块名(如 "ast.parser")
# Returns: 0 成功(含去重跳过),1 失败
# ============================================================
def AddIncludeToSha1MapStore(sha1: str, rel_path: str, inc_dir: str, mod_name: str) -> int:
"""单条登记 includes 条目到全局 SHA1 映射存储器(依赖图按需扫描用)"""
@@ -300,14 +270,10 @@ def LookupSha1ByModName(mod_name: str) -> str:
"""从模块名查找 SHA1(精确匹配 + 包名回退),委托通用实现 _FindSha1ByModName"""
return StubMergerIR._FindSha1ByModName(StubMerger._sha1_store_arr, StubMerger._sha1_store_mod, StubMerger._sha1_store_count, mod_name)
# ============================================================
# LookupSha1ByModNameSuffix - 后缀匹配查找模块 SHA1
# LookupSha1ByModNameSuffix - 后缀匹配查找模块 SHA1(用于 from .X import Y:impf.module 是 ".X",而映射表键是 "pkg.X")
#
# 用于 from .X import Y 的相对导入:impf.module 是 ".X"(带前导点),
# 去掉前导点后为 "X",SHA1 映射表中键是 "pkg.X",需要后缀匹配。
# 包含三种匹配方式:精确、后缀、反向后缀。
#
# Returns:
# SHA1 字符串指针(指向全局存储器内部,无需释放),None 未找到
# Returns: SHA1 字符串指针(指向全局存储器内部,无需释放),None 未找到
# ============================================================
def LookupSha1ByModNameSuffix(mod_suffix: str) -> str:
"""后缀匹配查找模块 SHA1"""
@@ -350,12 +316,9 @@ def LookupSha1ByModNameSuffix(mod_suffix: str) -> str:
# ============================================================
# LookupSha1ByAlias - 从 from_imports 别名映射解析模块 SHA1
#
# from_imports 条目格式: "local:module" 或 "local:module:orig"(空格分隔),
# 由 ImportsGenMixin.add_from_import 写入(import X as Y → "Y:X")。
# 命中 local==alias 时,对 module 执行 LookupSha1ByModName 返回 SHA1。
#
# Returns:
# SHA1 字符串指针(指向全局存储器内部),None 未找到
# from_imports 条目格式 "local:module" 或 "local:module:orig"(空格分隔,由 ImportsGenMixin.add_from_import 写入);
# 命中 local==alias 时对 module 执行 LookupSha1ByModName。
# Returns: SHA1 字符串指针(指向全局存储器内部),None 未找到
# ============================================================
def LookupSha1ByAlias(from_imports: str, alias: str) -> str:
"""从 from_imports 别名映射解析模块 SHA1(改名别名 import X as Y 场景)"""
@@ -408,12 +371,9 @@ def LookupSha1ByAlias(from_imports: str, alias: str) -> str:
# ============================================================
# LookupModNameByAlias - 从 from_imports 别名映射解析模块真名
#
# import X as Y 时 ImportsGenMixin.add_from_import 写入 "Y:X"(Y=别名,
# X=模块真名)。本函数返回别名 Y 对应的模块真名 X(供限定名解析
# "Y.Class" → 按 X 模块归属匹配,如 "types.fileinfo" → "fat32_types")。
#
# Returns:
# 模块名字符串(stdlib 分配,调用方负责 free),None 未找到
# import X as Y 时 ImportsGenMixin.add_from_import 写入 "Y:X"(Y=别名,X=模块真名);
# 本函数返回别名 Y 对应的模块真名 X(供限定名解析 "Y.Class" → 按 X 模块归属匹配)。
# Returns: 模块名字符串(stdlib 分配,调用方负责 free),None 未找到
# ============================================================
def LookupModNameByAlias(from_imports: str, alias: str) -> str:
"""从 from_imports 别名映射解析模块真名(import X as Y → Y:X)"""
@@ -464,15 +424,9 @@ def LookupModNameByAlias(from_imports: str, alias: str) -> str:
# ============================================================
# PopulateIncludesDeps - 将 includes 文件的依赖存入内存
#
# Phase B+ 前用 declare_only 翻译 includes 文件,提取 _imported_modules
# 后调用此函数存入内存。_BuildReachableSha1Set 从内存读取,不读 deps.txt。
#
# Args:
# sha1: includes 文件的 SHA1(16字符)
# deps_str: 依赖模块名字符串(空格分隔,如 "t c stdint string")
#
# Returns:
# 0 成功,非 0 失败
# Phase B+ 前用 declare_only 翻译 includes 文件、提取 _imported_modules 后调用本函数存入内存;_BuildReachableSha1Set 从内存读取,不读 deps.txt。
# Args: sha1 includes 文件的 SHA1(16字符);deps_str 依赖模块名字符串(空格分隔,如 "t c stdint string")
# Returns: 0 成功,非 0 失败
# ============================================================
def PopulateIncludesDeps(sha1: str, deps_str: str) -> int:
"""将 includes 文件的依赖存入内存"""
@@ -574,14 +528,9 @@ def GetSha1StoreArr(out_sha1_arr: t.CPtr, out_mod_arr: t.CPtr, out_rel_arr: t.CP
# ============================================================
# LookupSha1RelPath - 通过 SHA1 查找相对路径
#
# 遍历全局存储器,返回 sha1 对应的相对路径指针(只读,不要 free)。
# 用于错误提示:将 sha1 转换为人类可读的文件路径。
#
# Args:
# sha1: SHA1 字符串(16 字符)
#
# Returns:
# 相对路径字符串指针(只读),None=未找到
# 遍历全局存储器,返回 sha1 对应的相对路径指针(只读,不要 free),用于把 sha1 转成人类可读的文件路径。
# Args: sha1 SHA1 字符串(16 字符)
# Returns: 相对路径字符串指针(只读),None=未找到
# ============================================================
def LookupSha1RelPath(sha1: str) -> str:
"""通过 SHA1 查找相对路径,返回只读指针,None=未找到"""
+9 -23
View File
@@ -26,33 +26,20 @@ import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
import lib.Stub.StubMerger as StubMerger
# ============================================================
# StubMergerTopo - StubMerger 拆分(阶段 E):StubMergerTopo
# StubMergerTopo - 对可达 includes 做逆拓扑排序(被依赖者先编译)
#
# 直接移动模式:函数实现迁至此模块,原 StubMerger.vp 仅保留
# 常量/全局变量;调用方经本模块前缀访问。全局变量跨模块经
# StubMerger._xxx 访问(main.vp 先例)。
# 函数实现直接位于本模块,调用方经本模块前缀访问;全局变量跨模块经 StubMerger._xxx 访问。
# ============================================================
# ============================================================
# BuildTopoSortedIndices - 对可达 includes 逆拓扑排序(被依赖者先)
#
# 背景:跨模块 vtable 生成要求被依赖模块先完整翻译(如 astaux.vp 继承
# base.vp 的 AST,vtable 槽位需 AST.accept 的 TypeStr 已注册到全局表)。
# Phase B+ 原先按 store 扫描顺序编译,依赖顺序不保证 → fail-fast 暴露。
# 跨模块 vtable 生成要求被依赖模块先完整翻译(子类 vtable 槽位需父类方法的类型已注册到全局表)。
# 算法:Kahn 逆拓扑。边 m→d 表示 m 依赖 d(d 在 m 的 deps 中);入度为 0(不依赖任何可达模块)者先输出,
# 输出后对依赖它的模块减入度,入度归 0 者入队。reachable_count <= 0 时对全部 store 排序(与既有全量编译行为一致)。
#
# 算法:Kahn 逆拓扑。边 m→d 表示 m 依赖 d(d 在 m 的 deps 中)。
# indegree[m] = m 在可达集合内的直接依赖数;
# 入度为 0(不依赖任何可达模块)者先输出,输出后对依赖它的模块
# 减入度,入度归 0 者入队。结果即"被依赖者先编译"。
# reachable_count <= 0 时对全部 store 排序(与既有全量编译行为一致)。
#
# Args:
# store_count / store_sha1_arr / store_mod_arr: store 元数据
# reachable_set / reachable_count: 可达集合(_BuildReachableSha1Set 输出)
# sorted_idx: 输出数组(store_count+1 个 8 字节槽),前 N 个为拓扑序 store 索引
#
# Returns:
# 拓扑序条数;有环时剩余条目按 store 顺序补齐(避免漏编译)
# Args: store_count / store_sha1_arr / store_mod_arr store 元数据;reachable_set / reachable_count 可达集合(_BuildReachableSha1Set 输出);sorted_idx 输出数组(store_count+1 个 8 字节槽),前 N 个为拓扑序 store 索引
# Returns: 拓扑序条数;有环时剩余条目按 store 顺序补齐(避免漏编译)
# ============================================================
def BuildTopoSortedIndices(store_count: int, store_sha1_arr: bytes,
store_mod_arr: bytes, reachable_set: bytes,
@@ -130,9 +117,8 @@ def BuildTopoSortedIndices(store_count: int, store_sha1_arr: bytes,
queue[q_tail] = si2
q_tail += 1
out_count: int = 0
# Kahn 主循环必须用 while(不能用 for range):循环体内会动态入队
# (queue[q_tail]=si3; q_tail+=1),for range 上界只求值一次,
# 动态入队的模块永远不会被处理且兜底(indegree>0)也捞不回 → 拓扑漏项
# Kahn 主循环必须用 while 而非 for range:循环体内会动态入队(for range 上界只求值一次,
# 动态入队的模块永远不会被处理,且 indegree>0 兜底也捞不回 → 拓扑漏项)
q_head: int = 0
while q_head < q_tail:
m: int = queue[q_head]
+5 -14
View File
@@ -20,17 +20,10 @@ import lib.Projectrans.Config as Config
# ============================================================
# StubMerger - stub 声明合并模块
# StubMerger - stub 声明合并模块(Phase2 核心)
#
# 从 temp 目录加载所有 .stub.ll 文件,将声明内容合并到主 IR。
# 这是 Phase2 的核心功能:翻译主文件时,将所有依赖的 stub
# 声明合并到主 IR 前面,使链接器能解析跨模块符号引用。
#
# 合并策略:
# 1. 扫描 temp 目录,收集所有 {sha1}.stub.ll 文件
# 2. 读取每个 stub 文件内容
# 3. 过滤去重(避免重复声明)
# 4. 将声明插入到主 IR 的 target triple 之后
# 从 temp 目录加载所有 .stub.ll,把依赖模块的声明合并到主 IR 的 target triple 之后,使链接器能解析跨模块符号引用。
# 合并策略:扫描 temp 目录收集 {sha1}.stub.ll → 读取内容 → 过滤去重(避免重复声明)→ 插入主 IR。
# ============================================================
# 全局 mbuddy 指针
@@ -63,10 +56,8 @@ _includes_deps_sha1: bytes # SHA1 数组(每个 17 字节,MAX_INCLUDES 个
_includes_deps_str: bytes # 依赖字符串数组(每个 512 字节,MAX_INCLUDES 个)
_includes_deps_count: int # 条目数
# BuildCombinedIR 全目录扫描缓存:Phase B 对每个源文件调用一次 BuildCombinedIR,
# 每次 _collect_stub_sha1s 递归扫描 temp_dir 全部切片子目录(O(N×S) 目录枚举,
# 90 文件 × 全目录是 Phase B 慢的根因之一)。按 temp_dir 缓存扫描结果,
# 首次扫描后复用,temp_dir 变化时自动重建。
# BuildCombinedIR 全目录扫描缓存:Phase B 每个源文件调用一次 BuildCombinedIR,而 _collect_stub_sha1s 会递归枚举
# temp_dir 全部切片子目录(O(N×S) 目录枚举很慢);按 temp_dir 缓存扫描结果,首次扫描后复用,temp_dir 变化时自动重建。
_stub_scan_cache_dir: str # 缓存对应的 temp_dir(None=未初始化)
_stub_scan_cache_arr: str # 扫描结果数组(每 17 字节一个 SHA1,MAX_INCLUDES_SHA1 个)
_stub_scan_cache_count: int # 收集数量
+2 -13
View File
@@ -11,25 +11,14 @@ class CompilerFactory:
Pool: rowptr[memhub.MemBuddy] # 主内存池(瞬时数据,mark/reset 回收)
PermPool: rowptr[memhub.MemBuddy] # 永久内存池(持久数据,从不 reset)
# ---- REnum 变体注册表(编译期单例持有,跨 Generator/Phase 存活)----
# [D30 根治] 此前这 4 张表是 ClassGenBase 的**实例字段**,被 InitClassGen
# 在每个 Generator 构造时清零;而 struct entry 在 perm_pool 跨 Phase 存活。
# 两者寿命不对称 ⇒ 新 Generator 建好后表被清空、struct 仍在 ⇒
# _translate_renum_def 命中 existing 早退 → 变体表永久缺该类 →
# _is_renum_class_name 恒 0 → match 走 _handle_simple_match(无变体绑定)→
# case 绑定名 NameError(includes/llvmlite/__types.vp L407 `bits` 根因)。
# 表内存本就是 perm_pool 分配(设计上就是持久的),故把**指针与计数**
# 一并挂到编译期单例 factory 上,与 struct entry 同寿命。
# ---- REnum 变体注册表(编译期单例持有,与 perm_pool 的 struct entry 同寿命)----
RenumClassNames: sizet | t.CPtr
RenumClassVariantCounts: sizet | t.CPtr
RenumVariantNames: sizet | t.CPtr
RenumVariantTags: sizet | t.CPtr
RenumClassCount: int
# ---- REnum 变体字段别名表(同上,必须同寿命)----
# _bind_variant_fields 靠 _lookup_renum_member_ty 查这些表拿到字段真实类型;
# 表被清 → 类型槽 0 → 绑定回退 payload 槽位 i64 → 指针字段被当整数读 →
# 递归传参/赋值 int→ptr 被拦(llvmlite __types.vp L448 CastError 根因)。
# ---- REnum 变体字段别名表(同上,必须同寿命;_bind_variant_fields 查字段真实类型)----
RenumAliasClass: sizet | t.CPtr
RenumAliasField: sizet | t.CPtr
RenumAliasIndex: sizet | t.CPtr
+143 -215
View File
@@ -199,16 +199,9 @@ class AssignGenMixin:
val_fp: int64 = self.extract_cdefine_int_value(val_node)
self.Gen.TypeGenCDef.register_cdefine_constant(pool, name, val_fp)
# ============================================================
# 源文件重读恢复 CDefine 浮点值
#
# 背景:AST 节点的 float_val/str_val 字段可能被 pool 分配操作覆写
# (MemBuddy 内存冲突),导致注册的浮点 CDefine 值错误(llc ret i32 0)。
# 本函数直接读源文件找 "NAME: t.CDefine = value" 行,atof 解析 value——
# 源文件文本不受 pool 影响,是最可靠的恢复路径。
#
# 原模块级 _g_recover_found 全局已迁移为实例字段 self.RecoverFound。
# ============================================================
# 源文件重读恢复 CDefine 浮点值:读 "NAME: t.CDefine = value" 行 atof 解析
# (AST 节点 float_val/str_val 可能被 pool 分配覆写,源文件文本最可靠)。
# 是否命中记在实例字段 self.RecoverFound。
def _recover_found(self) -> int:
"""检查最近一次 _recover_cdefine_float 是否成功"""
@@ -227,7 +220,7 @@ class AssignGenMixin:
fp: str = self.Gen.TypeGenResolve.get_current_file()
if fp is None or name is None or out is None:
return 0
f: fileio.File | t.CPtr = fileio.File(fp, fileio.MODE.R)
f: rowptr[fileio.File] = fileio.File(fp, fileio.MODE.R)
if f.closed:
return 0
BUF_SZ: sizet = 1048576
@@ -297,12 +290,9 @@ class AssignGenMixin:
eq += 1
while eq < line_start + line_len and buf[eq] == ' ':
eq += 1
# Call 包装的浮点常量(如 PI: t.CDefine = float(3.14159...) /
# t.CDouble(1.0)):值的 token 是整段 "float(3.14159...)"。
# atof 只认数字/符号/'.',首字符 'f' → 恒得 0.0,导致浮点 CDefine
# 被注册为 0(gfx.vp PI 编成 0.0 的直接根因)。此处跳过 "float("
# 之类前缀,从括号内首个数字/符号/'.' 起取值。
# 裸字面量(3.14 / -1.5,首字符已是数字/符号/'.')不受影响。
# Call 包装的浮点常量(t.CDouble(1.0) / float(3.14...)):跳过 "float(" 等
# 前缀,从括号内首个数字/符号/'.' 起取值(否则 atof 只认数字恒得 0.0)。
# 裸字面量(3.14 / -1.5)首字符已是数字/符号/'.',不受影响。
if eq < line_start + line_len:
c0_rf: char = buf[eq]
if not ((c0_rf >= '0' and c0_rf <= '9') or c0_rf == '-' or c0_rf == '+' or c0_rf == '.'):
@@ -522,12 +512,9 @@ class AssignGenMixin:
"""
if ty_name is None:
return 0
# [FIX 2026-08-31] 前缀归一化:剥离 "t."/"c." 内建命名空间前缀,使
# "int" 与裸名 "CInt" 判定一致。泛型实参经 _norm_type_arg_name 规则 3
# 保留 "t." 前缀(list[int] 的 T → "int"),不剥离则 _is_scalar_ty_name
# 匹配不到白名单裸名 "CInt" → 误判为非标量 → _is_ptr_element_annotation 返回
# 1(8 字节指针槽)→ _list.vp elem_ptr[0]=item 的 GEP 元素类型退化为指针 →
# 整数存指针误报 CastError(ast/base.vp L391 list[int] 根因)。
# 前缀归一化:剥离 "t."/"c." 内建命名空间前缀,使 "int" 与裸名 "CInt" 判定
# 一致(泛型实参经 _norm_type_arg_name 规则 3 会保留 "t.")。不剥离则
# 误判为非标量 → 8 字节指针槽 → GEP 元素类型退化为指针(整数存指针报 CastError)。
if ty_name[0] == 't' and ty_name[1] == '.':
ty_name += 2
elif ty_name[0] == 'c' and ty_name[1] == '.':
@@ -700,11 +687,9 @@ class AssignGenMixin:
builder, arr_ty, elem_ty, array_alloca, idx_val)
if elem_ptr is None:
continue
# 内层嵌套数组:元素类型是数组且元素节点是列表字面量 → 递归数组
# 初始化(逐元素 GEP + store)。list 字面量赋给 t.CArray 属数组
# 语义,不应特化 list 泛型(gfx.vp faces: t.CArray[t.CArray[int,3],12]
# 根因)——否则 _list 未注册时 "list literal generic specialization
# failed";只有显式 list[T] 注解才引入 _list。
# 内层嵌套数组:元素类型是数组且元素节点是列表字面量 → 递归数组初始化
# (逐元素 GEP + store)。list 字面量赋给 t.CArray 属数组语义,不特化
# list 泛型(否则 _list 未注册时报特化失败);只有显式 list[T] 注解才引入 _list。
inner_arr: int = 0
if self.Gen.ExprGenBase.is_array_type(elem_ty) != 0:
if elem_node is not None and elem_node.kind() == ast.ASTKind.List:
@@ -779,9 +764,7 @@ class AssignGenMixin:
elif rhs_bits > lhs_bits and lhs_bits > 0:
lhs = self.Gen.ExprGenBase.coerce_to_type(builder, lhs, rhs.Ty, node, trans)
# 无符号操作数(如 u_num_l: uint64 的 //= / %=)→ udiv/urem。
# 此前一律 sdiv/srem(translate_binop 的 Div/Mod 已修,但 AugAssign 走本
# 函数独立发射 → viperlib %lu/%lx 的 //= 仍为 sdiv,T3 未覆盖根因)。
# 无符号操作数(如 u_num_l: uint64 的 //= / %=)→ udiv/urem,有符号走 sdiv/srem。
aug_unsigned: int = 0
if lhs_node is not None:
if self.Gen.ExprGenOps._node_type_unsigned(pool, lhs_node, trans) != 0:
@@ -1088,15 +1071,10 @@ class AssignGenMixin:
return 1
return 0
# ============================================================
# _infer_static_array_ty_from_literal - 单参 CArray + 列表字面量 → 静态数组
#
# 单参 t.CArray[elem_ty] 解析为 Ptr(elem_ty),但当 RHS 是列表字面量时,
# 按 TPC 语义应推断为 [len x elem_ty](对齐 TPC ParseListAnnotation):
# 避免误走动态 list 构造(_find_pool_var 兜底引用外部 @_mbuddy → 链接期
# undefined)。PreScan 与 Handle 都调用(PreScan 预创建 alloca 的类型必须
# 与 Handle 一致,否则 get_or_create_sym 复用旧 alloca 使推断失效)。
# ============================================================
# _infer_static_array_ty_from_literal - 单参 CArray + 列表字面量 → [len x elem_ty]
# 单参 t.CArray[elem_ty] 本解析为 Ptr(elem_ty),RHS 为列表字面量时按 TPC
# ParseListAnnotation 语义推断为静态数组(否则误走动态 list 构造 → 链接期 undefined)。
# PreScan 与 Handle 都必须调用:两处 alloca 类型须一致,否则复用旧 alloca 使推断失效。
def _infer_static_array_ty_from_literal(self, pool: rowptr[memhub.MemBuddy],
annot: ast.AST | t.CPtr,
value: ast.AST | t.CPtr,
@@ -1239,8 +1217,8 @@ class AssignGenMixin:
# 单参 t.CArray[elem_ty] + 列表字面量 → 静态数组(与 Handle 推断一致)。
# PreScan 预创建的 alloca 类型必须与 Handle 期望一致,否则 Handle 的
# get_or_create_sym 复用旧 alloca,数组推断失效仍走动态 list → _mbuddy。
var_ty = self._infer_static_array_ty_from_literal(pool, annot, aa.value, var_ty)
# [D31 fail-fast] 泛型注解退化成裸基类 → 立即终止(暴露退化点)
var_ty = self._infer_static_array_ty_from_literal(pool, annot, aa.value, var_ty)
# [D31 fail-fast] 泛型注解退化成裸基类 → 立即终止(暴露退化点)
self._fail_on_degenerated_generic_annotation(aa, nm.id, annot, var_ty)
else:
# fast-fail: 注解类型解析失败,Python traceback 立即终止(不静默回退 i32)
@@ -1261,11 +1239,9 @@ class AssignGenMixin:
if alloca is not None:
if self.Gen.VarGen.define_var(
trans.SymTab, nm.id, alloca) == 0:
# 存储原始类型注解的类名(方法调用检测时,Ptr(i8) 回退到类名查找结构体)
# 传 pool:泛型 Subscript 返回 mangled name(GSList[BasicBlock] → 特化
# struct 名),否则 pool=None 只返回裸类名(GSList → 泛型模板未注册
# → 属性访问 lookup_field_by_class("GSList") 报"类未注册",LLVMLiteTest
# __function.vp blocks.Tail 根因;对齐 ClassGenMixin L3159 BUG 1 方案3)
# 存储原始类型注解的类名(方法调用检测时 Ptr(i8) 回退按类名查结构体)。
# 传 pool:泛型 Subscript 返回 mangled 特化名;pool=None 只得裸模板名,
# 模板未注册时属性访问会报"类未注册"。
if annot is not None:
cls_nm_ps: str = self.Gen.TypeGenResolve.extract_class_name_from_annotation(
annot, trans._imported_modules, pool, trans._from_imports)
@@ -1280,27 +1256,13 @@ class AssignGenMixin:
return 0
# ============================================================
# _prespecialize_generic_annotation - 注解为泛型 Subscript(含 | t.CPtr 联合)
# 时先注册特化,保证随后的 resolve_annotation_type 得到「带类型实参」的特化
# 结构体(如 {sha1}.list[ast.ASTptr]),而不是退化为不透明基类(裸 list*)。
#
# 背景(崩溃根因,IR 实证):`body_list: list[ast.AST | t.CPtr] | t.CPtr =
# mc.children` 若在 resolve 时该特化尚未注册,注解会退化成裸 list* →
# 之后 body_list.get(i) 被分派到默认 list[int].get(返回 i32)→
# inttoptr i32→AST* 截断指针 → 读垃圾 AST 节点,
# StmtGenMixin._translate_stmt_impl 崩溃。gen1 IR 实证(MatchGenMixin 模块):
# %"body_list.1" = alloca %"….list"* ← 裸 list(丢类型实参)
# %cast_arg_0.9 = bitcast %"….list"* … to %"….list[int]"*
# %call = call i32 @…list[int].get(…) ; 返回 i32
# %int2ptr_stmt = inttoptr i32 %call to %"….AST"* ← 截断
# PreScan 路径(pre_scan_allocas)此前已有此补救,但 Handle 路径
# (translate_annassign)缺失 ⇒ 在任何「未复用预扫描 alloca」的场景
# (如循环体内换作用域重新声明同名变量)又会建出裸 list* alloca。
# 故抽成公共 helper,两条路径共用,保证两处解析结果一致。
#
# _prespecialize_generic_annotation - 注解为泛型 Subscript(含 | t.CPtr 联合)时
# 先注册特化,使随后的 resolve_annotation_type 得到带类型实参的特化结构体,
# 而非退化为不透明基类(裸 list* 会让 .get() 误分派到 list[int].get 截断指针)。
# PreScan(pre_scan_allocas)与 Handle(translate_annassign)两条路径共用本 helper,
# 保证两处解析结果一致(未复用预扫描 alloca 的路径也必须先特化)。
# _specialize_generic_class 幂等,重复调用安全。
# 返回 1=执行过特化尝试;0=注解形态不适用(非泛型 Subscript)。
# ============================================================
def _prespecialize_generic_annotation(self, trans: HT.Translator | t.CPtr,
pool: rowptr[memhub.MemBuddy],
annot: ast.AST | t.CPtr) -> int:
@@ -1343,66 +1305,66 @@ class AssignGenMixin:
#
# 返回新增的变量数
# ============================================================
def _fail_on_degenerated_generic_annotation(self,
aa: ast.AnnAssign | t.CPtr,
var_name: str,
annot: ast.AST | t.CPtr,
var_ty: llvmlite.LLVMType | t.CPtr) -> int:
"""fail-fast:泛型注解解析成了「裸泛型基类指针」(丢失类型实参)
场景:注解是泛型下标 `list[X]` 或 `list[X] | t.CPtr`,但
`resolve_annotation_type` 退化成裸基类 `list*`(没有 `[...]`)
⇒ 之后该变量的任何泛型方法调用(`.get()`/`.set()`/...)都会按**错误**
元素类型特化(实测 `list[int].get` 返回 i32)⇒ `inttoptr` 把 i32 当
指针 ⇒ 读垃圾 AST 节点(D21/D25/D31 崩溃族,ASan 现场
`MatchGenMixin._handle_simple_match → StmtGenMixin._translate_stmt_impl`)。
与 D25 既有守卫的区别:那条守卫判「复用到的**旧 alloca** 是裸泛型基类」;
本守卫判「**本次注解解析结果本身**就是裸泛型基类」,覆盖「旧 alloca 恰好
也是裸类型 → 两边一致 → 既有守卫不触发」的漏网路径。
返回 0=未退化(继续);退化则 fatal_error 终止(不返回)。
"""
if aa is None or annot is None or var_ty is None:
return 0
# 取注解里的泛型 Subscript 一侧:Subscript 本身,或 A | t.CPtr 的 Subscript 分支
ann_sub: ast.Subscript | t.CPtr = None
if annot.kind() == ast.ASTKind.Subscript:
ann_sub = (ast.Subscript | t.CPtr)(annot)
elif annot.kind() == ast.ASTKind.BinOp:
bop_ff: ast.BinOp | t.CPtr = (ast.BinOp | t.CPtr)(annot)
if bop_ff.op == ast.OpKind.BitOr:
if bop_ff.left is not None and bop_ff.left.kind() == ast.ASTKind.Subscript:
ann_sub = (ast.Subscript | t.CPtr)(bop_ff.left)
elif bop_ff.right is not None and bop_ff.right.kind() == ast.ASTKind.Subscript:
ann_sub = (ast.Subscript | t.CPtr)(bop_ff.right)
if ann_sub is None or ann_sub.value is None \
or ann_sub.value.kind() != ast.ASTKind.Name:
return 0
base_nm_ff: ast.Name | t.CPtr = (ast.Name | t.CPtr)(ann_sub.value)
if base_nm_ff.id is None:
return 0
# 仅当该基名确实是泛型模板(list/GSList/...)才判定为退化
if self.Gen.ClassGenGeneric._find_generic_template(base_nm_ff.id) is None:
return 0
if var_ty.Pointee is None:
return 0
vt_nm_ff: str = llvmlite.get_struct_name(var_ty.Pointee)
if vt_nm_ff is None:
return 0
vt_pf_ff: int = GenApi.sha1_prefix_len(vt_nm_ff)
vt_short_ff: str = vt_nm_ff + vt_pf_ff if vt_pf_ff != 0 else vt_nm_ff
if string.strchr(vt_short_ff, '[') is not None:
return 0 # 带类型实参:未退化
self.Gen.TypeGenErr.fatal_error(
aa,
f"变量 '{var_name}' 的泛型注解 '{base_nm_ff.id}[...]' 解析成了裸基类 "
f"'{vt_nm_ff}*'(丢失类型实参);后续 .get()/.set() 会按错误元素类型"
f"特化(如 list[int].get 返回 i32)→ inttoptr 截断指针崩溃。"
f"禁止静默继续——请检查该注解在 resolve_annotation_type 中的退化点。",
"TypeError")
return 0
def _fail_on_degenerated_generic_annotation(self,
aa: ast.AnnAssign | t.CPtr,
var_name: str,
annot: ast.AST | t.CPtr,
var_ty: llvmlite.LLVMType | t.CPtr) -> int:
"""fail-fast:泛型注解解析成了「裸泛型基类指针」(丢失类型实参)
场景:注解是泛型下标 `list[X]` 或 `list[X] | t.CPtr`,但
`resolve_annotation_type` 退化成裸基类 `list*`(没有 `[...]`)
⇒ 之后该变量的任何泛型方法调用(`.get()`/`.set()`/...)都会按**错误**
元素类型特化(实测 `list[int].get` 返回 i32)⇒ `inttoptr` 把 i32 当
指针 ⇒ 读垃圾 AST 节点(D21/D25/D31 崩溃族,ASan 现场
`MatchGenMixin._handle_simple_match → StmtGenMixin._translate_stmt_impl`)。
与 D25 既有守卫的区别:那条守卫判「复用到的**旧 alloca** 是裸泛型基类」;
本守卫判「**本次注解解析结果本身**就是裸泛型基类」,覆盖「旧 alloca 恰好
也是裸类型 → 两边一致 → 既有守卫不触发」的漏网路径。
返回 0=未退化(继续);退化则 fatal_error 终止(不返回)。
"""
if aa is None or annot is None or var_ty is None:
return 0
# 取注解里的泛型 Subscript 一侧:Subscript 本身,或 A | t.CPtr 的 Subscript 分支
ann_sub: ast.Subscript | t.CPtr = None
if annot.kind() == ast.ASTKind.Subscript:
ann_sub = (ast.Subscript | t.CPtr)(annot)
elif annot.kind() == ast.ASTKind.BinOp:
bop_ff: ast.BinOp | t.CPtr = (ast.BinOp | t.CPtr)(annot)
if bop_ff.op == ast.OpKind.BitOr:
if bop_ff.left is not None and bop_ff.left.kind() == ast.ASTKind.Subscript:
ann_sub = (ast.Subscript | t.CPtr)(bop_ff.left)
elif bop_ff.right is not None and bop_ff.right.kind() == ast.ASTKind.Subscript:
ann_sub = (ast.Subscript | t.CPtr)(bop_ff.right)
if ann_sub is None or ann_sub.value is None \
or ann_sub.value.kind() != ast.ASTKind.Name:
return 0
base_nm_ff: ast.Name | t.CPtr = (ast.Name | t.CPtr)(ann_sub.value)
if base_nm_ff.id is None:
return 0
# 仅当该基名确实是泛型模板(list/GSList/...)才判定为退化
if self.Gen.ClassGenGeneric._find_generic_template(base_nm_ff.id) is None:
return 0
if var_ty.Pointee is None:
return 0
vt_nm_ff: str = llvmlite.get_struct_name(var_ty.Pointee)
if vt_nm_ff is None:
return 0
vt_pf_ff: int = GenApi.sha1_prefix_len(vt_nm_ff)
vt_short_ff: str = vt_nm_ff + vt_pf_ff if vt_pf_ff != 0 else vt_nm_ff
if string.strchr(vt_short_ff, '[') is not None:
return 0 # 带类型实参:未退化
self.Gen.TypeGenErr.fatal_error(
aa,
f"变量 '{var_name}' 的泛型注解 '{base_nm_ff.id}[...]' 解析成了裸基类 "
f"'{vt_nm_ff}*'(丢失类型实参);后续 .get()/.set() 会按错误元素类型"
f"特化(如 list[int].get 返回 i32)→ inttoptr 截断指针崩溃。"
f"禁止静默继续——请检查该注解在 resolve_annotation_type 中的退化点。",
"TypeError")
return 0
def translate_annassign(self, trans: HT.Translator | t.CPtr,
node: ast.AST | t.CPtr) -> int:
"""翻译 AnnAssign(target=Name, annotation=..., value=expr)"""
@@ -1447,11 +1409,9 @@ class AssignGenMixin:
var_ty: llvmlite.LLVMType | t.CPtr = None
annot: ast.AST | t.CPtr = aa.annotation
if annot is not None:
# 与 PreScan 同源:先注册泛型特化,避免 list[X](含 | t.CPtr 联合)
# 在 resolve 时退化为裸 list* → 之后 .get() 误分派到默认
# list[int](返回 i32)→ inttoptr 截断指针 → 读垃圾 AST 节点崩溃。
# 此处是崩溃根因路径:循环体内重新声明同名变量时不会复用预扫描
# alloca,必须在本路径同样先特化(详见 _prespecialize_generic_annotation)。
# 与 PreScan 同源:先注册泛型特化,避免 list[X] 退化为裸 list* 后 .get()
# 误分派(返回 i32 → inttoptr 截断)。此处为循环体内重声明同名变量、
# 不复用预扫描 alloca 的路径(详见 _prespecialize_generic_annotation)。
self._prespecialize_generic_annotation(trans, pool, annot)
resolved: llvmlite.LLVMType | t.CPtr = self.Gen.TypeGenResolve.resolve_annotation_type(
pool, annot, trans._imported_modules, trans._from_imports, trans, 0)
@@ -1669,7 +1629,7 @@ class AssignGenMixin:
# [D31 fail-fast] 泛型注解退化成裸基类 → 立即终止(暴露退化点)
self._fail_on_degenerated_generic_annotation(aa, nm.id, annot, var_ty)
# 普通局部变量
# 普通局部变量
# 创建 alloca
alloca: llvmlite.Value | t.CPtr = self.Gen.VarGen.get_or_create_sym(
trans.SymTab, pool, builder, nm.id, var_ty)
@@ -1679,42 +1639,37 @@ class AssignGenMixin:
aa,
f"无法为变量 {nm.id} 创建存储",
"TypeError")
# [D25 根治] 复用到的 alloca 类型必须与本次注解解析类型一致。
# get_or_create_sym 只用「名字 + 作用域」复用(ty 不参与判定,见 VarGenMixin),
# 因此可能命中一个**类型不同**的旧 alloca(同名重声明 / 不同路径建出的退化
# alloca,如裸 list* 对上 list[ast.ASTptr]*)→ alloca 类型与使用点注解类型
# 分裂 → 方法调用按错误的元素类型特化(list[int].get 返回 i32)→ inttoptr
# 截断指针 → 读垃圾 AST 节点(D21/D25 崩溃族)。
# 只比较「双方都是指针且 pointee 都是结构体」这一最典型的退化形态:
# 结构体名不同即 fail-fast,直接暴露退化点与变量名(不兜底)。
if alloca is not None and alloca.Ty is not None and var_ty is not None:
av_pt_d: llvmlite.LLVMType | t.CPtr = alloca.Ty.Pointee
if av_pt_d is not None and self.Gen.ExprGenBase.is_ptr_type(av_pt_d) != 0 \
and self.Gen.ExprGenBase.is_ptr_type(var_ty) != 0:
av_in: llvmlite.LLVMType | t.CPtr = av_pt_d.Pointee
vt_in: llvmlite.LLVMType | t.CPtr = var_ty.Pointee
if av_in is not None and vt_in is not None:
av_nm_d: str = llvmlite.get_struct_name(av_in)
vt_nm_d: str = llvmlite.get_struct_name(vt_in)
if av_nm_d is not None and vt_nm_d is not None \
and string.strcmp(av_nm_d, vt_nm_d) != 0:
# 只在「旧 alloca 的 pointee 是**裸泛型模板基类**(丢类型实参,
# 如裸 list / 裸 GSList)而注解是另一个类型」时 fail-fast ——
# 这正是 D25 的退化形态(后续方法调用按错误元素类型特化 → 截断)。
# 普通的不同具体类(如 Tuple* vs AST*)是**同名重声明换类型**的
# 合法用法(两者都是 8 字节指针槽,运行期无误),不在此拦截。
av_pf: int = GenApi.sha1_prefix_len(av_nm_d)
av_short: str = av_nm_d + av_pf if av_pf != 0 else av_nm_d
if string.strchr(av_short, '[') is None:
av_tpl: ast.ClassDef | t.CPtr = self.Gen.ClassGenGeneric._find_generic_template(av_short)
if av_tpl is not None:
self.Gen.TypeGenErr.fatal_error(
aa,
f"变量 '{nm.id}' 复用到的存储类型为裸泛型基类 '{av_nm_d}*'"
f"(丢了类型实参),与注解解析类型 '{vt_nm_d}*' 不一致;"
f"禁止静默混用——方法调用会按错误元素类型特化并截断指针",
"TypeError")
# 复用到的 alloca 类型必须与本次注解解析类型一致:get_or_create_sym 只用
# 「名字 + 作用域」复用(ty 不参与判定,见 VarGenMixin),可能命中类型不同的
# 旧 alloca(同名重声明 / 退化 alloca,如裸 list* 对上 list[ast.ASTptr]*)。
# 只比较「双方都是指针且 pointee 都是结构体」:结构体名不同即 fail-fast(不兜底)。
if alloca is not None and alloca.Ty is not None and var_ty is not None:
av_pt_d: llvmlite.LLVMType | t.CPtr = alloca.Ty.Pointee
if av_pt_d is not None and self.Gen.ExprGenBase.is_ptr_type(av_pt_d) != 0 \
and self.Gen.ExprGenBase.is_ptr_type(var_ty) != 0:
av_in: llvmlite.LLVMType | t.CPtr = av_pt_d.Pointee
vt_in: llvmlite.LLVMType | t.CPtr = var_ty.Pointee
if av_in is not None and vt_in is not None:
av_nm_d: str = llvmlite.get_struct_name(av_in)
vt_nm_d: str = llvmlite.get_struct_name(vt_in)
if av_nm_d is not None and vt_nm_d is not None \
and string.strcmp(av_nm_d, vt_nm_d) != 0:
# 仅当旧 alloca 的 pointee 是裸泛型模板基类(丢类型实参,如裸
# list / 裸 GSList)而注解是另一个类型时 fail-fast(后续方法调用
# 会按错误元素类型特化 → 截断);不同具体类(如 Tuple* vs AST*)
# 属合法的同名重声明换类型(都是 8 字节指针槽),不拦截。
av_pf: int = GenApi.sha1_prefix_len(av_nm_d)
av_short: str = av_nm_d + av_pf if av_pf != 0 else av_nm_d
if string.strchr(av_short, '[') is None:
av_tpl: ast.ClassDef | t.CPtr = self.Gen.ClassGenGeneric._find_generic_template(av_short)
if av_tpl is not None:
self.Gen.TypeGenErr.fatal_error(
aa,
f"变量 '{nm.id}' 复用到的存储类型为裸泛型基类 '{av_nm_d}*'"
f"(丢了类型实参),与注解解析类型 '{vt_nm_d}*' 不一致;"
f"禁止静默混用——方法调用会按错误元素类型特化并截断指针",
"TypeError")
# FakeDuck: str 变量创建影子结构体 {var}__meta__(per-instance 字段存储)
# 仅裸 str 注解(Name("str"))且 _str 已注册时创建;bytes / char|t.CPtr 不创建。
# a 保持为 i8*(可 printf/len),a__meta__ 存 __data__/__mbuddy__ 等字段,
@@ -1789,11 +1744,8 @@ class AssignGenMixin:
if inm2.id is not None:
if self.Gen.AsyncGen.is_async_func(trans, inm2.id) != 0:
self.Gen.AsyncGen.register_coro_var(trans, nm.id, inm2.id)
# 局部数组:列表字面量 → 逐元素 GEP + store 初始化
# 修复:此前局部 CArray[...] = [x, y] 会走 translate_list_literal,
# 错误触发 list[T] 泛型特化(堆对象)导致崩溃(如 this.vp 的
# t.CArray[char, 256] = [0])。局部 alloca 未零初始化,
# 先整体置零再写列表元素(与全局数组默认零初始化语义一致)。
# 局部数组:列表字面量 → 先整体置零,再逐元素 GEP + store 初始化。
# 不走 translate_list_literal(避免误触发 list[T] 泛型特化)。
local_arr_ty: llvmlite.LLVMType | t.CPtr = None
if aa.value.kind() == ast.ASTKind.List and alloca.Ty is not None:
pt_l: llvmlite.LLVMType | t.CPtr = alloca.Ty.Pointee
@@ -1824,16 +1776,10 @@ class AssignGenMixin:
builder, pool, mod, aa.value,
None, 0, trans, lv_target_ty)
if rhs_val is not None:
# 注解退化修复(对齐 TPC 从构造结果推断类型):
# 无实参泛型模板类注解(如 vi: vector.Vector | CPtr,模板
# 未注册 struct → resolve 退化)会让 alloca 类型退化为
# Ptr(i8),但 rhs 构造返回精确结构体指针(如 Ptr(Vector[int]
# 结构))。此时按退化 alloca store 会丢失特化类型,后续
# vi.push 方法调用按 Ptr(i8) 查不到类名 → AnnotClassName
# 回退到模板名(无实参)→ vpi 查不到返回类型 fail-fast。
# 用 rhs 精确类型重建 alloca 并更新符号表注册,使方法调用
# 按精确特化类名解析。旧退化 alloca 行残留为 entry block
# 死代码(文本 IR 即时生成,无法删除,无害)。
# 注解退化修复(对齐 TPC 从构造结果推断类型):无实参泛型模板注解
# 会让 alloca 退化为 Ptr(i8),而 rhs 构造返回精确结构体指针 → 用 rhs
# 精确类型重建 alloca 并更新符号表,使方法调用按精确特化类名解析;
# 旧退化 alloca 残留为 entry 块死代码(文本 IR 即时生成无法删除,无害)。
precise_rhs_ty: llvmlite.LLVMType | t.CPtr = None
if rhs_val.Ty is not None:
match rhs_val.Ty:
@@ -1847,23 +1793,13 @@ class AssignGenMixin:
pass
if precise_rhs_ty is not None and alloca.Ty is not None:
alloca_pt: llvmlite.LLVMType | t.CPtr = alloca.Ty.Pointee
# 禁止「降级重建」:RHS 的 pointee 若为**裸泛型基类**(丢类型
# 实参,如 {sha1}.list / {sha1}.GSList),以它重建 alloca 会把后
# 注解已解析出的「带类型实参特化」(如 list[ast.ASTptr])覆盖成裸
# 基类 → 之后 .get() 按错误元素类型特化(list[int].get 返回 i32)
# → inttoptr 把 i32 当指针(截断)→ translate_stmt 读垃圾 AST 节
# 点崩溃。本重建本意是「注解退化成裸基类/i8*,RHS 构造出精确特化」
# 的**反向升级**(见上方注释),因此仅在 RHS 不是裸泛型基类时才执行。
# 现场根因:AST.children 字段注册为裸 list*(其注解
# list[rowptr[AST]] | t.CPtr 在字段注册期未解析出带实参特化),
# 故 `body_list: list[ast.AST | t.CPtr] | t.CPtr = mc.children`
# 的 RHS 静态类型退化为裸 list*,触发本降级重建 →
# MatchGenMixin._handle_renum_match 崩溃(IR 实证:
# %"body_list.1" = alloca %"….list"* 被使用,%body_list 特化 alloca
# 成死代码;.get 被分派到 list[int].get 返回 i32 → inttoptr 截断)。
# 裸泛型基类不可作为 receiver 静态类型,否则泛型方法分派必错。
# 禁止「降级重建」:RHS 的 pointee 若是裸泛型基类(丢类型实参,
# 如 {sha1}.list / {sha1}.GSList),以它重建会把注解已解析出的
# 带类型实参特化覆盖成裸基类 → .get() 按错误元素类型分派 →
# inttoptr 截断崩溃。故仅 RHS 不是裸泛型基类时才执行重建
# (本重建本意是注解退化为裸基类/i8*、RHS 精确的反向升级)。
# 取注解侧泛型基名(Base ∈ list/dict/...,仅 Name 形态的
# Subscript 一侧),用于下方**不依赖泛型模板注册表**的方向判定。
# Subscript 一侧),用于下方不依赖泛型模板注册表的方向判定。
ann_gen_base: str = None
if annot is not None:
ag_side: ast.AST | t.CPtr = annot
@@ -2275,10 +2211,7 @@ class AssignGenMixin:
if tk == ast.ASTKind.Attribute:
# property setter 拦截: obj.attr = v 且 attr 是 @x.setter 方法时,
# 生成 call Class.attr$set(obj, v),跳过字段存储
# 注意参数顺序:_try_property_setter(pool, builder, mod, ...)——
# 旧代码传 (builder, pool, ...) 导致 pool/builder 错位,pool 参数
# 实际是 IRBuilder 对象,pool.alloc 虚调用读 IRBuilder[0] 当 vtable
# 跳到垃圾地址崩溃(BuiltinDecoratorTest Phase A 崩溃根因)。
# 注意参数顺序:_try_property_setter(pool, builder, mod, ...)。
if self.Gen.CallGenProp._try_property_setter(
pool, builder, mod, target, rhs_val, trans) == 1:
continue
@@ -2366,10 +2299,9 @@ class AssignGenMixin:
target,
f"global 变量 '{nm.id}' 未注册,无法赋值",
"NameError")
# 对象类全局(裸类名注解 → 静态 storage):对象构造/赋值 → 引用
# 绑定到 storage + 复制对象到 storage。否则 store 栈 alloca 指针
# 悬垂(devfs serial0_file 根因),或模块级构造初始化器被丢弃后
# 全局保持 NULL(页 0 混叠根因,见 TranslatorGenMixin storage 逻辑)。
# 对象类全局(裸类名注解 → 静态 storage):对象构造/赋值 → 引用绑定
# 到 storage + 复制对象到 storage;否则 store 栈 alloca 指针悬垂,
# 或模块级构造初始化器被丢弃后全局保持 NULL(见 TranslatorGenMixin)。
stg_ref_as: llvmlite.Value | t.CPtr = None
if nm.id is not None:
stg_key_as: str = pool.alloc(sizet(string.strlen(nm.id)) + 8)
@@ -2524,14 +2456,10 @@ class AssignGenMixin:
return None
obj_cls: str = self._infer_rhs_annot(pool, mod, ca.value, trans)
if obj_cls is None:
# 模块级函数调用(requests.get):ca.value 是已导入模块名
# 而非对象变量(模块名 lookup_var_entry 返回 None → 上面
# 对象类名推断失败)。从该模块 vpi 的返回注解缓存解析返回
# 类名(键 {sha1}.{func},cls_name=None 即模块级函数,与
# CallGenMixin 跨模块返回类型解析一致)。
# WebTest resp = requests.get(...) 根因:无注解赋值变量
# AnnotClassName 推断失败(annot=None)→ 属性访问按需过滤
# (依赖 AnnotClassName)失效 → "属性访问失败: .status_code"。
# 模块级函数调用(requests.get):ca.value 是已导入模块名而非对象变量
# (模块名 lookup_var_entry 返回 None → 对象类名推断失败)。从该模块
# vpi 返回注解缓存解析返回类名(键 {sha1}.{func},cls_name=None 即模块级
# 函数,与 CallGenMixin 跨模块返回类型解析一致)。
if ca.value.kind() == ast.ASTKind.Name:
mod_nm_rhs: ast.Name | t.CPtr = (ast.Name | t.CPtr)(ca.value)
if mod_nm_rhs.id is not None:
+36 -250
View File
@@ -83,9 +83,7 @@ class CallGenBase:
GlobalFuncCount: int
# --- 全局函数表"名 → 槽下标"索引(开放寻址 | 线性探测;槽存 index+1,0=空槽) ---
# GlobalFuncs 在 PermPool 固定容量分配、只追加不重排、从不 reset ⇒ 存槽下标
# 稳定(远优于存会被回收的节点指针);GlobalFuncCount 单调递增,
# Built == GlobalFuncCount 才可信,不可信/未命中一律回退线性扫描。
# GlobalFuncs 只追加不重排 ⇒ 槽下标稳定;Built == GlobalFuncCount 才可信,否则回退线性扫描。
GlobalFuncNameIdx: sizet | t.CPtr
GlobalFuncNameIdxCap: int
GlobalFuncNameIdxBuilt: int
@@ -95,11 +93,7 @@ class CallGenBase:
GlobalDefaultsCount: int
# --- 依赖模块 text.ll「方法返回类型文本」缓存(_LookupMethodRetTyFromTextLL 性能优化) ---
# key = text.ll 里 define 的完整 mangled 函数名("{sha1}.{后缀}")→ 返回类型文本。
# 表与键/值均 PermPool 分配(跨文件/Phase 存活);sha1 本身是内容哈希 ⇒ 天然
# 内容寻址,无需 size/mtime 校验。值只存**文本**(绝不存 LLVMType*——其分配在
# 每文件 reset_to 失效的 pool 中);命中后用调用方 pool 重建类型,与原路径等价。
# 惰性建表;建表/分配失败即不缓存,回退原扫描(正确性保证,非兜底)。| 不用全局裸表。
# key = define 的完整 mangled 函数名("{sha1}.{后缀}"),值只存文本(绝不存 LLVMType*,其分配在每文件 reset 失效的 pool 中);表与键/值均 PermPool 分配,命中后用调用方 pool 重建类型,建表失败即不缓存并回退原扫描。
MethodRetTyMap: hashtable.HashTable | t.CPtr
def InitCallGen(self) -> int:
@@ -127,11 +121,7 @@ class CallGenBase:
self.MethodRetTyMap = None
return 0
# ============================================================
# P4 簇 1+2 迁移方法(函数体来自 HandlesExprCall.vp:
# 模块 SHA1 查询/CExport 注册表/c.* 系列/内联汇编/LLVMIR)
# 本模块函数调用改 self.,外部模块调用保留模块前缀
# ============================================================
# P4 簇 1+2 迁移方法:模块 SHA1 查询 / CExport 注册表 / c.* 系列 / 内联汇编 / LLVMIR
@@ -200,9 +190,7 @@ class CallGenBase:
"""
if pool is None or mod_sha1 is None or func_name is None:
return None
# star import 候选模块累积(空格分隔):from X import * 无法确定
# func_name 归属,收集全部候选由调用方逐个验证(is_cexport_func /
# _is_state_func_from_vpi 按 vpi 返回注解确认定义模块)。
# star import 候选模块累积(空格分隔):from X import * 无法确定归属,收集全部候选由调用方逐个验证
star_cands: str = None
star_cands_len: sizet = 0
temp_dir: str = self.Gen.TypeGenBase.get_temp_dir()
@@ -217,7 +205,7 @@ class CallGenBase:
if buf is None:
stdlib.free(vpi_path)
return None
vf: fileio.File | t.CPtr = fileio.File(vpi_path, fileio.MODE.R)
vf: rowptr[fileio.File] = fileio.File(vpi_path, fileio.MODE.R)
stdlib.free(vpi_path)
if vf.closed:
stdlib.free(buf)
@@ -242,11 +230,7 @@ class CallGenBase:
ll: sizet = pos - ls
if pos < total:
pos += 1
# 自含定义检测(须在 from-import 行匹配之前,对所有行生效):
# __values.vp 自含 def ConstFloat 且 `from stdint import *`——
# star 候选 stdint 会把函数归属劫持为 f5522571bcce7bcb.ConstFloat
# (跨模块返回类型解析 fail-fast 根因)。模块级 def 行首无缩进
# (类内方法 def 有缩进,首字符为空格,不会误命中)。
# 自含定义检测(须在 from-import 行匹配之前,对所有行生效):模块级 def 行首无缩进,类内方法 def 有缩进不会误命中
if ll > 4 and buf[ls] == 'd' and buf[ls + 1] == 'e' \
and buf[ls + 2] == 'f' and buf[ls + 3] == ' ':
fstart_od: sizet = ls + 4
@@ -284,11 +268,7 @@ class CallGenBase:
mod_end: sizet = imp_pos
# 名称列表从 "import " 之后(逗号分隔,含 "A as B" 取原名 A)
nm_start: sizet = imp_pos + 8
# star import 支持:from X import * → 任意函数都可能属 X 模块。
# 不立即返回(首个 star 行未必是 func_name 的定义模块,如
# fltk/__init__ 依次 from _types/_enums/_cfl import *),累积
# 全部候选,扫描完由调用方逐个验证(is_cexport_func /
# _is_state_func_from_vpi 按 vpi 返回注解确认定义模块)。
# star import 支持(from X import *):首个 star 行未必是定义模块,累积全部候选,扫描完由调用方按 vpi 返回注解逐个验证
if nm_start < ls + ll and buf[nm_start] == '*':
star_dot: int = 0
if buf[mod_start] == '.':
@@ -384,13 +364,7 @@ class CallGenBase:
stdlib.free(buf)
return mod_buf
scan = n_end
# 全部行扫描完毕:显式 from-import 精确匹配已提前返回;仅有 star
# import 时返回累积候选(空格分隔,调用方逐个验证定义模块)。
# 但模块自含 def {func_name}(未遇同名 from-import 覆盖,否则上面
# 已提前返回 import 源)时本模块就是定义者,不是 re-export——star
# 候选(如 __values.vp `from stdint import *` 的 stdint)会劫持归属,
# 生成错误 SHA1(f5522571bcce7bcb.ConstFloat)→ 跨模块返回类型解析
# fail-fast。返回 None 保持原模块定义归属。
# 全部行扫描完毕:仅有 star import 时返回累积候选(调用方逐个验证定义模块);模块自含 def {func_name} 时本模块即定义者,返回 None 保持归属
if star_cands is not None:
stdlib.free(buf)
if has_own_def == 0:
@@ -544,30 +518,16 @@ class CallGenBase:
if mod is None or name is None:
return None
name_len: sizet = string.strlen(name)
# 热点优化:原实现把「精确匹配」+「本模块同名 mangled」+「后缀匹配
# define 优先」+「后缀匹配 declare 兜底」拆成 4 趟完整链表扫描,每趟
# 对每个节点调用一次 function_get_name/function_get_next(实测二者各
# ~1.046 亿次的热点来源)。合并为 2 趟,链表顺序与优先级语义不变:
# 趟 1:精确匹配(命中即返回,优先级最高,与原第一遍一致)+ 收集
# 首个本模块同名 mangled 候选(原"第一遍半",仅在无精确匹配
# 时使用);
# 趟 2:收集首个 IsDeclared==0 后缀命中(原第二遍优先)与首个任意
# 后缀命中(原第三遍兜底),扫描结束后按原优先级返回。
# 热点优化:原 4 趟完整链表扫描合并为 2 趟——趟 1 精确匹配(命中即返回)+ 收集首个本模块同名 mangled 候选;趟 2 收集首个 IsDeclared==0 后缀命中与首个任意后缀命中,扫描后按原优先级返回(语义不变)
cur: llvmlite.Function | t.CPtr = mod.FuncHead
mangle_hit: llvmlite.Function | t.CPtr = None
while cur is not None:
cur_name: str = llvmlite.function_get_name(cur)
if cur_name is not None:
# 快速路径:精确匹配要求首字符相等(strcmp 判等蕴含首字符相等),
# 首字符不同者直接跳过 strcmp;mangled 名以 sha1 十六进制字符打头,
# 裸名多为字母/下划线,绝大多数节点可被此条件短路。
# 快速路径:strcmp 判等蕴含首字符相等,首字符不同者跳过 strcmp(mangled 名以 sha1 十六进制打头,裸名多为字母/下划线)
if cur_name[0] == name[0] and string.strcmp(cur_name, name) == 0:
return cur
# 本模块同名候选:仅接受恰为 "{owner_sha1}.{name}" 的形态
# (mangled 模块级函数名含且只含一个 '.'),不匹配
# "{sha1}.Class.method" 等方法名。是否 IsDeclared==0 均可返回:
# 前向 declare 与 define 同名同符号。只在 owner_sha1 提供且尚未
# 命中时计算,避免多做一次名字长度读取。
# 本模块同名候选:仅接受恰为 "{owner_sha1}.{name}" 的形态(不匹配 "{sha1}.Class.method");declare 与 define 同名同符号,是否 IsDeclared==0 均可返回
if owner_sha1 is not None and mangle_hit is None:
# 名字长度改取 Function.NameLen(创建时一次算好),免去对热链
# 每个节点重复 strlen(cur_name)(全程序 strlen 热点来源)。
@@ -586,17 +546,7 @@ class CallGenBase:
cur = llvmlite.function_get_next(cur)
if mangle_hit is not None:
return mangle_hit
# 后缀匹配(.name)
# 不跳过 declare:方法 stub 也是 declare,需要通过后缀匹配找到
# 调用处(translate_call 等)负责判断 IsDeclared 并决定使用裸名还是 sha1 前缀名
#
# 修复:本模块 define 优先于跨模块 stub declare。
# 根因:跨模块同名函数(如 lexer.vp 的 _advance 与 parser.vp 的 _advance)
# 的 stub declare 在 stub 注入时排在 FuncHead 前面,裸名调用 _advance 后缀
# 匹配按链表顺序先命中跨模块 declare → 参数按 Lexer* coerce → Parser* 被
# bitcast 成 Lexer* → lexer 版 _advance 按 Lexer 布局操作 Parser 指针,
# cur 永不推进 → parse 无限循环。本模块同名 define 必须优先。
# 无同名 define 时回退到 stub declare(跨模块方法/函数调用)。
# 后缀匹配(.name):不跳过 declare(方法 stub 也是 declare,由调用处判断 IsDeclared 决定用裸名或 sha1 前缀名)。本模块同名 define 必须优先于跨模块 stub declare,无同名 define 时回退 stub declare
declare_hit: llvmlite.Function | t.CPtr = None
any_hit: llvmlite.Function | t.CPtr = None
cur = mod.FuncHead
@@ -627,12 +577,7 @@ class CallGenBase:
return any_hit
# ============================================================
# _ensure_method_declare - 为跨模块方法调用创建 declare 声明
#
# 当 found_func 为 None(stub 未注入或跨模块调用)时,
# 在当前模块中创建函数声明,避免 llc 报 undefined value。
# ============================================================
# _ensure_method_declare:found_func 为 None(stub 未注入或跨模块调用)时在当前模块创建 declare,避免 llc 报 undefined value
def _ensure_method_declare(self, pool: rowptr[memhub.MemBuddy],
mod: llvmlite.LLVMModule | t.CPtr,
call_name: str,
@@ -661,7 +606,6 @@ class CallGenBase:
stdlib.exit(1)
if existing.Name is not None:
# 快速路径:strcmp 判等蕴含首字符相等,首字符不同者跳过 strcmp
# (热点:本函数对模块函数链逐节点命名比对)。
if existing.Name[0] == call_name[0] \
and string.strcmp(existing.Name, call_name) == 0:
# 已存在:仅当是 declare 且参数数量不足时追加缺失参数
@@ -699,14 +643,7 @@ class CallGenBase:
pi2 += 1
return
existing = existing.Next
# 裸名 C 库函数(如 string.memset → @memset、stdlib.memcpy → @memcpy):
# call_name 无 sha1 前缀 → 无法提取定义侧模块查 vpi 参数注解 →
# 此前用实参类型建 declare(字面量 0=i32 把定义 sizet(i64) 固化窄,
# VKernel process.vp string.memset 声明 @memset(i8*,i32,i32) vs 定义
# @memset(i8*,i32,i64),跨模块 ABI 宽度不一致,优化路径读高位垃圾
# 写穿内存)。已知 C 库函数直接按标准签名建 declare(与 CallGenLib
# _ensure_c_lib_declare 白名单一致:memset/memcpy/memmove/memcmp/
# strcmp/strncmp/strcpy/strncpy/strcat/strlen/puts/atoi/atol/abs/labs)。
# 裸名 C 库函数(如 string.memset → @memset):call_name 无 sha1 前缀,无法提取定义侧模块查 vpi 参数注解,故按标准签名建 declare(与 CallGenLib._ensure_c_lib_declare 白名单一致)
if call_name is not None and string.strchr(call_name, 46) is None:
std_lib_fn: llvmlite.Function | t.CPtr = self.Gen.CallGenLib._ensure_c_lib_declare(
pool, mod, call_name)
@@ -717,25 +654,12 @@ class CallGenBase:
if func is None:
return
func.IsDeclared = 1
# 定义侧参数类型优先(根治实参字面量固化错误宽度):
# declare 此前用调用点实参类型(param_head)建参数,字面量 0=i32 会把
# 定义 sizet(i64) 的参数固化窄(SlotsBugTest _translate_struct_ctor
# can=i32 vs 定义 i64 → 调用侧 trunc i64→i32、callee 按 i64 读栈残留
# 垃圾高位 → can=巨值读未写槽位崩溃根因)。从 call_name 提取
# {sha1}.{cls}.{method} 查 vpi 参数注解缓存取定义侧类型:仅当实参与
# 定义都是整数且定义更宽时采用定义宽度(指针/联合保持实参类型,避免
# 字符串路径把 list*/Struct* 降级为 i8*)。
# 注:反方向(定义侧更窄,如 su8250.putchar 定义 (i16,i8) 而字面量
# 1016=i32)由调用点 CallGenLib._coerce_args_to_def_params 先把实参
# 窄化到定义宽度,本处依 param_head 建 declare 即已对齐(此块做补强,
# 对已窄化的实参不再触发)。
# 定义侧参数类型优先:从 call_name 提取 {sha1}.{cls}.{method} 查 vpi 参数注解取定义侧类型,仅当实参与定义都是整数且定义更宽时采用定义宽度(指针/联合保持实参类型)。反方向(定义更窄)由 CallGenLib._coerce_args_to_def_params 在调用点先窄化实参
sha1_d: str = None
cls_d: str = None
meth_d: str = None
if call_name is not None:
# [C10 收敛] SHA1 前缀判定走唯一真源 `GenApi.sha1_prefix_len`
# (旧实现自带一份 hex+'.' 校验,判据 `>= 17` 与真源一致);
# 前缀长度取返回值,不再硬编码 17/16。
# SHA1 前缀判定走唯一真源 GenApi.sha1_prefix_len,前缀长度取其返回值(不硬编码 17/16)
sha1_pfx_d: int = GenApi.sha1_prefix_len(call_name)
if sha1_pfx_d != 0:
pfx_len_d: sizet = sizet(sha1_pfx_d)
@@ -794,14 +718,7 @@ class CallGenBase:
stdlib.free(cls_d)
llvmlite.module_add_function(mod, func)
# ============================================================
# _lookup_method_ret_ty_via_vpi - 从 vpi stub 返回注解缓存精确获取方法返回类型
#
# 方法定义处(.vpi stub)的返回注解(如 json.JsonValue._pool_alloc →
# "str")在 stub 注入时由 VpiReader 缓存(_cache_vpi_return_annots_text)。
# 此函数按 模块SHA1 + 类名 + 方法名 查缓存并纯字符串解析为 LLVMType。
# 无需硬编码白名单——返回类型始终来自方法定义处。
# ============================================================
# _lookup_method_ret_ty_via_vpi:按 模块SHA1 + 类名 + 方法名 查 vpi 返回注解缓存并解析为 LLVMType,未命中返回 None(不硬编码白名单,类型始终来自定义处)
# ============================================================
def _lookup_method_ret_ty_via_vpi(self, pool: rowptr[memhub.MemBuddy],
@@ -823,16 +740,7 @@ class CallGenBase:
return None
return VpiReader._resolve_vpi_ret_annot_str(pool, annot, i32_ty, i8_ptr_ty)
# ============================================================
# _vpi_method_param_annot - 从 vpi 参数注解缓存获取方法参数注解串
#
# 跨模块方法调用在 stub 未注入(依赖收集不加载被调模块 vpi,如
# self.Gen.ExprGenCdef.method 经 Generator 属性访问)时,函数表无
# ParamCount/默认参数信息 → 调用点缺参 → 被调函数读栈残留。此函数
# 按需加载并查询 vpi 参数注解缓存('@' 分隔,含首参 self 空串占位),
# 调用方据此补全 declare/call 缺失参数(默认参数零值兜底)。
# 未命中返回 None。
# ============================================================
# _vpi_method_param_annot:按需加载并查询 vpi 参数注解串('@' 分隔,含首参 self 空串占位),供调用方补全 declare/call 缺失参数;未命中返回 None
def _vpi_method_param_annot(self, pool: rowptr[memhub.MemBuddy],
cls_sha1: str,
cls_name: str,
@@ -845,12 +753,7 @@ class CallGenBase:
VpiReader._ensure_vpi_param_annot_loaded(cls_sha1, self.Gen.CallGenBase.TempDir)
return VpiReader._lookup_vpi_param_annots(cls_sha1, cls_name, method_name)
# ============================================================
# _vpi_resolve_param_annot_ty - 解析 vpi 参数注解段为 LLVMType
#
# 与 _lookup_method_ret_ty_via_vpi 的返回注解解析同源;解析失败返回
# None,调用方以 i8* 兜底(ABI 兼容,参数槽位宽度一致)。
# ============================================================
# _vpi_resolve_param_annot_ty:解析 vpi 参数注解段为 LLVMType,失败返回 None(调用方以 i8* 兜底)
def _vpi_resolve_param_annot_ty(self, pool: rowptr[memhub.MemBuddy],
seg: str) -> llvmlite.LLVMType | t.CPtr:
"""解析单个 vpi 参数注解段为 LLVMType,失败返回 None"""
@@ -860,17 +763,7 @@ class CallGenBase:
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, llvmlite.Int8(pool))
return VpiReader._resolve_vpi_ret_annot_str(pool, seg, i32_ty, i8_ptr_ty)
# ============================================================
# _special_method_ret_ty - 编译器内置特殊方法的固定返回类型
#
# __before_init__ / __init__:编译器生成/约定返回 void
# __new__:返回 Ptr(StructTy)(存储空间指针)
#
# 这些方法不存在于源码(vpi stub 无条目),跨模块调用时
# found_func/text.ll/vpi 缓存必然未命中,但返回类型是编译器
# 确定性语义(与 HandlesClassDef._translate_method 3543-3548 一致),
# 在此直接固定,不触发 fail-fast 误报。非特殊方法返回 None。
# ============================================================
# _special_method_ret_ty:编译器内置特殊方法的固定返回类型——__before_init__/__init__ → void,__new__ → Ptr(StructTy),非特殊返回 None(vpi 无条目,固定即可避免 fail-fast 误报)
# ============================================================
def _special_method_ret_ty(self, pool: rowptr[memhub.MemBuddy],
@@ -890,14 +783,7 @@ class CallGenBase:
return None
return None
# ============================================================
# _fail_method_ret_ty - 方法返回类型无法精确获取时 fail-fast
#
# 返回类型来源(按序):当前模块函数 → text.ll define → vpi stub 返回注解缓存。
# 全部未命中时立即 ERROR + 错误堆栈终止,绝不按名推断白名单/静默默认 i32。
#
# node: 调用点 AST 节点(可选,用于 ERROR 堆栈定位)
# ============================================================
# _fail_method_ret_ty:方法返回类型无法精确获取时 ERROR + 错误堆栈终止(node 为调用点 AST,用于定位),绝不静默默认 i32
# ============================================================
def _fail_method_ret_ty(self, method_name: str,
@@ -916,12 +802,7 @@ class CallGenBase:
"TypeError")
return None
# ============================================================
# SetTempDir - 设置 temp 目录路径
#
# 在 Phase2.py 的 Phase A 翻译前调用,使 CallGenMixin
# 能够读取依赖模块的 text.ll 文件。
# ============================================================
# SetTempDir:设置 temp 目录路径(Phase A 翻译前调用,供读取依赖模块 text.ll)
# ============================================================
def SetTempDir(self, temp_dir: str) -> None:
@@ -934,27 +815,7 @@ class CallGenBase:
else:
self.Gen.CallGenBase.TempDirLen = 0
# ============================================================
# _ParseLLVMRetTyStr - 将 LLVM IR 返回类型字符串转换为 llvmlite.LLVMType
#
# 从 define 行中提取的返回类型字符串(define 与 @ 之间的内容),
# 转换为 llvmlite.LLVMType 对象。
#
# 支持的类型:
# void → Void
# i8/i16/i32/i64 → Int8/Int16/Int32/Int64
# double → Double, float → Float
# 任何含 * 的类型 → Ptr(Int8)(通用指针,保留 64 位地址)
#
# Args:
# pool: 内存池
# buf: 包含类型字符串的缓冲区
# start: 类型字符串起始位置
# end: 类型字符串结束位置(不含)
#
# Returns:
# LLVM 类型对象,或 None(无法解析)
# ============================================================
# _ParseLLVMRetTyStr:把 LLVM IR 返回类型字符串(define 与 @ 之间)转为 LLVMType——void / iN / double / float,含 * 者返回通用 Ptr(Int8);无法解析返回 None
# ============================================================
def _ParseLLVMRetTyStr(self, pool: rowptr[memhub.MemBuddy],
@@ -985,11 +846,7 @@ class CallGenBase:
if ty_len == 0:
return None
# struct 字面量 {T1, T2, ...}(tuple 打包返回类型,如 b2a_hqx ->
# tuple[str, t.CUnsignedShort] 编译为 {i8*, i16})。必须在 * 检查
# 之前——否则 "{i8*, i16}" 内含 * 会被误判为 i8*(b2a_hqx 调用侧
# 返回类型退化 i8* → HandlesAssign tuple 解包静默跳过 → free 未初始化
# 指针崩溃,HashLibTest hqx 段)。
# struct 字面量 {T1, T2, ...}(tuple 打包返回类型)必须在 * 检查之前,否则内含 * 会被误判为 i8*(破坏 tuple 解包)
if s < e and buf[s] == '{':
th_st: llvmlite.ParamNode | t.CPtr = None
tt_st: llvmlite.ParamNode | t.CPtr = None
@@ -1074,14 +931,7 @@ class CallGenBase:
# 无法解析
return None
# ============================================================
# _LookupCachedRetTyText - 查「依赖模块 text.ll 方法返回类型文本」持久缓存
#
# 缓存键 = text.ll 中 define 的完整 mangled 函数名
# ("{sha1}.{class}.{method}";bare 模式 "{sha1}.{method}")。sha1 本身是内容
# 哈希 ⇒ 天然内容寻址,无需 size/mtime 校验。命中返回返回类型文本(PermPool
# 持久),未命中/表未建返回 None(调用方回退原扫描)。
# ============================================================
# _LookupCachedRetTyText:查「依赖模块 text.ll 方法返回类型文本」持久缓存——键为 define 完整 mangled 名("{sha1}.{class}.{method}",bare 模式 "{sha1}.{method}"),命中返回文本,未命中/表未建返回 None
def _LookupCachedRetTyText(self, class_sha1: str, class_name: str,
method_name: str, use_bare: int) -> str:
"""查返回类型文本缓存:命中返回文本,未命中返回 None"""
@@ -1113,16 +963,7 @@ class CallGenBase:
return None
return (str | t.CPtr)(v)
# ============================================================
# _CacheModuleRetTyTexts - 把某模块 text.ll 全部 define 行的返回类型写入持久缓存
#
# 仅在 _LookupMethodRetTyFromTextLL 成功读到 text.ll 后调用(未读到文件绝不写
# 缓存:Phase A 按文件顺序翻译,依赖模块此遍可能尚未生成 text.ll,未命中必须
# 回退原扫描、负结果不可缓存)。键=define 完整 mangled 函数名(深拷贝进
# PermPool),值="define " 与 "@" 之间的返回类型文本(深拷贝进 PermPool)。
# 同名只记首次出现(与逐行扫描「首个命中即返回」一致);只登记以 "{sha1}."
# 开头的本模块 mangled 名(调用方查询键恒为此前缀,外部/内部符号永不命中)。
# ============================================================
# _CacheModuleRetTyTexts:把某模块 text.ll 全部 define 行的返回类型写入持久缓存(键/值深拷贝进 PermPool,同名只记首次,只登记 "{sha1}." 开头的本模块 mangled 名);仅在成功读到 text.ll 后调用,负结果绝不缓存
def _CacheModuleRetTyTexts(self, class_sha1: str, text_buf: bytes,
text_len: sizet) -> None:
"""登记 text.ll 全部 define 行到持久缓存(建表/分配失败即不缓存,回退原扫描)"""
@@ -1195,26 +1036,7 @@ class CallGenBase:
stdlib.free(key_buf)
pos = le + 1
# ============================================================
# _LookupMethodRetTyFromTextLL - 从依赖模块的 text.ll 查找方法返回类型
#
# 读取 {temp_dir}/{sha1[0:2]}/{sha1}.text.ll (level=1),搜索
# define 行中包含 "{sha1}.{class_name}.{method_name}" 的函数,
# 提取返回类型并转换为 llvmlite.LLVMType。
#
# 解决问题: 旧实现 _infer_method_ret_ty(已移除)默认返回 i32,导致指针返回值
# 被 inttoptr 截断为 32 位(coerce_to_type line 238-239)。
# 本函数从 text.ll 获取实际返回类型,避免截断。
#
# Args:
# pool: 内存池
# class_sha1: 类所属模块的 SHA1
# class_name: 类名
# method_name: 方法名
#
# Returns:
# LLVM 类型对象,或 None(未找到/text.ll 不存在)
# ============================================================
# _LookupMethodRetTyFromTextLL:读取 {temp_dir}/{sha1[0:2]}/{sha1}.text.ll (level=1),按 "{sha1}.{class_name}.{method_name}" 搜索 define 行并解析返回类型;未找到/文件不存在返回 None
# ============================================================
def _LookupMethodRetTyFromTextLL(self, pool: rowptr[memhub.MemBuddy],
@@ -1236,10 +1058,7 @@ class CallGenBase:
if sha1_len < 2:
return None
# [按模块一次性缓存·命中] 该模块 text.ll 已被读过一次 ⇒ O(1) 取回函数返回
# 类型文本,用**调用方 pool** 重建类型直接返回(与原路径语义等价,不再开
# 文件/读盘/逐字节重扫)。未命中 → 走下方原实现;仅在成功读到 text.ll 后
# 才回写缓存(依赖模块此遍可能尚未生成,负结果绝不缓存)。
# 缓存命中:O(1) 取回返回类型文本并用调用方 pool 重建类型直接返回;未命中走下方原实现
cached_text: str = self._LookupCachedRetTyText(
class_sha1, class_name, method_name, use_bare)
if cached_text is not None:
@@ -1262,11 +1081,8 @@ class CallGenBase:
self.Gen.CallGenBase.TempDir, subdir_buf, class_sha1)
stdlib.free(subdir_buf)
# 读取 text.ll(按文件实际大小分配:固定 _TEXT_LL_READ_BUF_SIZE(256KB)
# 会截断大模块的 text.ll,末尾方法定义查不到 → 误报"方法返回类型无法
# 精确获取"(追加80b 根因,同追加74 _read_source_line 截断)。64MB 上限
# 防御异常超大文件整体读取。)
tf: fileio.File | t.CPtr = fileio.File(path_buf, fileio.MODE.R)
# 按文件实际大小读取 text.ll(固定缓冲区会截断大模块导致末尾定义查不到);64MB 上限防御异常超大文件
tf: rowptr[fileio.File] = fileio.File(path_buf, fileio.MODE.R)
if tf.closed:
stdlib.free(path_buf)
return None
@@ -1313,9 +1129,7 @@ class CallGenBase:
# 在 text.ll 中搜索 define 行包含 search_buf
buf_len: sizet = string.strlen(read_buf)
# [按模块一次性缓存·回写] 已成功读到本模块 text.ll:登记其全部 define 行
# (同模块后续查询 O(1) 命中,不再重开/重读/逐字节重扫)。此处不改动下方
# 原扫描逻辑(保留为未命中路径)。
# 已成功读到本模块 text.ll:登记其全部 define 行供后续 O(1) 命中(下方原扫描保留为未命中路径)
self._CacheModuleRetTyTexts(class_sha1, read_buf, buf_len)
pos: sizet = 0
@@ -1330,10 +1144,7 @@ class CallGenBase:
le += 1
ll: sizet = le - ls
# 检查是否是 define 行
# 快速路径:行首字节不是 'd' 时不可能等于 "define "(strncmp 判等
# 蕴含首个字节相等),直接短路省去绝大多数非 define 行的 strncmp 调用;
# 仅做否定判定,不改变任何原本会命中的行。
# define 行判定:快速路径先看行首字节是否 'd'(仅否定短路,不改变命中结果)
if ll >= 7 and read_buf[ls] == 'd' and string.strncmp(read_buf + ls, "define ", 7) == 0:
# 找 @ 符号
at_pos: sizet = ls + 7
@@ -1365,14 +1176,7 @@ class CallGenBase:
stdlib.free(read_buf)
return result_ty
# ============================================================
# _dep_text_ll_exists - 探测依赖模块 text.ll 是否已生成
#
# 用于区分跨模块返回类型精确解析失败的两种场景:
# 1. text.ll 存在但函数未命中 → 真错误(依赖已编译,函数缺失)
# 2. text.ll 不存在 → 依赖模块尚未翻译(Phase A 按文件顺序翻译,
# main.vp 先于其 import 的 mbuddytest.vp),标记重试待下遍
# ============================================================
# _dep_text_ll_exists:探测 {temp_dir}/{sha1[0:2]}/{sha1}.text.ll 是否存在(1=存在),用于区分「依赖已编译但函数缺失」与「依赖模块尚未翻译」
# ============================================================
def _dep_text_ll_exists(self, pool: rowptr[memhub.MemBuddy],
@@ -1398,10 +1202,7 @@ class CallGenBase:
viperlib.snprintf(path_buf, path_len, "%s/%s/%s.text.ll",
self.Gen.CallGenBase.TempDir, subdir_buf, sha1)
stdlib.free(subdir_buf)
# [廉价存在性判定] 用 GetFileAttributesA(无输出缓冲、不创建文件句柄)替代
# 原「只读打开探测」:原实现只为看是否存在却真开句柄(走 create_file_utf8),
# 本函数 33,244 次调用占总 create_file_utf8 调用 46.6%。写法对齐同仓既有
# IncludesScanner._PathExistsA(该处亦以属性查询替代开文件句柄)。
# 存在性判定用 GetFileAttributesA(属性查询,不开文件句柄)
attr: ULONG = win32file.GetFileAttributesA(path_buf)
exists: int = 1
# INVALID_FILE_ATTRIBUTES = 不存在/不可访问 → 0。
@@ -1414,21 +1215,6 @@ class CallGenBase:
stdlib.free(path_buf)
return exists
# ============================================================
# 属性方法(@property/@x.setter/@x.deleter)与静态/类方法调用辅助
#
# 元数据表由 HandlesClassDef 在类翻译/预注册阶段填充,
# 这里负责把 obj.prop / obj.prop=v / del obj.prop / Class.method()
# 翻译为对应的 {sha1}.ClassName.prop 系列函数调用。
# ============================================================
# 属性方法(@property/@x.setter/@x.deleter)与静态/类方法调用辅助:把 obj.prop / obj.prop=v / del obj.prop / Class.method() 翻译为 {sha1}.ClassName.prop 系列函数调用(元数据表由 HandlesClassDef 填充)
# ============================================================
# _translate_property_call — 翻译属性方法调用
#
# 与 _translate_method_call 的调用名/返回类型解析逻辑一致:
# 1. 优先用模块中已有的函数(精确/后缀匹配,来自 stub 注入)
# 2. 否则用类所属模块的 SHA1 构造 "{sha1}.ClassName.method" 调用名
# 3. 返回类型:函数实类型 → text.ll 查找 → 按方法名推断(默认 i32)
#
# call_head/call_count: 已构建好的参数链表(如 getter 只有 obj 一个参数)
# ============================================================
# _translate_property_call:翻译属性方法调用——优先用模块中已有函数(精确/后缀匹配),否则用类所属模块 SHA1 构造 "{sha1}.ClassName.method";call_head/call_count 为已构建好的参数链表(如 getter 只有 obj 一个参数)
+4 -34
View File
@@ -388,14 +388,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase):
return nl_addr
return_var: llvmlite.Value | t.CPtr = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if return_var is None and mod is not None:
# 模块全局变量:c.Addr(模块全局) → 返回全局变量指针。
# 此前局部符号表查找失败即返回 None → 跌落跨模块函数调用路径
# 生成 call @Addr(裸名,无 obj 定义)→ ld.lld undefined symbol:
# Addr(VKernel main.vp L87 c.Addr(ncpu_cr) 及 4d/8d/a1/c3/d3
# 大量模块全局取地址根因)。必须经 make_global_ref 转成带引号的
# Ptr(Ty) 引用——直接返回 GlobalVariable 节点时其 Ty 是 pointee、
# Name 无 @ 引号,参与 ptrtoint 会渲染成 "ptrtoint void to i64"
# (llc: void type only allowed for function results)。
# 模块全局变量 c.Addr(模块全局) → 返回全局变量指针:必须经 make_global_ref 转成带引号的 Ptr(Ty) 引用(直接返回 GlobalVariable 节点时其 Ty 是 pointee、Name 无 @ 引号,参与 ptrtoint 会渲染成 "ptrtoint void to i64")
gv_ref_nm: llvmlite.GlobalVariable | t.CPtr = self.Gen.ExprGenAttr.find_global_in_module(mod, nm.id)
if gv_ref_nm is not None and gv_ref_nm.Name is not None and gv_ref_nm.Ty is not None:
return_var = self.Gen.ExprGenAttr.make_global_ref(pool, gv_ref_nm.Name, gv_ref_nm.Ty)
@@ -472,13 +465,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase):
at_addr: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(arg0)
if at_addr is None or at_addr.attr is None or at_addr.value is None:
return None
# 跨模块函数地址:c.Addr(serial.puts) → 函数符号本身(@sha1.puts)。
# 此前 Attribute 分支只支持 self/cls 字段 GEP;serial.puts 的 value 是
# 已导入模块名(非局部变量)→ 下方 self_alloca 查找失败 return None →
# 回退 ExprGenAttr 当全局变量处理(创建 @sha1.puts external global i8*
# + load)→ 与 vpi 注入的 puts 函数 declare 同名冲突 → llc
# "redefinition of function '@a6890f1aeecb0aaf.puts'"
# (elf.vp L485 c.Addr(serial.puts) 根因)。
# 跨模块函数地址 c.Addr(serial.puts) → 函数符号本身(@sha1.puts):不可回退 ExprGenAttr 当全局变量处理,否则与 vpi 注入的 puts 函数 declare 同名冲突(llc "redefinition of function")
if at_addr.value.kind() == ast.ASTKind.Name and trans is not None \
and trans._imported_modules is not None:
mod_nm2: ast.Name | t.CPtr = (ast.Name | t.CPtr)(at_addr.value)
@@ -490,17 +477,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase):
if f_ref2 is not None:
fname2: str = llvmlite.function_get_name(f_ref2)
if fname2 is not None:
# 构造带 @ 名的 SSAValue(Ptr(FuncTy)):直接返回
# Function 节点会被 IRBuilder 格式化成 RetTy(void)
# → "bitcast void to i8*"(elf.vp L2874 根因)。
# Function 无 Ty 字段(成员仅 Next/Name/RetTy/
# Params/Blocks/IsDeclared/IsVarArg/Attrs),
# 用 RetTy+Params 重建函数类型。Params 是
# GSList(含 Head 字段),必须经
# function_get_param_head 取链表头——直接
# 把 Params 当 Param 遍历会读 GSList.Head/
# Next 当 Name/Ty → 垃圾参数类型
# "i32 (i1425633744, i8*)"(elf.vp L2874 根因)。
# 构造带 @ 名的 SSAValue(Ptr(FuncTy)):直接返回 Function 节点会被 IRBuilder 格式化成 RetTy(void)→ "bitcast void to i8*"。Function 无 Ty 字段,用 RetTy+Params 重建函数类型;Params 是 GSList,必须经 function_get_param_head 取链表头(当 Param 直接遍历会读出垃圾参数类型)
p_head2: llvmlite.Param | t.CPtr = llvmlite.function_get_param_head(f_ref2)
p_cnt2: sizet = 0
p_cur2: llvmlite.Param | t.CPtr = p_head2
@@ -522,14 +499,7 @@ class CallGenCBuiltin(CallGenBase.CallGenBase):
return llvmlite.SSAValue(
pool, llvmlite.Ptr(pool, fn_ty2), faddr_buf2)
else:
# 模块限定全局:c.Addr(gdt.gp) → 全局符号本身(@gp)。
# 跨模块全局(含 CExport 裸名 @gp)已注入为 external
# global。经 make_global_ref 转成带引号的 Ptr(Ty) 引用
# (直接返回 GlobalVariable 节点会渲染 "ptrtoint void")。
# 此前未处理 → return None → 跌落跨模块函数调用路径
# 生成 call @Addr(裸名,无 obj 定义)→ ld.lld
# undefined symbol: Addr(apic.vp L193 c.Addr(gdt.gp)
# / L199 c.Addr(idt.idt) 根因)。
# 模块限定全局 c.Addr(gdt.gp) → 全局符号本身(@gp):跨模块全局(含 CExport 裸名)已注入为 external global,经 make_global_ref 转成带引号的 Ptr(Ty) 引用(直接返回 GlobalVariable 节点会渲染 "ptrtoint void")
g_ref2: llvmlite.GlobalVariable | t.CPtr = self.Gen.ExprGenAttr.find_global_in_module(
mod, at_addr.attr)
if g_ref2 is not None and g_ref2.Name is not None and g_ref2.Ty is not None:
+7 -71
View File
@@ -687,26 +687,8 @@ class CallGenCall(CallGenBase.CallGenBase):
if obj_ptr is None or cl is None:
return None
# [修复] list 泛型容器方法兜底拦截:接收者被分派为 list 特化类名
# (list[...] / {sha1}.list[...],剥离外层前缀后以 'list' 开头)且方法属
# list 内置集合时,本调用不存在对应 C 函数——若继续走下方 MRO/函数/
# text.ll/vpi stub 返回类型解析必然全部落空 → fatal(mro_ret)
# (AstTest includes/ast/parser.vp _fix_store_ctx 局部变量
# tp_elts: rowptr[list[rowptr[AST]]] 后调 tp_elts.get(i) 根因:函数局部
# 变量经 pre_scan/块作用域登记后,CallGenDispatch 的 list 注解拦截
# 偶发漏判时仍会把 obj_var 当作普通结构体变量送入本函数)。直接转交
# 本类 _translate_list_builtin_call 内联翻译(与 _tc_h6_method_dispatch
# 尾部 list 分支同语义:内联 GEP __data__/__count__/__elem_size__),
# 保证 get/append 等 list 内置方法一律命中、绝不进入 MRO 解析。
# [D25 分派修复] class_name 可能因「注解在 Handle 阶段退化」而为**空串**
# (AssignGenMixin 的 extract_class_name_from_annotation 对受损的注解子
# 节点返回 None → 不写 AnnotClassName)。空串会让下方 list 内建拦截
# (_is_list_builtin_method)与 D25 泛型基类 fail-fast **双双失效**
# ⇒ 调用落到「用 obj_ptr 实际特化名构造函数名」的路径:裸 list* 接收者
# 被误绑到无关特化(如 list[str].get)⇒ 元素类型与真实不符(IR 实证:
# body_list.get 分派到 list[str].get)。
# 接收者的**实际 struct 名**比退化的注解可靠,用它补全判定来源
# (提升到分支外声明,D25 fail-fast 同样使用)。
# [修复] list 泛型容器方法兜底拦截:接收者被分派为 list 特化类名(list[...] / {sha1}.list[...])且方法属 list 内置集合时,本调用不存在对应 C 函数,继续走 MRO/函数/text.ll/vpi 解析必然落空 → fatal;直接转交 _translate_list_builtin_call 内联翻译(GEP __data__/__count__/__elem_size__)
# [D25 分派修复] class_name 可能因注解在 Handle 阶段退化而为空串,使 list 内建拦截(_is_list_builtin_method)与 D25 泛型基类 fail-fast 双双失效 ⇒ 误绑到无关特化。接收者的**实际 struct 名**比退化注解可靠,用它补全判定来源(提升到分支外声明,D25 fail-fast 同样使用)
lb_src: str = class_name
if lb_src is None or lb_src[0] == '\0':
lb_sty_s: llvmlite.LLVMType | t.CPtr = \
@@ -781,32 +763,8 @@ class CallGenCall(CallGenBase.CallGenBase):
if mc_entry is None:
mc_entry = self.Gen.StructGen.find_struct(class_name)
# ============================================================
# [D25 分派根治] 泛型特化接收者以**实际结构体名**为准 + 元素类型未知即 fail-fast
#
# obj_ptr 的 LLVM 结构体类型名是 IR 层真值(实测
# "e94ef9e139461915.list[ast.ASTptr]");而 class_name 来自注解/变量标注,
# 当同一函数作用域内同名变量被**声明/赋值两次**时会退化:实测退化为
# 无关特化 "list[int]"(MatchGenMixin._handle_renum_match 的 body_list)。
# 此时若仍以 class_name 构造 lookup_name(并走 mc_entry.Name 后缀匹配),
# 分派会绑定到错误特化 —— IR 实证:
# %"cast_arg_0.10" = bitcast %"list[ast.ASTptr]"* → %"list[int]"*
# %call = call i32 @"...list[int].get"(...) ; 元素按 i32 读
# %int2ptr_stmt = inttoptr i32 %call to %"AST"* ; 指针截断 → 读垃圾节点崩溃
#
# 判定(仅作用于泛型容器接收者;普通类名不含方括号,既有路径不受影响):
# 接收者实际结构体名去 sha1 前缀后,若基名是已注册的泛型模板
# (list/dict/GSList/...):
# - 含类型实参 '[' ⇒ 以该**实际特化名**覆盖 class_name —— 于是
# _handle_renum_match/_handle_simple_match 与本来就正确的
# _handle_sequence_match(其接收者同为该特化名)走同一条分派路径;
# - 无类型实参 ⇒ 接收者元素类型未知,fail-fast(沿用 D25 口径:
# 禁止静默按默认 list[int] 特化,绝不生成 inttoptr 截断代码)。
# ============================================================
# 共用口径收敛到 CallGenProp._resolve_generic_receiver_name(与下方 list
# 内建判定 _is_list_builtin_receiver 同源,唯一实现):去 sha1 前缀 → 基名是
# 已注册泛型模板且含 '[' ⇒ 用实际特化名覆盖 class_name;裸泛型基类(无 '[')
# ⇒ fail-fast(D25);非泛型模板接收者 ⇒ 返回 None(既有普通类路径逐字节不变)。
# [D25 分派根治] 泛型容器接收者以**实际结构体名**为准:obj_ptr 的 LLVM 结构体类型名是 IR 层真值,class_name 可能因同名变量重复声明而退化到无关特化。判定仅作用于泛型容器接收者(普通类名不含方括号,既有路径不受影响)
# 共用口径收敛到 CallGenProp._resolve_generic_receiver_name(唯一实现):去 sha1 前缀 → 基名是已注册泛型模板且含 '[' ⇒ 覆盖 class_name;裸泛型基类 ⇒ fail-fast;非泛型模板 ⇒ None
mc_act_d: str = self.Gen.CallGenProp._resolve_generic_receiver_name(
pool, mc_struct_ty, method_name, cl)
if mc_act_d is not None:
@@ -852,20 +810,8 @@ class CallGenCall(CallGenBase.CallGenBase):
break
mi += 1
# 兄弟 Mixin 兜底(与 TPC HandlesExprCall 一致):方法不在本类 MRO 链
# (如 BaseB 内 self.helper() 调兄弟 BaseA.helper,BaseB 无 BaseA 的
# 继承信息)。遍历已注册 struct 表找定义类方法,this 由下方
# _adjust_this_for_inherited_method 按组合布局前移 sizeof(定义类)。
# 注意:仅对普通类名兜底——泛型特化类名(list[X] / dict[K,V])是
# 模板生成的特殊容器,其特化函数(如 list[ASTptr].__getitem__)虽
# 在模块中存在,但绝非"兄弟 Mixin"(NoVTableCrossMixinTest 的
# BaseA/BaseB/Combined 场景)。若不过滤,JsonValue.__getitem__ 等
# 普通方法调用在 found_func 未命中时会误绑定到 list 特化函数,
# this 按 list 布局前移 sizeof(list) → 运行期读 -1 崩溃
# (Config.vp Load_project_config root.__getitem__ 根因)。
# 跨模块兄弟 Mixin 兜底命中记录:方法定义类在另一文件(如
# FuncGenBase._mangle_name——调用类 ClassGenVTable 不继承它),
# 本地 IR 无 define,需经候选类模块 sha1 查 text.ll 返回类型。
# 兄弟 Mixin 兜底(与 TPC HandlesExprCall 一致):方法不在本类 MRO 链时遍历已注册 struct 表找定义类方法,this 由下方 _adjust_this_for_inherited_method 按组合布局前移 sizeof(定义类)。仅对普通类名兜底——泛型特化类名(list[X]/dict[K,V])必须过滤,否则普通方法在 found_func 未命中时会误绑到 list 特化函数,this 前移错位导致运行期崩溃
# 跨模块兄弟 Mixin 兜底命中(定义类在另一文件、本地 IR 无 define)需经候选类模块 sha1 查 text.ll 返回类型。
mixin_hit: int = 0
mixin_ret_ty: llvmlite.LLVMType | t.CPtr = None
mixin_anc_name: str = None
@@ -1254,17 +1200,7 @@ class CallGenCall(CallGenBase.CallGenBase):
if kw_calls is not None:
kwc = len(kw_calls)
# 多继承 this 调整(字节偏移法,2026-08-10,与 TPC HandlesExprCall._adjust_this_for_inherited_method 一致):
# 继承方法体按"定义类自身布局"读 self 字段(如 B.get_b 内 GEP(self_B, [0,1])
# 的字节偏移是 B 布局中 b 的 offset 12)。父类字段区在子类中的字节偏移可能
# 不同(如 AB={vtable,a@8,b@12} 中 b 偏移 16),self 需按字节差前移,使父类
# 方法内二次 GEP 恰好落到子类字段。
# delta = 字段在子类中字节偏移 − 字段在父类自身布局中字节偏移
# delta==0(第一父类,字段区前缀对齐)无需调整;delta>0 时按字节 GEP 到
# 父类字段区在子类中的等效基址,再 bitcast 成父类指针。
# 原实现仅覆盖"无 vtable 的父类"(其首字段偏移 0,GEP 到首字段即父类起点),
# 且用 GEP 字段法——对 vtable 父类(首字段偏移非 0,如 B)被直接跳过不调整
# → AB 场景 B.get_b 按 B 布局读 self.b@12 落到 a → AB 字段/方法 FAILED。
# 多继承 this 调整(字节偏移法,与 TPC HandlesExprCall._adjust_this_for_inherited_method 一致):继承方法体按"定义类自身布局"读 self 字段,父类字段区在子类中字节偏移可能不同,故 delta = 字段在子类中字节偏移 − 字段在父类自身布局中字节偏移;delta==0(第一父类,字段区前缀对齐)无需调整,delta>0 时按字节 GEP 到父类字段区在子类中的等效基址再 bitcast 成父类指针
self_arg: llvmlite.Value | t.CPtr = obj_ptr
if mc_anc is not None and string.strcmp(mc_anc, class_name) != 0:
# 判断 mc_anc 是否为 class_name 的第一父类:第一父类字段区前缀对齐
+4 -37
View File
@@ -244,12 +244,7 @@ class CallGenCtor(CallGenBase.CallGenBase):
# 见 translate_constant;否则 coerce inttoptr 字符值当指针崩溃)
fe: GenTypes.FieldEntry | t.CPtr = None
if ctor_entry is not None:
# OOP 结构体字段表第 0 个字段是 __vtable__(vtable 指针占位),
# 构造参数按"用户字段顺序"存储:必须跳过 __vtable__ 字段,
# 否则参数会覆盖 vtable 槽(对象头读到垃圾指针,
# isinstance 的 icmp 全部不匹配 + 方法分派错乱)。
# 原实现直接 _get_field_entry(ctor_entry, ai) 取到 __vtable__
# → GEP Index 0 → 覆盖 vtable(Dog("dog") 场景崩溃漂移根因)。
# 结构体字段表第 0 个字段是 __vtable__(vtable 指针占位):构造参数按用户字段顺序存储,必须跳过 __vtable__,否则参数覆盖 vtable 槽(对象头读到垃圾指针 → isinstance 全部不匹配 + 方法分派错乱)
target_idx: int = 0
for fi in range(ctor_entry.FieldCount):
cand_fe: GenTypes.FieldEntry | t.CPtr = self.Gen.StructGen._get_field_entry(
@@ -726,15 +721,7 @@ class CallGenCtor(CallGenBase.CallGenBase):
if val_bits == 1:
# i1 是布尔值(来自 icmp/fcmp),无符号语义,必须 zext
return llvmlite.build_zext(builder, val, target_ty)
# 统一拓宽方向(与 coerce_to_type 的 _zext_or_sext_val 同源):
# i1/i8 → zext;源解析为无符号(t.CUIntXXT 强转 / 无符号注解变量)→ zext;
# 否则 → sext(默认有符号)。
# 说明:拓宽应保持**源**的数值语义,故由源符号性决定,与目标类型无关
#(C 整数转换规则:有符号源拓宽即保号)。此前本处按「目标名符号性」决策,
# 且 target_name 未知时默认 zext,与 coerce_to_type / should_zext_widen
# 的默认 sext 极性相反 —— 同一窄→宽在强转路径与赋值/比较路径结果相反;
# 且 `u64(x)`(x 为有符号 i32)被强制 zext,与 `y: uint64 = x` 的 sext
# 不一致(负值场景两者位模式不同)。现统一为源符号性决定。
# 统一拓宽方向(与 coerce_to_type 的 _zext_or_sext_val 同源):i1/i8 → zext;源为无符号(t.CUIntXXT 强转 / 无符号注解变量)→ zext;否则 sext(默认有符号)。拓宽保持**源**的数值语义(有符号源拓宽即保号),与目标类型无关
if self.Gen.ExprGenBase._zext_or_sext_val(pool, val, src_node, trans) != 0:
return llvmlite.build_zext(builder, val, target_ty)
return llvmlite.build_sext(builder, val, target_ty)
@@ -820,21 +807,7 @@ class CallGenCtor(CallGenBase.CallGenBase):
if tn is not None:
result.append(tn)
return result
# ============================================================
# 泛型类构造调用辅助: list[str](pool, 8) → __new__ + __before_init__ + __init__
#
# 复用 translate_list_literal 的构造逻辑:
# 1. alloca tmp(特化类临时空间,作为 __new__ 的 self)
# 2. 翻译 cl.args 为参数指针数组
# 3. call __new__(tmp, *args) → storage_ptr
# 4. call __before_init__(storage_ptr)
# 5. call __init__(storage_ptr, *args)
# 6. return storage_ptr
#
# 必须独立于 translate_call 的普通函数路径:spec_name 是类名不是函数名,
# 走裸名路径会被 _lookup_mod_sha1 的 star import 回退误导,用错误的 SHA1
# 且缺少 __new__ 方法名后缀,导致链接报 undefined reference。
# ============================================================
# 泛型类构造调用辅助 list[str](pool, 8):复用 translate_list_literal 的构造逻辑——alloca tmp → 翻译 cl.args 为参数指针数组 → __new__(tmp,*args) → __before_init__ → __init__ → 返回 storage_ptr。必须独立于 translate_call 普通函数路径:spec_name 是类名不是函数名,走裸名会被 _lookup_mod_sha1 的 star import 回退误导
# ============================================================
def _infer_generic_ctor_type_arg(self, pool: rowptr[memhub.MemBuddy],
@@ -1188,13 +1161,7 @@ class CallGenCtor(CallGenBase.CallGenBase):
if tmp is None:
return None
# 翻译实参为参数指针数组(extra_args[slot] = Value 指针):
# 位置实参 → 槽 0..can-1;关键字实参 → 按 __init__ 形参名定位槽位
# ([C②] 与 _find_ctor_type_param_kw_arg_index 共用同一套形参遍历:
# Box(v=42) 的 v → 槽 0;Pair(b="a", a=1) 的 a → 槽 0、b → 槽 1。
# 关键字不能按"出现顺序"追加到位置实参之后——乱序会串位。
# 位置实参优先(先例:_translate_method_call L1406-1418 位置优先、
# 其余按形参名匹配)。此前只翻译 cl.args ⇒ 关键字实参被静默丢弃。)
# 翻译实参为参数指针数组(extra_args[slot] = Value 指针):位置实参 → 槽 0..can-1;关键字实参 → 按 __init__ 形参名定位槽位(与 _find_ctor_type_param_kw_arg_index 共用同一套形参遍历)。位置实参优先,关键字不可按出现顺序追加——乱序会串位
cargs: list[ast.AST | t.CPtr] | t.CPtr = cl.args
can: sizet = 0
if cargs is not None:
+4 -33
View File
@@ -1113,19 +1113,7 @@ class CallGenDispatch(CallGenBase.CallGenBase):
pool, builder, mod, st_nm.id, func_name, cl, trans)
if st_res is not None:
return st_res
# 实例方法经类名调用: ClassName.method(obj, args) → obj.method(args)
# (Python 语义,首参是 self 对象;zdeflate.vp
# zdeflate_stream.find_match(s, data, ...) 根因:_translate_
# static_class_call 对非静态方法返回 None 后,ClassName 被
# 通用路径当模块名 → "跨模块调用找不到模块 SHA1")
# st_nm.id == func_name(类名==方法名,如 CompilerFactory.
# CompilerFactory(...))不是实例方法调用而是构造调用:
# 模块名与类名同名时本分支把模块限定类构造误判为
# ClassName.method(obj,args),首实参当 self 传 →
# _call_method_on_ptr 查 "X.X" 返回类型永远落空 →
# fail-fast(main.vp CompilerFactory 追加84 根因)。
# 跳过本分支,落到 translate_call 尾部的 ctor 检测
# (L7426 模块限定类构造 find_struct_by_module 正确路由)。
# 实例方法经类名调用 ClassName.method(obj, args) → obj.method(args)(Python 语义,首参是 self 对象);但 st_nm.id == func_name(类名==方法名,如 CompilerFactory.CompilerFactory(...))是构造调用而非实例方法调用——本分支会误把模块限定类构造当 ClassName.method(obj,args) 且首实参当 self 传,导致返回类型查 "X.X" 永远落空 fail-fast。故跳过本分支,落到 translate_call 尾部的 ctor 检测
if can >= 1 and cargs is not None \
and string.strcmp(st_nm.id, func_name) != 0:
im_self: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(
@@ -1238,24 +1226,7 @@ class CallGenDispatch(CallGenBase.CallGenBase):
obj_var: llvmlite.Value | t.CPtr = self.Gen.VarGen.lookup_var(
trans.SymTab, method_obj_nm.id)
if obj_var is not None and obj_var.Ty is not None:
# 检查是否为 Ptr(Struct(...))(结构体值变量)
# 或 Ptr(Ptr(Struct(...)))(指针变量,需 load)
# list 泛型容器注解先行识别(早期拦截,供下方各 class 分支共用):
# rowptr[list[rowptr[AST]]] 变量(ast/parser.vp _fix_store_ctx
# 的 tp_elts,元素类在另一模块)LLVM 形态为
# Ptr(Ptr(Struct('list[sha.AST]')));list 特化 struct 不注册为
# 普通类,get_class_name_by_type 或返回 None、或对类型名尾部
# (list[sha.AST]) 后缀匹配误命中内层元素类(如 'AST')→ 下方
# 两个 Ptr 分支的 list 内置拦截落空/错判。AnnotClassName 经
# extract_class_name_from_annotation 已剥最外层 rowptr 存为
# "list[元素]",此处再兜底剥一次(rowptr[list[...]] 形态):
# 内层以 'list' 开头且方法属 list 内置集合时,本 obj.method 调用
# 必须交给本方法尾部 list 内置分支(内联 GEP)翻译——置位
# is_list_annot_builtin 后跳过下方所有 class 方法分支/FakeDuck/
# AnnotClassName 方法构造,绝不进入 _translate_method_call/MRO
# 返回类型解析(函数/text.ll/vpi stub 均未命中 → fatal(mro_ret),
# AstTest ast/parser.vp 根因)。普通类/str/bytes 注解不以 'list'
# 开头 → 不置位,行为不变。
# list 泛型容器注解先行识别(早期拦截,供下方各 class 分支共用):rowptr[list[rowptr[AST]]] 的 LLVM 形态为 Ptr(Ptr(Struct('list[sha.AST]'))),而 list 特化 struct 不注册为普通类,get_class_name_by_type 或返回 None、或后缀误命中内层元素类 ⇒ 下方两个 Ptr 分支的 list 内置拦截落空/错判。此处再兜底剥一次最外层 rowptr,内层以 'list' 开头且方法属 list 内置集合时置位 is_list_annot_builtin,交给本方法尾部 list 内置分支(内联 GEP)翻译
var_entry: GenTypes.VarEntry | t.CPtr = self.Gen.VarGen.lookup_var_entry(
trans.SymTab, method_obj_nm.id)
is_list_annot_builtin: int = 0
@@ -1350,7 +1321,7 @@ class CallGenDispatch(CallGenBase.CallGenBase):
if self.Gen.StructGen.get_struct_type(cls_nm2) is not None:
# list 内置方法跳过 method_call(同 Ptr(Struct) 分支),
# 落到下方 list 内置分支
if self.Gen.CallGenProp._is_list_builtin_receiver(
if self.Gen.CallGenProp._is_list_builtin_receiver(
pool, obj_var, cls_nm2, func_name, cl) == 0:
# 结构体已注册:load obj_ptr 走方法调用
i8_ptr_ty2: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(
@@ -1366,7 +1337,7 @@ class CallGenDispatch(CallGenBase.CallGenBase):
else:
# list 内置方法跳过(结构体未注册路径同样不能
# 构造 {sha1}.{cls}.append 调用名——list 方法无此函数)
if self.Gen.CallGenProp._is_list_builtin_receiver(
if self.Gen.CallGenProp._is_list_builtin_receiver(
pool, obj_var, cls_nm2, func_name, cl) == 0:
# FakeDuck: str 变量方法调用 → 扩展类 _Type/_str
# a.type_id() / a.upper() / " ".isspace() 等
+2 -16
View File
@@ -23,16 +23,7 @@ from . import CallGenBase
class CallGenFd(CallGenBase.CallGenBase):
"""FakeDuck / requires 字段注入 / provider 栈"""
# ============================================================
# _translate_fakeduck_call - FakeDuck: str 变量方法调用 → 扩展类 _Type/_str
#
# 对齐 TPC HandlesExprCall.py L2841-2880:
# ClassName 常规解析失败且对象是 char*(i8*)时,遍历扩展类查找
# {CN}.{method} 函数。对象是 Name 变量且有影子({var}__meta__,fd2
# 创建)时 self=影子;否则(字符串字面量/匿名 str)在入口创建临时
# 影子,写 __data__=obj 值、__mbuddy__=NULL 后作 self,避免
# bitcast i8* → _str* 读取垃圾指针导致 ACCESS_VIOLATION。
# ============================================================
# _translate_fakeduck_call:FakeDuck str 变量方法调用 → 扩展类 _Type/_str(对齐 TPC HandlesExprCall.py L2841-2880)。对象是 Name 且有影子({var}__meta__)时 self=影子;否则在入口创建临时影子(__data__=obj 值、__mbuddy__=NULL)后作 self,避免 bitcast i8* → _str* 读垃圾指针
# ============================================================
def _translate_fakeduck_call(self, pool: rowptr[memhub.MemBuddy],
@@ -173,12 +164,7 @@ class CallGenFd(CallGenBase.CallGenBase):
fd_fname: str = llvmlite.function_get_name(fd_func)
if fd_fname is None:
return None
# 返回类型精确化:跨模块 declare 的 list[T] 特化返回类型可能被简化
# 为 Ptr(i8),调用方变量 alloca 类型随之退化 → parts[0] 走字符下标
# 路径而非 list 内联 __getitem__(printf %s 解引用字符值崩溃)。
# 从定义模块 text.ll 读取精确返回类型(对齐普通方法调用 _translate_method_call)。
# 仅当函数名确为 "{16hex}.{func}" 才提取模块 sha1(判定走唯一真源
# GenApi.sha1_prefix_len,C10 收敛),避免把裸名前 16 字节误当 sha1。
# 返回类型精确化:跨模块 declare 的 list[T] 特化返回类型可能被简化为 Ptr(i8),调用方变量 alloca 类型随之退化 → parts[0] 走字符下标路径而非 list 内联 __getitem__(printf %s 崩溃)。从定义模块 text.ll 读取精确返回类型;仅当函数名确为 "{16hex}.{func}"(判定走唯一真源 GenApi.sha1_prefix_len)才提取模块 sha1
if GenApi.sha1_prefix_len(fd_fname) != 0:
fd_sha1: str = pool.alloc(17)
if fd_sha1 is not None:
+2 -20
View File
@@ -1056,14 +1056,7 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
# Built=-1),名注册后不再改写 ⇒ owner_sha1 为 None 时原线性扫描等价于
# "首个同名条目的 Func",未命中必为 None。索引不可信时回退下方线性扫描。
return None
# 单遍扫描(合并原 owner 优先遍 + 通用遍,语义完全一致但省一半遍历):
# 扫描中同时记录"首个同裸名条目"作通用兜底;一旦发现本模块条目
# (mangled 名以 "{owner_sha1}." 开头,16 位前缀 + '.')立即返回。
# 等价性:原两遍先做 owner 前缀遍(命中即返),再从头做首个同裸名遍;
# 本单遍按同一顺序扫描,owner 命中时机相同,未命中时返回的首个同裸名
# 条目也与原第二遍一致(含 entry.Func 为 None 时返回 None 的旧语义)。
# 前缀相同但非 mangled 名(CExport 等裸名条目名不含 '.')不命中,
# 由首个同裸名兜底返回(同旧语义)。
# 单遍扫描(合并原 owner 优先遍 + 通用遍,语义完全一致但省一半遍历):扫描中同时记录"首个同裸名条目"作通用兜底;一旦发现本模块条目(mangled 名以 "{owner_sha1}." 开头,16 位前缀 + '.')立即返回
fallback_entry: GenTypes.FuncEntry | t.CPtr = None
for i in range(func_count):
entry_addr: uint64 = uint64(funcs) + i * entry_size
@@ -1211,18 +1204,7 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
return 0
# ============================================================
# _build_func_spec - 由函数对象构造整签名 TypeSpec(S3 条目双写)
#
# Kind=TS_FUNC:Ret + 形参 Spec 数组 + ParamCount + IsVarArg。必须在形参
# 全部 add_param 完成后调用。
# 参数沿 `llvmlite.function_get_param_head` 的 Param 链表**两遍遍历**(先计数
# 再填充)——llvmlite 无按下标取参,且无法预知长度以一次分配。
# 注意与 `TSpec.SpecFromLLVMType` 的 Func 分支的区别:后者只能看到
# `LLVMType.Func`(**不含变参信息**,恒置 IsVarArg=0),故此处直接用
# `NewTypeFunc(..., func.IsVarArg)` 构造。
# 失败(池分配失败等)返回 None;调用方保留 None,不静默造伪 spec。
# ============================================================
# _build_func_spec:由函数对象构造整签名 TypeSpec(S3 条目双写)——Kind=TS_FUNC,含 Ret + 形参 Spec 数组 + ParamCount + IsVarArg,必须在形参全部 add_param 完成后调用。参数沿 function_get_param_head 的 Param 链表两遍遍历(先计数再填充,llvmlite 无按下标取参且无法预知长度)。与 TSpec.SpecFromLLVMType 的 Func 分支不同:后者不含变参信息(恒置 IsVarArg=0),故此处直接用 NewTypeFunc(..., func.IsVarArg)。失败返回 None,调用方保留 None 不造伪 spec
def _build_func_spec(self, func: llvmlite.Function | t.CPtr) -> TSpec.TypeSpec | t.CPtr:
"""由函数对象构造整签名 TypeSpec,失败返回 None"""
if func is None:
+18 -123
View File
@@ -133,10 +133,7 @@ class CallGenLib(CallGenBase.CallGenBase):
# 仅裸名(无 '.' 分隔 → 无 sha1 前缀);带前缀走 vpi 定义侧解析
if string.strchr(call_name, 46) is not None:
return head
# 白名单:size_t(i64) 长度参数所在下标(与 _ensure_c_lib_declare 一致)。
# vsnprintf 非变参(第二参即 sizet 长度)——viperlib.vp `export def
# vsnprintf(buf, size: sizet, fmt, ap)`,跨模块裸名调用时字面量 1024
# 固化 i32 vs 定义 i64,与 memset 同族。
# 白名单:size_t(i64) 长度参数所在下标(与 _ensure_c_lib_declare 一致;vsnprintf 第二参即 sizet 长度)
size_idx: int = -1
if string.strcmp(call_name, "memset") == 0: size_idx = 2
elif string.strcmp(call_name, "memcpy") == 0: size_idx = 2
@@ -190,26 +187,7 @@ class CallGenLib(CallGenBase.CallGenBase):
i2 += 1
return new_head
# ============================================================
# _coerce_args_to_def_params - 跨模块调用实参按定义侧形参类型 coerce
#
# B3 同族通用化:_ensure_method_declare 用调用点实参类型建 declare,字面量
# 实参(i32 0/1016/512)把定义侧窄整型形参(i8/i16)固化宽(或把宽形参固化
# 窄)→ declare 与 define 宽度不一致(跨模块 IR/ABI 失配,x86-64 下侥幸正确:
# 调用侧写 32 位寄存器清高 32 位,callee 按 i64 读高位是 0)。本函数在调用点
# 按定义侧(vpi 参数注解)重建/转换实参,随后 _ensure_method_declare 依 head
# 建 declare → call/declare/define 三者一致。
#
# 处理"带 sha1 前缀的被调方限定名"两种形态:
# 1) {sha1}.{func} —— 模块级函数,实参段 0 即首参(无 self 占位);
# 2) {sha1}.{cls}.{method} —— 类方法,vpi 注解段 0 为 self 空段,而实参
# 链首元素正是 self → 段下标与实参下标一一对应(与
# _ensure_method_declare 里直接以 pi 作段索引的约定一致)。
# 此前仅支持形态 1(遇 '.' 直接 return head),方法调用点实参从不按定义侧
# 扩宽 → `default_fs.win_read(0)` 传 i32 而 define 为 uint64(N5)。
# 裸名 C 库函数由 _promote_clib_call_args 与 _ensure_c_lib_declare 标准
# 签名处理。未命中/不可解原样保留(不兜底改型)。
# ============================================================
# _coerce_args_to_def_params:按 vpi 定义侧形参注解把跨模块调用实参 coerce 到目标类型并重建实参链,使 call/declare/define 宽度一致;支持 {sha1}.{func} 与 {sha1}.{cls}.{method} 两形态(段下标与实参下标一一对应),裸名 C 库走 _promote_clib_call_args/_ensure_c_lib_declare,未命中原样保留
def _coerce_args_to_def_params(self, pool: rowptr[memhub.MemBuddy],
builder: llvmlite.IRBuilder | t.CPtr,
call_name: str,
@@ -257,19 +235,11 @@ class CallGenLib(CallGenBase.CallGenBase):
return head
string.strncpy(sha1_buf, call_name, 16)
sha1_buf[16] = '\0'
# 注意:不能用 self._vpi_method_param_annot —— 其形参校验含
# `cls_name is None → return None`,而模块级函数必传 cls_name=None,
# 于是恒返回 None → 本函数永不生效(ABI 收敛首轮/二轮均未生效的根因)。
# 改为直接 ensure 加载 + 直查缓存(键 {sha1}.{func},与 cls_name=None 匹配)。
# 直接 ensure 加载 + 直查缓存(不用 _vpi_method_param_annot:其 cls_name is None 时恒返回 None,模块级函数永不生效)
if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0:
VpiReader._ensure_vpi_param_annot_loaded(sha1_buf, self.Gen.CallGenBase.TempDir)
prm_annots: str = VpiReader._lookup_vpi_param_annots(sha1_buf, cls_nm, meth_nm)
# 泛型特化类({sha}.list[str].__new__):定义侧注解写在**模板类**上
# (vpi 键段为 "list[T]",见 VpiReader._cache_vpi_param_annots_text 的
# 类名取值),特化名的精确键必然遗漏 ⇒ 逐段退查模板注解并把类型形参
# 替换为特化实参(VpiReader._resolve_vpi_param_annot_seg_template,
# 与 _lookup_vpi_ret_annot 的泛型特化归一化匹配同源)。tpl_src=1 使
# 指针口径也对齐定义侧(MemBuddy* → MemManager*)。
# 泛型特化类({sha}.list[str].__new__):定义侧注解写在模板类上,精确键必遗漏 ⇒ tpl_src=1 时退查模板注解并替换类型形参(VpiReader._resolve_vpi_param_annot_seg_template)
tpl_src: int = 0
if prm_annots is None:
if cls_nm is not None and string.strchr(cls_nm, 91) is not None:
@@ -294,7 +264,7 @@ class CallGenLib(CallGenBase.CallGenBase):
else:
tgt_ty = VpiReader._resolve_vpi_param_annot_seg(
pool, sha1_buf, cls_nm, meth_nm, idx, i32_ty, i8_ptr_ty)
if tgt_ty is not None and cur.Ty is not None:
if tgt_ty is not None and cur.Ty is not None:
# 实参 AST(源符号性判定用):链下标 idx 减 self 偏移即 AST 下标
arg_idx: int = idx - self_off
arg_node: ast.AST | t.CPtr = None
@@ -314,23 +284,7 @@ class CallGenLib(CallGenBase.CallGenBase):
stdlib.free(cls_nm)
return new_head
# ============================================================
# _coerce_arg_value_to_ty - 单个实参 Value 转换到目标形参类型
#
# 整型:
# - 同宽 → 原样;
# - 窄化(目标更窄,本簇主场景 i32→i8/i16)→ 常量按目标宽度重建常量,
# SSA 用 trunc(trunc 与符号无关,窄化安全);
# - 扩宽(定义侧形参更宽)→ 常量按目标宽度重建常量;SSA 按**源**符号性
# 扩宽(zext/sext,真源 ExprGenBase._zext_or_sext_val;同规则先例
# ExprGenBase.coerce_to_type / CallGenCtor._translate_t_type_cast /
# CallGenLib._promote_clib_call_args)→ declare 由扩宽后的实参链建立,
# declare/call/define 三者同宽(此前 SSA 原样返回 → 与定义侧宽度不一致)。
# 指针:实参与目标皆指针且"泛型 i8* 性"不同时 bitcast(如 i32*→i8*),
# 使 declare 指针类型与 define 一致;同为 i8* 或同为具体 struct 指针
# 则原样保留(避免把 list*/Struct* 无谓降级为 i8* 的 IR 噪声)。
# 其它(结构体值/void 等)→ 原样保留。不可转换亦原样返回(不兜底改型)。
# ============================================================
# _coerce_arg_value_to_ty:单个实参 Value 转换到目标形参类型——整型同宽原样、窄化 trunc/按宽度重建常量、扩宽按源符号性 zext/sext;指针 i8* 性不同则 bitcast;其余原样返回(不可转换亦不改型)
def _coerce_arg_value_to_ty(self, pool: rowptr[memhub.MemBuddy],
builder: llvmlite.IRBuilder | t.CPtr,
val: llvmlite.Value | t.CPtr,
@@ -365,31 +319,14 @@ class CallGenLib(CallGenBase.CallGenBase):
builder, val, tgt_ty)
if nv_t is not None:
return nv_t
# 扩宽(实参窄于形参,N5):字面量/默认值此前保持 i32
# 而 declare/define 已是 i64(_ensure_method_declare
# 只改 declare 不改实参)→ 调用实参与签名失配。
# 仅编译期常量在此重建:const_int 按十进制值打印,
# i32 -1 → i64 -1(sext 语义)、i32 4294967295 →
# i64 4294967295(zext 语义),各自保持原类型数值,
# 无需 AST 判符号性。
# 扩宽(实参窄于形参):编译期常量按十进制值重建,各自保持原类型数值,无需 AST 判符号性
if val.IsConst != 0:
nv_w: llvmlite.Value | t.CPtr = llvmlite.const_int(
pool, bits_b, val.IntVal)
if nv_w is not None:
return nv_w
return val
# 非常量(SSA)扩宽:此前原样返回,declare 与 call 保持
# 调用点窄宽度,而定义侧(vpi 形参注解)更宽 → 同一
# 符号 declare/call 与 define 宽度不一致(includes/
# requests/__init__.vp `string.strncpy(..., host_len)`
# 3 参 i32 而 string.vp 定义 `n: t.CSizeT`=i64;同模块/
# 同依赖的 strncpy 常量实参分支已扩宽成 i64,SSA 实参
# 分支未扩宽 → 同一模块两条 declare 宽度不同)。
# 扩宽方向由**源**符号性决定(与 coerce_to_type /
# CallGenCtor 同一真源 ExprGenBase._zext_or_sext_val:
# i1/i8、无符号源 → zext;否则 sext),与目标类型无关。
# 扩宽后 declare 由本函数返回的新实参链建立(param_head
# → _ensure_method_declare),declare/call/define 三者同宽。
# 非常量(SSA)扩宽:扩宽方向由源符号性决定(i1/i8、无符号源 → zext,否则 sext),使 declare/call/define 三者同宽
if bits_b > bits_a:
if self.Gen.ExprGenBase._zext_or_sext_val(
pool, val, node, trans) != 0:
@@ -428,12 +365,7 @@ class CallGenLib(CallGenBase.CallGenBase):
if nv_p is not None:
return nv_p
elif tpl_src != 0:
# 模板注解来源:两侧同为具体结构体指针但基类型不同
# (实参 MemBuddy* vs 定义侧形参 MemManager*,注解
# `memhub.MemManager | t.CPtr`)→ bitcast 使 declare
# 指针口径与 define 一致(位不变、ABI 无影响)。
# 非模板来源保持既有"具体指针原样保留"策略,
# 避免把 list*/Struct* 无谓降级为 i8* 的 IR 噪声。
# 模板注解来源:两侧同为具体结构体指针但基类型不同(实参 MemBuddy* vs 定义侧形参 MemManager*)⇒ bitcast 使 declare 指针口径与 define 一致
nv_p2: llvmlite.Value | t.CPtr = llvmlite.build_bitcast(
builder, val, tgt_ty)
if nv_p2 is not None:
@@ -441,11 +373,7 @@ class CallGenLib(CallGenBase.CallGenBase):
case _:
pass
case _:
# 聚合值 → 指针:rvalue 结构体/数组落栈取址(N18 同源补齐)。
# FuncGenDef 形参一律 lowering 为 Ptr(struct/array),Viper 无
# "按值传结构体"形参;跨模块(declare 由 stub 建、found_func
# 为 None)时上游 coerce_to_type 不参与,故在此按 tgt_ty 为
# 指针 + 实参为聚合形态补 materialize。
# 聚合值 → 指针:rvalue 结构体/数组落栈取址(FuncGenDef 形参一律 lowering 为 Ptr,跨模块时上游 coerce_to_type 不参与)
match tgt_ty:
case llvmlite.LLVMType.Ptr(_):
vk_a: int = val.Ty.kind()
@@ -546,9 +474,7 @@ class CallGenLib(CallGenBase.CallGenBase):
return llvmlite.Int64(pool)
if string.strcmp(func_name, "atoll") == 0:
return llvmlite.Int64(pool)
# llvmlite 函数名长度访问器返回 t.CSizeT(i64):stub/vpi 尚未注入时若
# 默认 i32 会与 declare 的 i64 签名不一致(call i32 vs declare i64),
# 与 function_get_name 指针白名单同理须登记返回类型。
# llvmlite 函数名长度访问器返回 sizet(i64):默认 i32 与 declare 的 i64 签名不一致,须登记
if string.strcmp(bare_name, "function_get_name_len") == 0 or \
string.strcmp(func_name, "function_get_name_len") == 0:
return llvmlite.Int64(pool)
@@ -590,9 +516,7 @@ class CallGenLib(CallGenBase.CallGenBase):
return i8_ptr_ty
if string.strcmp(func_name, "GetModuleHandleW") == 0:
return i8_ptr_ty
# GetCommandLineA/W 返回 LPSTR(i8*):缺则 64 位指针被截成 i32 →
# inttoptr 后 strlen 读到无效地址(SysLibTest argv 段 ASAN 崩溃,
# 非 ASAN 环境随机表现:IDE 0xC0000005 / 部分环境侥幸可读)。
# GetCommandLineA/W 返回 LPSTR(i8*):缺则 64 位指针被截成 i32,解引用读到无效地址
if string.strcmp(func_name, "GetCommandLineA") == 0:
return i8_ptr_ty
if string.strcmp(func_name, "GetCommandLineW") == 0:
@@ -626,9 +550,7 @@ class CallGenLib(CallGenBase.CallGenBase):
if string.strcmp(func_name, "GlobalSize") == 0:
return llvmlite.Int64(pool)
# llvmlite build_* 函数返回类型推断
# stubs 翻译时未注入,需推断返回类型避免指针被截断为 i32
# 大多数 build_* 函数返回 Value*(指针),少数终止指令返回 void
# llvmlite build_* 返回类型推断:多数返回 Value*,少数终止指令返回 void(stubs 未注入时防指针截断)
if string.strncmp(func_name, "build_", 6) == 0:
# 返回 void 的 build_* 函数(终止指令和无返回值指令)
if string.strcmp(func_name, "build_ret") == 0 or string.strcmp(func_name, "build_ret_void") == 0 or \
@@ -639,11 +561,7 @@ class CallGenLib(CallGenBase.CallGenBase):
# 其他 build_* 函数返回 Value*(指针)
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
# llvmlite 类型工厂函数返回 LLVMType*(指针)
# Int1/Int8/Int16/Int32/Int64/Ptr/Func/Array/Struct/Void/Half/Float/Double/FP128/Label
# new_param_node/new_phi_incoming 返回 ParamNode*/PhiIncoming*(指针)
# const_int32/const_int64/ConstFloat/ConstZero/ConstNull/ConstInt 返回 Value*(指针)
# function_get_name/param_get_ty 等访问器返回指针
# llvmlite 类型工厂 / 常量构造 / 访问器函数返回指针(LLVMType*/Value*):Int*/Ptr/Func/Array/Struct/Void 等、const_*/Const*、function_get_*/param_get_*
if string.strcmp(func_name, "Int1") == 0 or string.strcmp(func_name, "Int8") == 0 or string.strcmp(func_name, "Int16") == 0 or \
string.strcmp(func_name, "Int32") == 0 or string.strcmp(func_name, "Int64") == 0 or string.strcmp(func_name, "Ptr") == 0 or \
string.strcmp(func_name, "Func") == 0 or string.strcmp(func_name, "Array") == 0 or string.strcmp(func_name, "Struct") == 0 or \
@@ -659,11 +577,7 @@ class CallGenLib(CallGenBase.CallGenBase):
string.strcmp(func_name, "function_get_param_head") == 0 or string.strcmp(func_name, "param_get_next") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
# 默认 i32:无登记、无模块归属、白名单全部未命中的外部函数。
# fatal_if_unknown=1(纯裸名外部调用,无法收敛)→ fail-fast 报错:
# 默认 i32 会截断指针/i64,静默生成导致运行时数据损坏(不兜底原则)。
# fatal_if_unknown=0(Phase A 中间遍,_deps_pending 下遍重译收敛)→
# 保持默认 i32 占位,不输出 WARN(下遍依赖就绪后走 text.ll/vpi 精确解析)。
# 默认 i32 占位:fatal_if_unknown=1 → fail-fast 报错;=0(Phase A 中间遍)保持占位,不输出 WARN
if fatal_if_unknown != 0:
self.Gen.TypeGenErr.fatal_error(
node,
@@ -704,10 +618,7 @@ class CallGenLib(CallGenBase.CallGenBase):
if string.strcmp(api_name, func_name) == 0:
return i64_ty
# 内存分配函数:size 参数在声明中为 i64(size_t),裸名回退时
# 不提升则 call(i32 size) 与 define(i64 size) 不匹配(desktop start
# 第 4 次 mm.malloc(w*4) 返回值被破坏收到 0x0 根因;HelloWorld
# vpsdk 的 malloc 同理)。calloc/realloc 的 size 参数在 idx 1。
# 内存分配函数:size 参数声明为 i64(size_t),裸名回退时不提升则 call 与 define 宽度不匹配;calloc/realloc 的 size 在 idx 1
if string.strcmp(func_name, "malloc") == 0 \
or string.strcmp(func_name, "alloc") == 0 \
or string.strcmp(func_name, "alloc_buf") == 0:
@@ -819,18 +730,7 @@ class CallGenLib(CallGenBase.CallGenBase):
# 返回 dst(匹配 C memcpy 语义: 返回 dest 指针)
return dst_coerced
# ============================================================
# _translate_call_with_kwargs — 带关键字参数的函数调用
#
# 支持两种语法:
# add(b=4, a=3) — 全关键字乱序
# greet("Hello", name="World") — 位置 + 关键字混合
#
# 策略: 遍历 found_func.Params 链表,对每个参数:
# 1. 若位置参数仍有剩余(param_idx < can),取位置参数
# 2. 否则按参数名匹配关键字参数
# 3. 按参数顺序构建调用参数链表
# ============================================================
# _translate_call_with_kwargs:带关键字参数的函数调用,按 found_func.Params 顺序构建实参链表(位置参数优先,否则按参数名匹配关键字)
# ============================================================
def _translate_call_with_kwargs(self, pool: rowptr[memhub.MemBuddy],
@@ -906,12 +806,7 @@ class CallGenLib(CallGenBase.CallGenBase):
param_idx += 1
cur_param = llvmlite.param_get_next(cur_param)
# 使用模块中的实际函数名(可能带 SHA1 前缀,使用访问器)
# declare 和 define 统一用 found_func.Name:
# - 普通函数的 declare/define 都带 SHA1 前缀(forward_declare_functions 和 stub 注入都用 mangled_name)
# - t.CExtern/t.State/t.CExport 函数的 declare/define 都用裸名
# 旧逻辑(IsDeclared==1 时用 func_name 裸名)是错误的:stub 注入的 declare Name 已带 SHA1 前缀,
# 用裸名会导致 call @_PathToModuleName 而 define 是 @"sha1._PathToModuleName",链接报 undefined reference
# declare 与 define 统一用 found_func.Name:普通函数带 SHA1 前缀,t.CExtern/t.State/t.CExport 用裸名(用裸名会造成 call 与 define 符号名不一致)
call_name: str = func_name
found_name: str = llvmlite.function_get_name(found_func)
if found_name is not None:
+4 -59
View File
@@ -409,21 +409,7 @@ class CallGenMixin(CallGenBase.CallGenBase):
if tp_count_bare > 0:
inferred_args_bare: list[str] | t.CPtr = list[str](pool, 8)
if inferred_args_bare is not None:
# 类型形参 → 位置实参对齐:取 __init__ 签名中注解为该
# 类型形参的形参位次(Vector[T].__init__(self, pool,
# capacity, _hint: T) ⇒ T 对齐第 3 个位置实参)。
# 不能按「类型形参序号 = 实参序号」硬对齐:第 1 个实参
# 常是 pool/size 等非 T 参数。此前先把全部实参推断结果
# 压成 hint 列表并过滤掉纯 "int"(想借此排除非 T 参数),
# 再按形参序号取 hint[tpi] —— 整型实参被剔除后 hint 的
# 下标与形参序号错位(Pair(1,"a") 误判为 Pair<str,str>)。
# 签名不可得(无 __init__/无 T 注解形参)时退化为同一
# 序号(第 tpi 个位置实参)。
# [C①] 注解上下文(赋值目标注解 / 函数返回注解)提供的
# 类型实参:优先级 = 显式 [...] > 位置/关键字实参推断 >
# 注解上下文 > fail-fast。仅当本次构造就是注解赋值/return
# 的直接值(identity 判定见 helper 内)时才返回非 None;
# 注解 → 类型实参的解析复用 _extract_type_args_from_slice。
# 类型形参 → 位置实参对齐:取 __init__ 签名中注解为该类型形参的形参位次(Vector[T].__init__(self, pool, capacity, _hint: T) ⇒ T 对齐第 3 个位置实参)。不能按「类型形参序号 = 实参序号」硬对齐(第 1 个实参常是 pool/size 等非 T 参数);签名不可得(无 __init__/无 T 注解形参)时退化为同一序号。[C①] 注解上下文提供的类型实参优先级:显式 [...] > 位置/关键字实参推断 > 注解上下文 > fail-fast,仅当本次构造就是注解赋值/return 的直接值(identity 判定)时才返回非 None
ctx_args_bare: list[str] | t.CPtr = self.Gen.CallGenCtor._infer_ctor_type_args_from_ctx(
pool, trans, func_name, int(tp_count_bare), cl)
tpi_bare: sizet = 0
@@ -618,18 +604,7 @@ class CallGenMixin(CallGenBase.CallGenBase):
if decor_wrap_nm is not None:
found_func = self.find_func_in_module(mod, decor_wrap_nm)
else:
# 本模块已定义函数名的裸名调用:先按裸名在模块函数表
# (FuncEntry 数组,forward_declare_functions 已把所有模块级
# 函数登记进 trans._funcs:Name=裸名、Func=其 declare/define)
# 精确命中,再回落 find_func_in_module 的 LLVM 模块链表扫描。
# 根因:同模块互调(string.vp atof_out 内 atof(src) → 本模块
# @{sha1}.atof)此前只靠 find_func_in_module 扫 FuncHead,
# 链表扫描漏掉本模块 atof 时 found_func=None → 误走 C 库自动
# declare / from_imports 回退,最终报"裸名调用 'atof' 未定义且
# 未导入"。函数表才是同模块函数名的权威注册表(即注释 L540-541
# "优先从函数表按裸名精确匹配" 的原意,此处补落地)。模块表只含
# 模块级函数(无方法),CExport 函数表中 Func 名即裸名,复用
# 不改变既有 CExport/非 CExport 的符号语义。
# 本模块已定义函数名的裸名调用:先按裸名在模块函数表(FuncEntry 数组,forward_declare_functions 已把所有模块级函数以 Name=裸名、Func=其 declare/define 登记进 trans._funcs)精确命中,再回落 find_func_in_module 的 LLVM 模块链表扫描。函数表才是同模块函数名的权威注册表(链表扫描漏掉本模块定义时会误走 C 库自动 declare / from_imports 回退)。模块表只含模块级函数(无方法),CExport 条目 Func 名即裸名,复用不改变既有符号语义
mod_fn_tb: llvmlite.Function | t.CPtr = None
if actual_funcs is not None:
# 同模块函数优先:_funcs 表在 forward_declare_functions 之前
@@ -946,21 +921,7 @@ class CallGenMixin(CallGenBase.CallGenBase):
# 本遍仅临时占位。
call_ret_ty = self.Gen.CallGenLib._infer_external_func_ret_ty(pool, func_name, node, 0)
else:
# 裸名调用(cl.func 是 Name,mod_ret_sha1=None)。两类来源:
# 1. from-import 的函数(如 SSAValue from .__values)→ 后续
# L1193-1251 解析模块归属构造 mangled call_name,L1272 经
# text.ll/vpi 精确解析返回类型(可收敛)→ fatal_if_unknown=0
# 仅临时占位,不 fail-fast。
# 2. 纯外部 C 函数(无登记、无模块归属)→ 白名单未命中无法收敛
# → fail-fast(fatal_if_unknown=1)。
# 闭包变量(嵌套函数 def inner() 提升为 __closure_inner)也是裸名:
# 返回类型由 translate_closure_call 固定按 i32 处理(L842-888 闭包
# 路径),不可收敛但可解析 → 必须按可解析占位,否则被 fail-fast
# 误杀(ExpandKeywordsTest "function get_ten 内 return inner()" 根因)。
# 同理:当前作用域局部变量持有函数指针/闭包(如 f: t.CPtr =
# make_counter() 后 f(),ClosureCallTest 根因)——调用方在 L842-888
# 经 lookup_var 命中闭包/函数指针路径路由,不依赖外部返回类型推断,
# 也必须按可解析占位。
# 裸名调用(cl.func 是 Name,mod_ret_sha1=None)两类来源:from-import 的函数(后续解析模块归属构造 mangled call_name 并经 text.ll/vpi 精确解析返回类型,可收敛)→ fatal_if_unknown=0 仅临时占位,不 fail-fast;纯外部 C 函数(无登记、无模块归属,白名单未命中)→ fail-fast(fatal_if_unknown=1)。闭包变量与持有函数指针/闭包的局部变量同样按可解析占位——返回类型由 translate_closure_call/lookup_var 路径固定处理,不依赖外部返回类型推断,否则被 fail-fast 误杀
bare_resolvable: int = 0
if self.Gen.CallGenLeafKit.is_closure_name(func_name) != 0:
bare_resolvable = 1
@@ -1225,23 +1186,7 @@ class CallGenMixin(CallGenBase.CallGenBase):
tail.Next = df_arg_val
tail = df_arg_val
elif found_func is not None:
# 默认参数缺失/表信息不全时的零值兜底:
# 1) df_param_count>0 但 df_defaults 为空(函数表有参数数、无默认值记录)
# → 现有 if 不满足(要求 df_defaults 非 None 且 default_count>0),
# 缺参调用按 x64 约定读 [rsp+0x28] 栈参数区垃圾 → ASan 崩溃。
# 2) 调用方文件先于定义文件编译(编译顺序问题,如 Phase1Main 调
# StubMergerBuild._BuildReachableSha1Set 缺 app_deps),
# find_func_entry_in_table/find_func_defaults 均查不到(df_param_count=0)
# → 同样缺参崩溃。
# 两种情况都用 found_func 实际参数数兜底,缺参填零值
# (对齐 TPC _fill_default_args 的零值兜底语义;found_func 是 stub
# declare,参数数与 define 一致,按它填充安全)。
# 注意排除 EH 输出参数(%__eh_msg_out__/%__eh_code_out__):
# 含 raise 的函数定义尾部注入 2 个 EH 形参,若计入 fc_cnt 会为
# raise_disk() 这类无参调用补 2 个 zeroinitializer 占位,随后
# _emit_eh_guarded_call 又追加真实 EH 实参 → call 4 参 vs define
# 2 参 → x64 ABI 错位 → 函数写 null 输出指针 → 0xC0000005
# (TestFBProject raise_disk 崩溃根因)。
# 默认参数缺失/表信息不全时的零值兜底:函数表有参数数但无默认值记录、或调用方先于定义文件编译导致查不到默认值时,用 found_func 实际参数数(stub declare,与 define 一致)兜底并缺参填零值(对齐 TPC _fill_default_args 语义)。注意排除 EH 输出参数(%__eh_msg_out__/%__eh_code_out__),否则会与 _emit_eh_guarded_call 追加的真实 EH 实参重复导致 ABI 错位
fc_cnt: int = 0
fc_p: llvmlite.Param | t.CPtr = llvmlite.function_get_param_head(found_func)
while fc_p is not None:
+2 -24
View File
@@ -430,18 +430,7 @@ class CallGenProp(CallGenBase.CallGenBase):
pfx: int = GenApi.sha1_prefix_len(name)
return name + pfx if pfx != 0 else name
# ============================================================
# _resolve_generic_receiver_name - 解析接收者实际泛型特化名(IR 真值)
#
# 接收者的实际 LLVM 结构体类型名是 IR 层真值;注解/注册名可能因
# 「同一函数作用域内同名变量被声明/赋值两次」退化(如误为 "list[int]")
# ⇒ 分派错特化。故泛型判定统一以实际类型名为准:
# 去 sha1 前缀 → 取 '[' 前基名 → 基名是已注册泛型模板时:
# - 含类型实参 '[' ⇒ 返回实际特化名(去 sha1 后)
# - 无类型实参 ⇒ 接收者元素类型未知,fail-fast(D25 口径)
# 基名非泛型模板 | 实际类型不可得 ⇒ 返回 None(调用方按各自口径回退)
# 与 CallGenCall._translate_method_call 的 D25 分派修复共用本函数(唯一口径)。
# ============================================================
# _resolve_generic_receiver_name:解析接收者实际泛型特化名(IR 真值)——接收者实际 LLVM 结构体类型名比可能退化的注解/注册名可靠,故泛型判定以此为准:去 sha1 前缀取 '[' 前基名,基名是已注册泛型模板时含类型实参 ⇒ 返回实际特化名,无类型实参 ⇒ fail-fast(D25);基名非泛型模板或实际类型不可得 ⇒ 返回 None(调用方各自回退)。与 CallGenCall._translate_method_call 的 D25 分派修复共用本函数(唯一口径)
def _resolve_generic_receiver_name(self, pool: rowptr[memhub.MemBuddy],
struct_ty: llvmlite.LLVMType | t.CPtr,
method_name: str,
@@ -481,18 +470,7 @@ class CallGenProp(CallGenBase.CallGenBase):
"TypeError")
return None
# ============================================================
# _is_list_builtin_receiver - 基于接收者实际类型判定 list 内建方法
#
# 触发判据与 _resolve_generic_receiver_name 同源:优先采用接收者实际 LLVM
# 结构体类型名(去 sha1 判基名是否为 list 等),而非可能退化的注解名。
# - 实际类型可用:
# * 泛型模板接收者 → 含 '[' 用实际特化名判定;裸基类 fail-fast(D25);
# * 非泛型模板(普通类)→ 用实际短名判定(基名非 list ⇒ 不命中)。
# - 实际类型不可得(get_struct_type_from_value 返回 None)→ 回退原注解名/
# 注册名判定(信息缺失下的既有行为,如 str 影子类接收者)。
# 返回: 1=list 内置方法 / 0=非(与 _is_list_builtin_method 结果口径一致)
# ============================================================
# _is_list_builtin_receiver:基于接收者实际类型判定 list 内建方法,判据与 _resolve_generic_receiver_name 同源——优先采用接收者实际 LLVM 结构体类型名(去 sha1 判基名),不可得时回退注解名/注册名判定。返回 1=list 内置方法 / 0=非
def _is_list_builtin_receiver(self, pool: rowptr[memhub.MemBuddy],
recv_val: llvmlite.Value | t.CPtr,
fallback_name: str,
+1 -7
View File
@@ -302,13 +302,7 @@ class CallGenVa(CallGenBase.CallGenBase):
if func is not None and func.IsVarArg == 1:
fixed_count = self._get_func_fixed_param_count(func)
else:
# 两种情况回退已知变参 C 库函数签名表(snprintf 等):
# 1. func 为 None——跨模块 stub 未注入(CallGenMixin 注释:stubs
# 在翻译后才注入,翻译时跨模块函数 found_func 恒 None);
# 2. func 存在但 IsVarArg=0——该 declare 由 _ensure_method_declare
# 按实参类型临时创建(无 ... 标记),不能代表真实签名。
# 否则 i32 变参不提升,Win64 8 字节槽只写 4 字节 →
# va_arg(i64) 高 4 字节读残留栈垃圾(clean 日志 0x7ff700000052 根因)。
# 两种情况回退已知变参 C 库函数签名表(snprintf 等):func 为 None(跨模块 stub 未注入,翻译时 found_func 恒 None);func 存在但 IsVarArg=0(该 declare 由 _ensure_method_declare 按实参类型临时创建,无 ... 标记,不代表真实签名)。否则 i32 变参不提升,Win64 8 字节槽只写 4 字节 → va_arg(i64) 高 4 字节读残留栈垃圾
fixed_count = self._get_known_vararg_cfunc_fixed_count(call_name)
if fixed_count < 0:
return 0
+1 -12
View File
@@ -22,18 +22,7 @@ from . import CallGenBase
class CallGenVt(CallGenBase.CallGenBase):
"""vtable 槽位写入"""
# ============================================================
# _write_vtable_slot / _write_vtable_attr — vtable 方法槽位直接写入
#
# serial0_file.open = serial0_open(devfs 驱动注册模式):把模块级函数
# 指针写入对象 vtable 的方法槽位。vtable 方法不是数据字段(FieldEntry),
# get_attribute_ptr 查不到 → 此前 fatal_error "属性写入失败"。
# 对齐 TPC HandlesAssign.py L905-949 的写时复制:
# 对象 vtable 仍指向类默认 vtable 时,先复制一份(internal 全局 +
# llvm.memcpy)并让对象指向副本,再 GEP method_index 槽位 store
# 函数指针(i8*)。多次写入互不覆盖(第二次起 vtable 已非默认,
# 跳过复制直接写槽位)。
# ============================================================
# _write_vtable_slot / _write_vtable_attr:把模块级函数指针写入对象 vtable 的方法槽位(devfs 驱动注册模式,对齐 TPC HandlesAssign.py L905-949 写时复制)——vtable 方法不是数据字段,get_attribute_ptr 查不到;对象 vtable 仍指向类默认 vtable 时先复制一份(internal 全局 + llvm.memcpy)并让对象指向副本,再 GEP method_index 槽位 store i8*。多次写入互不覆盖
# ============================================================
def _write_vtable_slot(self, pool: rowptr[memhub.MemBuddy],
+4 -8
View File
@@ -127,11 +127,9 @@ class ClassGenBase:
GenericClassNames: list[str] | t.CPtr
GenericClassNodes: list[ast.ClassDef | t.CPtr] | t.CPtr
GenericClassSha1s: list[str] | t.CPtr
# 泛型模板来源文件路径(与上面三表同序)。特化翻译模板方法体时
# node.lineno 是模板文件行号,若 CurrentFile 仍是调用方文件,
# fatal_error 会把模板行号套到调用方文件上显示错位(如 vector.vp
# 的 T(...) 错报成 vectortest.vp 的 return 0)。此表用于特化期间
# 临时切换 CurrentFile 到模板文件,翻译完恢复。
# 泛型模板来源文件路径(与上面三表同序)。特化翻译模板方法体时 node.lineno 是模板
# 文件行号,若 CurrentFile 仍是调用方文件,fatal_error 会把模板行号套到调用方文件上
# 显示错位。此表用于特化期间临时切换 CurrentFile 到模板文件,翻译完恢复。
GenericClassFiles: list[str] | t.CPtr
# 泛型模板类型参数名表(与 GenericClassNames 同序,stdlib 深拷贝)。
# 模板节点 type_params 是 Phase 1 pool 分配的 list,跨 Phase 引用时内容
@@ -976,9 +974,7 @@ class ClassGenBase:
bname: str = self._get_base_name(base_node, pool)
if bname is not None:
# 基类别名解析(from t import CStruct as cs → cs 实际是 CStruct)。
# ClassGen 拆分(47ac2dd)时丢失,EdgeCaseTest Test12 class
# CsPoint(cs) 回归根因:'cs' 被当真实父类 → find_struct("cs")
# 失败 → 类 defer 永不注册 → 变量注解解析失败。
# 不解析会把别名当真实父类 → find_struct("cs") 失败 → 类 defer 永不注册。
bname = self._resolve_base_alias(bname, trans)
if self._is_marker_base(bname) == 0:
return bname
+18 -32
View File
@@ -51,11 +51,9 @@ class ClassGenDef(ClassGenBase.ClassGenBase):
self.Gen.StructGen.add_visible_struct(pool, class_name)
# 枚举类:交给 HandlesEnum 处理
# [D32 fail-fast] 分派歧义检测:REnum 优先于 CEnum/CUnion。若某类同时被判
# 为枚举,说明它被 `_is_enum_class` 抢先截胡 → 走 _translate_enum_def ⇒
# **变体表永不注册** ⇒ `_is_renum_class_name` 恒 0、变体构造拿不到 tag
# (includes/llvmlite/__types.vp L703 `LLVMType.Metadata()` 被当外部函数
# 根因)。两者语义互斥,禁止静默按枚举处理。
# 分派歧义检测(fail-fast):REnum 优先于 CEnum/CUnion。若某类同时被判为枚举,
# 说明被 `_is_enum_class` 抢先截胡 → 走 _translate_enum_def ⇒ 变体表永不注册
# ⇒ _is_renum_class_name 恒 0、变体构造拿不到 tag。两者语义互斥,禁止静默按枚举处理。
if self.Gen.ClassGenBase._is_enum_class(cd, trans) == 1:
if self.Gen.ClassGenBase._is_renum_class(cd, trans) == 1:
self.Gen.TypeGenErr.fatal_error(
@@ -89,11 +87,9 @@ class ClassGenDef(ClassGenBase.ClassGenBase):
# 检查是否已注册(用 SHA1 区分跨模块同名类)
existing: GenTypes.StructEntry | t.CPtr = self.Gen.StructGen.find_struct_by_module(
class_name, trans.ModuleSha1)
# 空类注册不完整检测:existing FieldCount==0 但源类体声明了字段
# (vpi 注入/注册缺陷,win32base SECURITY_ATTRIBUTES / win32file
# WIN32_FIND_DATAA 根因)。此前标记 _deps_pending 延迟 → 空 entry 持久
# 存在导致多遍死锁不收敛。改为中和旧 entry(置空 Name + Ty=None,使其
# 不可再被 find_struct_by_module 命中),走下方完整注册路径重建。
# 空类注册不完整检测:existing FieldCount==0 但源类体声明了字段(vpi 注入/注册缺陷)。
# 标记 _deps_pending 延迟会让空 entry 持久存在、多遍死锁不收敛 ⇒ 改为中和旧 entry
# (置空 Name + Ty=None,使 find_struct_by_module 不再命中),走下方完整注册路径重建。
needs_rereg: int = 0
if existing is not None:
if existing.FieldCount == 0 and trans._declare_only == 0:
@@ -241,15 +237,11 @@ class ClassGenDef(ClassGenBase.ClassGenBase):
# 否则子类无法继承父类字段 → field_count==0 → __sizeof__ 失败
parent_entry = self.Gen.StructGen.find_struct(pname)
if parent_entry is None:
# 父类尚未注册(依赖模块未翻译:Phase A 按文件序,子类模块可能
# 先于父类模块,如 Generator.vp G < TypeGenMixin.vp T)。此时
# **不能**继续注册——StructTable 在 StdlibAlloc=1 时用 perm_pool
# 分配(init_struct_table L136-140),残缺条目跨 pass 存活,
# 后续 pass 的 existing 早退(L2995-3006)永远无法修复 → 子类
# __init__ 写父类字段(self.TraceFrames)每遍永久失败。
# 整类 defer(不注册);模块级变量/函数参数解析失败静默降级 i32
# (handle_module_level_var L422-426 / FuncGenMixin L482-483),
# 不致命。
# 父类尚未注册(Phase A 按文件序,子类模块可能先于父类模块):此时
# **不能**继续注册——StructTable 在 StdlibAlloc=1 时用 perm_pool 分配,
# 残缺条目跨 pass 存活,后续 pass 的 existing 早退永远无法修复 → 子类
# __init__ 写父类字段每遍永久失败。整类 defer(不注册);模块级变量/函数
# 参数解析失败静默降级 i32,不致命。
return 0
pfc: int = parent_entry.FieldCount
for pfi in range(pfc):
@@ -367,16 +359,10 @@ class ClassGenDef(ClassGenBase.ClassGenBase):
# 供 translate_attribute 做后续 bitcast。
is_generic_sub: int = 0
if aa.annotation is not None and aa.annotation.kind() == ast.ASTKind.Subscript:
# t.CArray[elem, N](含裸 CArray[elem, N])是定长值数组,
# 非泛型堆对象——元素类型未就绪时若回退 Ptr(i8) 会注册成退化
# 字段布局,Phase B 复用该 entry 后 GEP 元素成 i8,字段访问/
# 赋值全部失效(zhuff_tree.codes: t.CArray[zhuff_code, 288]
# 在 1a 因 zhuff_code 未注册(code: UINT 查表失败,类注册整体
# 中止)退化为 Ptr(i8) 并注册,1b 复用退化 entry →
# self.codes[i].bits 字段查找失败 → pending(attr_read/write) →
# "if 条件表达式翻译失败"根因)。仅真正的泛型类(list[T]/
# Vector[T] 等堆对象)未特化才回退 Ptr(i8)(AnnotClassName
# 携带 mangled name 供后续 bitcast 恢复)。
# t.CArray[elem, N](含裸 CArray[elem, N])是定长值数组,非泛型堆对象——
# 元素类型未就绪时若回退 Ptr(i8) 会注册成退化字段布局,Phase B 复用该 entry
# 后 GEP 元素成 i8、字段访问/赋值全部失效。仅真正的泛型类(list[T]/Vector[T]
# 等堆对象)未特化才回退 Ptr(i8)(AnnotClassName 携带 mangled name 供后续恢复)。
sub_g: ast.Subscript | t.CPtr = (ast.Subscript | t.CPtr)(aa.annotation)
is_carray_sub_g: int = 0
if sub_g is not None and sub_g.value is not None:
@@ -563,9 +549,9 @@ class ClassGenDef(ClassGenBase.ClassGenBase):
if is_novtable_deco == 1:
entry.IsNoVTable = 1
if parent_name is not None:
# 1b 阶段父类名是 pool 字符串(本文件 AST/1a 预存),reset 后悬空;
# _translate_oop_methods/_detect_vtable_status 会在后续文件
# find_struct(ParentName) strcmp 解引用 → 用 stdlib 拷贝保证跨文件存活
# 1b 阶段父类名是 pool 字符串,reset 后悬空;_translate_oop_methods/
# _detect_vtable_status 会在后续文件 find_struct(ParentName) strcmp 解引用
# → 用 stdlib 拷贝保证跨文件存活。
if self.Gen.StructGen.get_stdlib_alloc() != 0:
pn_len: sizet = string.strlen(parent_name)
pn_buf: str = CompilerFactory.factory.PermPool.alloc(pn_len + 1)
+15 -23
View File
@@ -14,10 +14,9 @@ from . import ClassGenBase
# ============================================================
# 本地常量副本(原 HandlesEnum.vp / HandlesClassDef.vp 定义,去除 Handles
# 层拆分时随文件删除而丢失——未定义名在常量上下文被 TPV 折叠为 0,曾导致
# 枚举成员收集被静默跳过、register_enum 永不调用 → 所有 CEnum 注册失败 →
# `OpKind.Add` 裸名 NameError(includes/ast/base.vp L241 根因))
# 本地常量副本(原 HandlesEnum/HandlesClassDef 定义,拆分时随文件删除而丢失——
# 未定义名在常量上下文被 TPV 折叠为 0,会导致枚举成员收集被静默跳过、
# register_enum 永不调用 → 所有 CEnum 注册失败 → 裸名枚举访问 NameError)
# ============================================================
ENUM_MEMBER_MAX_LOCAL: t.CDefine = 128 # 与 StmtGenDeclMixin.ENUM_MEMBER_MAX 一致
RENUM_CLASS_MAX: t.CDefine = 64
@@ -633,21 +632,18 @@ class ClassGenEnum(ClassGenBase.ClassGenBase):
existing: GenTypes.StructEntry | t.CPtr = self.Gen.StructGen.find_struct_by_module(
class_name, trans.ModuleSha1)
if existing is not None:
# [D30 契约修复] existing 只保证 struct entry 在,不保证 REnum 语义(变体表)在。
# struct entry 由 register_struct 写 StructTable,跨 Phase 存活(perm_pool);
# 而变体表是 gen 实例字段(InitClassGen 每个 Generator 重置)。
# 若此处无条件早退,下方 L819 的 _register_renum_variants 永不再执行 ⇒
# 变体表缺该类 ⇒ _is_renum_class_name 恒 0 ⇒ _translate_match_impl 走
# _handle_simple_match(无变体绑定)⇒ case 绑定名未定义
# (includes/llvmlite/__types.vp L407 `case LLVMType.Int(bits):` 的 bits 根因,
# [RN] LLVMType -> 0 (scan count=1) 实证)。
# existing 只保证 struct entry 在,不保证 REnum 语义(变体表)在:struct entry
# 由 register_struct 写 StructTable,跨 Phase 存活(perm_pool);而变体表是 gen
# 实例字段(InitClassGen 每个 Generator 重置)。无条件早退会使
# _register_renum_variants 永不再执行 ⇒ 变体表缺该类 ⇒ _is_renum_class_name
# 恒 0 ⇒ match 走 _handle_simple_match(无变体绑定)⇒ case 绑定名未定义。
if self._is_renum_class_name(class_name) != 0:
if existing.Ty is not None:
llvmlite.module_add_named_type(trans.Module, pool, existing.Ty)
return 0
# 变体表缺失:中和旧 entry(对齐 ClassGenDef needs_rereg 先例),使
# find_struct_by_module 不再命中(按 Name 精确匹配),走下方完整注册路径
# 以同时修正 struct 布局 + 变体表。
# 以同时修正 struct 布局 + 变体表。
if existing.Name is not None:
existing.Name[0] = '\0'
existing.Ty = None
@@ -731,13 +727,10 @@ class ClassGenEnum(ClassGenBase.ClassGenBase):
max_field_count = field_idx
variant_count += 1
elif sk == ast.ASTKind.Assign:
# Assign 形式的**无载荷变体**(如 `Empty = 1` / `None_ = 0`):
# 与 TPC HandlesClassDef._EmitREnumLlvm L1324-1335、
# SymbolExtractor L241-250 同口径——它同样是变体(占一个变体
# 序号),只是无字段。此前这里只认 ClassDef ⇒ 该变体不计入
# variant_count、也不进变体表 ⇒ match 的 `case Option.Empty():`
# 查表失配 tag=-1 → 本会话硬校验 fail-fast
# (Test\EnumTest\App\enumtest.vp:51 现场)。
# Assign 形式的**无载荷变体**(如 `Empty = 1` / `None_ = 0`):与 TPC
# HandlesClassDef._EmitREnumLlvm 同口径——它同样是变体(占一个变体序号),
# 只是无字段。不认则 variant_count 与变体表都漏掉该变体 ⇒ match 的
# `case Option.Empty():` 查表失配 tag=-1 → 硬校验 fail-fast。
# tag 值由 _register_renum_variants 按同一口径分配(显式值优先)。
ag_v: ast.Assign | t.CPtr = (ast.Assign | t.CPtr)(stmt)
if ag_v is None or ag_v.targets is None:
@@ -1229,9 +1222,8 @@ class ClassGenEnum(ClassGenBase.ClassGenBase):
if self.RenumAliasCount >= RENUM_ALIAS_MAX:
return
# 去重检查(去指针化):class 槽位存 perm_pool 字符串拷贝,用 strcmp
# 内容比较——不再依赖指针值统一("LLVMType 字段 Pointee 未找到"四次
# 依旧根因:注册用 AST 指针、查找用 entry.Name 池拷贝,指针恒不等)。
# 去重检查(去指针化):class 槽位存 perm_pool 字符串拷贝,用 strcmp 内容比较——
# 不再依赖指针值统一(注册用 AST 指针、查找用 entry.Name 池拷贝,指针恒不等)。
for i in range(self.RenumAliasCount):
cls_existing_ptr: str = (str | t.CPtr)(
t.CVoid(self.RenumAliasClass[i], t.CPtr))
+5 -7
View File
@@ -24,10 +24,9 @@ GENERIC_TEMPLATE_MAX: t.CDefine = 512
SPEC_CACHE_MAX: t.CDefine = 512
# ============================================================
# ClassGenGeneric - ClassGenMixin 拆分(阶段 D):泛型模板注册/特化/克隆
# ClassGenGeneric - 泛型模板注册/特化/克隆
#
# 继承 ClassGenBase(1 级,TPV self 调用仅支持 1 级父类)。
# 跨 Mixin 调用统一走 HandlesClassDef 委托(经 _g_gen)。
# 继承链仅 1 级(TPV self 调用仅支持 1 级父类);跨 Mixin 调用统一走 HandlesClassDef 委托。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
@@ -543,10 +542,9 @@ class ClassGenGeneric(ClassGenBase.ClassGenBase):
self.GenericSpecDepth -= 1
return None
# 治本修复:Phase B 补译泛型特化类方法体(统一入口,在 cache/existing 检查之前)
# Phase 1a(declare_only==1)特化时只注册结构体不译方法体(MethodsEmitted==0),
# Phase B(declare_only==0)重复进入时若方法体未译,补译方法体生成 define,
# 避免 GSList[Line].append 等符号只有 declare 无 define → 链接失败。
# Phase B 补译泛型特化类方法体(统一入口,在 cache/existing 检查之前):Phase 1a
# 特化时只注册结构体不译方法体(MethodsEmitted==0),Phase B 重复进入时补译以生成
# define,避免符号只有 declare 无 define → 链接失败。
# 首次 Phase B 特化(Phase 1a 未特化)find_struct 返回 None,不补译,走下方正常全量翻译。
if trans._declare_only == 0:
pre_existing: GenTypes.StructEntry | t.CPtr = self.Gen.StructGen.find_struct(spec_name)
+1 -2
View File
@@ -5,8 +5,7 @@ from . import ClassGenBase
# ============================================================
# ClassGenMixin - 类定义/继承/泛型/vtable/enum/union/renum
#
# 500~1500 行/文件架构重构阶段 D:全部方法已拆至
# ClassGenBase/Enum/Generic/VTable/Def,本文件保留类声明。
# 方法已全部拆至 ClassGenBase/Enum/Generic/VTable/Def,本文件保留类声明。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
+19 -29
View File
@@ -499,15 +499,11 @@ class ClassGenVTable(ClassGenBase.ClassGenBase):
# mark_only 模式:只设置标志,不翻译方法体
if mark_only != 0:
# Phase 1a 预注册也收集 vtable 元数据(VTableMethods/VTableMethodCount)。
# 根因:Phase 1b 中依赖模块可能先于其依赖类全量翻译(如 tokens.vp 先于
# memhub 生成 text.ll),此时调用方查到 entry 有 HasVTable=1 但
# VTableMethods=None → _translate_method_call 虚分派条件
# (VTableMethods is not None)不成立 → pool.alloc 被静态绑定到
# MemManager.alloc(默认返回 None)→ KwEntry.__new__ 返回 null →
# __before_init__ 写 null 崩溃。
# 短名顺序与 Phase 1b 全量收集一致(父类方法在前 + 本类方法,确定性),
# 多遍扫描重复收集时用新缓冲区覆盖(stdlib 泄漏可忽略,Phase 1b 重译幂等)。
# Phase 1a 预注册也收集 vtable 元数据(VTableMethods/VTableMethodCount):
# 若 1b 中依赖模块先于其依赖类全量翻译,调用方会查到 HasVTable=1 但
# VTableMethods=None → 虚分派条件不成立 → 方法被静态绑定到基类实现(返回
# None)→ __new__ 返回 null → 崩溃。短名顺序与 Phase 1b 全量收集一致
# (父类方法在前 + 本类方法);多遍重复收集用新缓冲区覆盖(幂等)。
mk_has_vt: int = 0
if oop_entry is not None:
mk_has_vt = oop_entry.HasVTable
@@ -843,9 +839,9 @@ class ClassGenVTable(ClassGenBase.ClassGenBase):
# 进入函数作用域
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_FUNCTION)
# 为 self/cls 创建 alloca 并 store(与其他参数一致);@staticmethod 无 self,跳过
# 修复: 之前 self 注册为 SSA 值导致 translate_name_value 错误地 load 一次
# 将 Ptr(struct_ty) 变成 struct 值,引发变参函数 ABI 不匹配崩溃
# 为 self/cls 创建 alloca 并 store(与其他参数一致);@staticmethod 无 self,跳过。
# self 必须走 alloca:注册为 SSA 值会让 translate_name_value 多 load 一次,
# 把 Ptr(struct_ty) 变成 struct 值 → 变参函数 ABI 不匹配崩溃。
if is_static_m == 0:
self_alloca: llvmlite.Value | t.CPtr = llvmlite.build_alloca(func_builder, self_ptr_ty)
if self_alloca is not None:
@@ -972,19 +968,15 @@ class ClassGenVTable(ClassGenBase.ClassGenBase):
if stmt2 is not None:
self.Gen.StmtGen.translate_stmt(trans, stmt2)
# 追加89:方法末尾收尾与模块级函数(FuncGenMixin.translate_function_def)
# 同语义——编译器不替代码发明返回值,三种合法收尾:
# 方法末尾收尾与模块级函数(FuncGenMixin.translate_function_def)同语义——
# 编译器不替代码发明返回值,三种合法收尾:
# ① void 方法掉出末尾 → ret void(无值可发明,Python 语义)
# ② 末句是控制流语句(match/if 全分支 return)→ merge 块结构性
# 不可达 → build_unreachable(不可能抵达,GCC/Clang fall-through
# 视为 UB 同语义)
# ② 末句是控制流语句(match/if 全分支 return)→ merge 块结构性不可达 →
# build_unreachable(GCC/Clang 对 fall-through 视为 UB 同语义)
# ③ 无返回类型注解的方法 → 视为无返回值过程,ConstZero 兜底
# 有 -> 注解且末句普通语句掉出末尾 → fail-fast。
# 不做 noreturn 函数名白名单:错误终止路径(fatal_error 等)也必须在
# 源码层面保证块收尾(如显式 return 样板),每个函数都符合块设计。
# 原实现 L4529 const_int32 硬编码 ret i32 0,对 i64 返回方法报 llc
# "value doesn't match function result type 'i64'"(追加88 只修了
# FuncGenMixin 模块级函数路径,方法路径此处追加89 一并同步)
# 有 -> 注解且末句普通语句掉出末尾 → fail-fast;不做 noreturn 函数名白名单
# (错误终止路径也必须在源码层面保证块收尾)。返回类型按实际 ret_ty 构造
# (硬编码 ret i32 0 会对 i64 返回方法报 llc 结果类型不匹配)。
last_is_return: int = 0
# 追加89:bn3 提前初始化,避免 body is None 时下方引用未定义变量
bn3: sizet = 0
@@ -1032,12 +1024,10 @@ class ClassGenVTable(ClassGenBase.ClassGenBase):
if _is_void != 0:
llvmlite.build_ret_void(func_builder)
elif last_is_ctrl != 0 and trans._ctrl_merge_reachable == 0:
# 追加90(与 FuncGenMixin 同步):非 void 方法末句是控制流
# 且 merge 块结构性不可达(全分支 return,无 br 到 merge)
# → LLVM 语义 unreachable。merge 可达(分支 br 到 merge)时
# 不是结构性不可达,需正常收尾(下方 ConstZero / fail-fast)。
# 原无条件 unreachable 使 llc O2 优化 br-to-unreachable 为
# fall-through → 双分支执行崩溃(FuncTest check)
# 非 void 方法末句是控制流且 merge 块结构性不可达(全分支 return,无 br
# 到 merge)→ LLVM 语义 unreachable。merge 可达时须正常收尾(下方
# ConstZero / fail-fast),否则 llc O2 把 br-to-unreachable 优化为
# fall-through → 双分支执行崩溃。
llvmlite.build_unreachable(func_builder)
elif fd.returns is None:
# 无返回类型注解 → 无返回值过程方法,默认零值返回
+2 -19
View File
@@ -282,16 +282,7 @@ class ExprGenAttr(ExprGenBase.ExprGenBase):
is_t_type_obj: int = 0
if string.strcmp(mod_path_w, "t") == 0:
is_t_type_obj = self._is_t_type_object_name(at.attr)
# 类名不是模块级全局变量:Module.ClassName(如 llvmlite.LLVMType —— LLVMType
# 定义在 includes/llvmlite/__types.vp,包 __init__.vp 仅
# `from .__types import LLVMType` 再导出)此前落到本函数,被制造成
# @"481fd55266ff1187.LLVMType" = external global i8* 并 load,而全仓无该
# 全局定义(唯一引用点:ClassGenDef.vp L129 `llvmlite.LLVMType.Struct(...)`
# 变体构造时被丢弃的 callee 求值,load 结果无使用者)。非 ASan 构建该 load
# 是死代码,被 llc 优化删除故未暴露;ASan 插桩 IR 保留该全局地址 →
# ld.lld undefined symbol: 481fd55266ff1187.LLVMType(.refptr 段)。
# 已注册类/结构体/REnum 一律交给类/REnum 路径,不在此制造全局。
# 先例:上方 is_submod / t.ASM_DESCR 分支同为"非全局 → return None"。
# 类名不是模块级全局变量:Module.ClassName(如 llvmlite.LLVMType,定义在 .__types.vp,包 __init__.vp 仅再导出)此前被制造成 @"sha1.LLVMType" = external global i8* 并 load,而全仓无该全局定义(唯一引用点是变体构造时被丢弃的 callee 求值),ASan 插桩下 ld.lld undefined symbol。已注册类/结构体/REnum 一律交给类/REnum 路径,不在此制造全局
if is_t_type_obj == 0 and self.Gen.StructGen.find_struct(at.attr) is not None:
return None
# 兜底声明名:t 类型对象(t.py 不编译、无 sha1)保持裸名 linkonce_odr;
@@ -836,15 +827,7 @@ class ExprGenAttr(ExprGenBase.ExprGenBase):
alias_ptr: llvmlite.Value | t.CPtr = llvmlite.build_gep_struct(
builder, struct_ty, alias_fe.Ty, obj_ptr, alias_idx)
if alias_ptr is not None:
# [FIX 2026-08-31] REnum 变体字段按声明类型读取
# (对齐 match 绑定 _lookup_renum_member_ty 机制):
# 槽位是 i32/i64(布局按最大字段大小),直接 load
# 得整数位模式 → 赋给 X|t.CPtr 指针变量触发 int→ptr
# 禁令误报(AssignGenMixin.vp L608
# arr_ty = array_alloca.Ty.Pointee 根因)。
# 用别名表存的成员声明类型(自引用退化为 i8*)把
# 槽位地址 bitcast 后按声明类型 load——同宽无信息
# 丢失,指针字段读出真正的指针值。
# REnum 变体字段按声明类型读取(对齐 match 绑定 _lookup_renum_member_ty):槽位是 i32/i64(布局按最大字段大小),直接 load 得整数位模式,赋给 X|t.CPtr 指针变量会触发 int→ptr 禁令误报。用别名表存的成员声明类型(自引用退化为 i8*)把槽位地址 bitcast 后按声明类型 load——同宽无信息丢失,指针字段读出真正的指针值
decl_ty_rn: llvmlite.LLVMType | t.CPtr = \
self.Gen.ClassGenEnum._lookup_renum_field_alias_ty(
renum_se.Name, at.attr)
+2 -24
View File
@@ -459,18 +459,7 @@ class ExprGenBase:
# float → int
if val_fbits != 0 and target_bits != 0:
return llvmlite.build_fp2si(builder, val, target_ty)
# 聚合值 → 指针: 落栈 materialize 临时对象后传其地址
# FuncGenDef.vp:263-271 已确认「裸类/数组形参一律 lowering 为
# Ptr(struct/array)」——TPV 内部函数/方法不存在"按值传结构体"的形参
# (唯一例外:C ABI 外部声明(t.CExtern/t.State)的裸结构体形参按值,
# 见 VpiReader._AggregateParamTy「三处必须同步」)。故调用点的
# rvalue 聚合(函数调用返回值、下标元素 load 等)必须落栈取址,否则
# 实参类型与指针形参失配(gfx.vp `eye.Sub(target).Norm()`、
# `Mat4_Mul(m, Mat4_Translate(...))`、`DrawTriangle(.., v[i], ..)` 等
# rvalue 实参全部按值传 → 与 define 的 %T* 形参不符,N18)。
# 此前缺此分支 → 落到末尾 return val 原样按值传出。
# alloca 走入口块(支配性安全),生命周期覆盖整个调用,无需释放;
# pointee 与聚合类型一致时直接用 alloca 地址,避免同型 bitcast 噪声。
# 聚合值 → 指针:落栈 materialize 临时对象后传其地址。FuncGenDef 已确认「裸类/数组形参一律 lowering 为 Ptr(struct/array)」——TPV 内部函数/方法不存在"按值传结构体"的形参(唯一例外:C ABI 外部声明的裸结构体形参按值,见 VpiReader._AggregateParamTy「三处必须同步」)。故调用点的 rvalue 聚合(函数调用返回值、下标元素 load 等)必须落栈取址,否则实参类型与指针形参失配。alloca 走入口块(支配性安全),生命周期覆盖整个调用,pointee 与聚合类型一致时直接用 alloca 地址
if self.is_ptr_type(val.Ty) == 0 and self.is_ptr_type(target_ty) != 0:
vk_agg: int = val.Ty.kind()
if vk_agg == llvmlite.LLVMType.Struct or vk_agg == llvmlite.LLVMType.Array:
@@ -519,18 +508,7 @@ class ExprGenBase:
return 0
return self.Gen.ExprGenOps.should_zext_widen(pool, val, None, node, trans)
# ============================================================
# translate_ptr_arith - 指针算术: ptr + int / ptr - int
#
# 用 getelementptr(按 pointee 元素大小缩放索引),对齐 TPC
# ExprGen.emit_binary_op / HandlesAugAssign.py。
# 旧实现用 ptrtoint + add + inttoptr(按字节加):i8* 元素大小 1
# 恰好正确,但 i32*/struct* 等指针 `p + 3` 只前进 3 字节(应 12),
# 导致 StringIterTest Test 4/8 失败(指针偏移/指针算术)。
#
# 数组 pointee([N x ty]*)先 decay 为元素指针(GEP 0,0),
# 避免以整个数组为步长(对齐 TPC ExprGen L53-55)。
# ============================================================
# translate_ptr_arith:指针算术 ptr±int,用 getelementptr(按 pointee 元素大小缩放索引,对齐 TPC ExprGen.emit_binary_op / HandlesAugAssign.py);数组 pointee([N x ty]*)先 decay 为元素指针(GEP 0,0),避免以整个数组为步长
def translate_ptr_arith(self, pool: rowptr[memhub.MemBuddy],
builder: llvmlite.IRBuilder | t.CPtr,
ptr_val: llvmlite.Value | t.CPtr,
+5 -26
View File
@@ -28,18 +28,7 @@ import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
class ExprGenCdef(ExprGenBase.ExprGenBase):
"""跨模块 CDefine 查找"""
# ============================================================
# _build_source_path_from_sha1 - 通过 SHA1 从存储器构建源 .vp/.py 文件路径
#
# .vpi 文件可能未生成(Phase1 未输出 vpi),此时回退读取源 .py 文件。
# 从 StubMerger 全局存储器查找 SHA1 对应的相对路径,
# 根据 rel_path 前缀选择基础目录:
# - rel_path 以 "lib/" 开头 → App 源文件,用 {Config.SourceDir}/{rel_path}
# - 其他 → includes 文件,用 {Config.IncludesDir}/{rel_path}
#
# Returns:
# stdlib.malloc 分配的路径字符串(调用者负责 free),None 失败
# ============================================================
# _build_source_path_from_sha1:通过 SHA1 从 StubMerger 全局存储器构建源 .vp/.py 文件路径(.vpi 未生成时回退读源 .py);rel_path 以 "lib/" 开头 → {Config.SourceDir}/{rel_path},其他 → {Config.IncludesDir}/{rel_path}。返回 stdlib.malloc 分配的路径字符串(调用者负责 free),失败返回 None
def _build_source_path_from_sha1(self, sha1: str) -> str:
"""通过 SHA1 从全局存储器构建源 .py 文件路径"""
if sha1 is None:
@@ -72,17 +61,7 @@ class ExprGenCdef(ExprGenBase.ExprGenBase):
rel_path: str = rel_arr_ptr + ridx
if rel_path[0] == '\0':
return None
# 选择基础目录(优先级):
# 1. 条目存储的 includes 根目录 IncDir(绝对路径,多 includes 目录
# 支持):PopulateSha1MapStore 按每条目保存所属根目录,跨模块
# CDefine 查找回退读源时按 IncDir 直接拼路径,避免"首 includes
# 目录"启发式对非首目录文件失效(Gargantua 多 includes 目录:
# stdint.vp 属于 TransPyC/includes,而 IncludesDir=首目录 ViperOS
# → 拼错路径 → 文件不存在 → XMOD-CD 误报根因)。
# 2. rel_path 前缀启发式:以 "lib/" 开头 → App 源文件(SourceDir);
# "includes/" 开头 → includes 文件(去掉前缀);其他无前缀路径
# (如 VKernel 的 "drivers/core/cpu/cpu.vp")先试 SourceDir,
# 文件不存在再试 IncludesDir。
# 选择基础目录(优先级):1) 条目存储的 includes 根目录 IncDir(绝对路径,支持多 includes 目录,避免"首 includes 目录"启发式对非首目录文件失效);2) rel_path 前缀启发式——以 "lib/" 开头 → App 源文件(SourceDir);"includes/" 开头 → includes 文件(去掉前缀);其他无前缀路径先试 SourceDir,不存在再试 IncludesDir
base_dir: str = None
rel_used: str = rel_path
inc_arr_ptr: bytes = StubMergerStore.GetSha1StoreIncdirArrPtr()
@@ -114,7 +93,7 @@ class ExprGenCdef(ExprGenBase.ExprGenBase):
return None
viperlib.snprintf(buf, path_len, "%s/%s", base_dir, rel_used)
# 存在性检查:SourceDir 下不存在且 IncludesDir 不同 → 重试 IncludesDir
pf_chk: fileio.File | t.CPtr = fileio.File(buf, fileio.MODE.R)
pf_chk: rowptr[fileio.File] = fileio.File(buf, fileio.MODE.R)
if pf_chk.closed:
pf_chk.close()
if Config.IncludesDir is not None and base_dir != Config.IncludesDir:
@@ -125,7 +104,7 @@ class ExprGenCdef(ExprGenBase.ExprGenBase):
if buf is None:
return None
viperlib.snprintf(buf, path_len2, "%s/%s", Config.IncludesDir, rel_used)
pf_chk2: fileio.File | t.CPtr = fileio.File(buf, fileio.MODE.R)
pf_chk2: rowptr[fileio.File] = fileio.File(buf, fileio.MODE.R)
if pf_chk2.closed:
pf_chk2.close()
stdlib.free(buf)
@@ -310,7 +289,7 @@ class ExprGenCdef(ExprGenBase.ExprGenBase):
if pyi_buf is None:
stdlib.free(pyi_path)
return -1
pf: fileio.File | t.CPtr = fileio.File(pyi_path, fileio.MODE.R)
pf: rowptr[fileio.File] = fileio.File(pyi_path, fileio.MODE.R)
need_py_fallback: int = 0
bytes_read: LONG = 0
if pf.closed:
+2 -27
View File
@@ -113,17 +113,7 @@ class ExprGenConst(ExprGenBase.ExprGenBase):
double_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Double(pool)
return llvmlite.ConstFloat(pool, double_ty, cn.float_val)
elif cn.const_kind == ast.CONST_CHAR:
# 单引号单字符字面量:默认按字符值(i32,coerce_to_type 截断为 i8)。
# 但当目标类型是指针(str/bytes/t.CPtr 等)时,对齐 TPC 引号推断
# (HandlesExpr:"目标是 ptr 但值是 char → 走字符串路径"):物化为
# 1 字符字符串。否则 coerce inttoptr 把字符值当指针(如 '"'=0x22),
# 运行期 strlen 访问低地址 → ASan 崩溃(二代编 StringTest PyI 生成根因)。
# 1a 阶段(declare_only=1)不 LoadDepVpi(vpi 未预生成)→ 依赖函数
# found_func=None → translate_call 缺省参数补全不生效 → translate_value
# 调用参数数 < define(如 target_ty 缺省)→ x64 ABI 被调方读第 9 参
# 栈槽残留垃圾(0x4 等)。declare_only 不产出 IR,target_ty 无用,
# 未对齐(8 对齐失败)即视为垃圾跳过,避免 is_ptr_type 解引用崩溃
# (SlotsBugTest 1a string.vp 崩溃根因;1b found_func 非 None 正常填充)。
# 单引号单字符字面量(CONST_CHAR):默认按字符值(i32,coerce 截断为 i8);但当目标类型是指针(str/bytes/t.CPtr)时须物化为 1 字符字符串,否则 coerce inttoptr 把字符值当指针(如 '"'=0x22)→ 运行期 strlen 访问低地址崩溃。1a 阶段(declare_only=1)依赖函数 found_func=None,target_ty 未对齐即视为垃圾跳过,避免 is_ptr_type 解引用崩溃
if target_ty is not None and (uint64(target_ty) & 7) == 0 \
and self.is_ptr_type(target_ty) != 0:
cbuf: bytes = stdlib.malloc(2)
@@ -257,22 +247,7 @@ class ExprGenConst(ExprGenBase.ExprGenBase):
# 注册到模块作用域,避免重复创建
self.Gen.VarGen.define_module_var(trans.SymTab, "_mbuddy", gv_ref_fb)
return gv_ref_fb
# ============================================================
# translate_list_literal — 翻译 list 字面量 [a, b, c]
#
# 将 list 字面量翻译为 list[T](pool) 构造 + append 调用序列
#
# Args:
# builder: IRBuilder
# pool: 编译器内存池(MemBuddy)
# mod: LLVMModule
# node: ast.List 节点
# elem_type_name: 元素类型名(如 "str", "int"),None 表示从元素推断
# trans: Translator 对象
#
# Returns:
# list 对象指针(Ptr(list[T] struct)),None 失败
# ============================================================
# translate_list_literal:把 list 字面量 [a, b, c] 翻译为 list[T](pool) 构造 + append 调用序列。elem_type_name 为元素类型名(如 "str"/"int"),None 表示从元素推断。返回 list 对象指针(Ptr(list[T] struct)),失败返回 None
def translate_list_literal(self, builder: llvmlite.IRBuilder | t.CPtr,
pool: rowptr[memhub.MemBuddy],
mod: llvmlite.LLVMModule | t.CPtr,
+2 -19
View File
@@ -67,16 +67,7 @@ class ExprGenName(ExprGenBase.ExprGenBase):
nm_id: str = nm.id
if nm_id is None:
return None
# CDefine 编译期常量: 返回常量值(浮点 CDefine → double 常量,整数 → i32)
# NAME: t.CDefine = value 形式定义的常量在编译期已注册到本地表
# 浮点 CDefine(如 vipermath 的 U_M_PI=3.14...)必须返回浮点常量,
# 否则在 double 函数里 return 生成 ret i32 0(llc 报错)。
# 注意:同名变量优先——当前作用域已存在变量时,不得被 CDefine 表条目
# 遮蔽。vpi stub 注入会把依赖模块的 CDefine 注册进导入方 CDefine 表
# (numpy 的 e: t.CDefine = 2.718...),测试模块局部变量 e 被误解析为
# 浮点常量 → numpy.np_sign(e) 参数传 double → coerce Ptr 触发
# "float <-> ptr" CastError(NumpyTest np_sign 根因)。守卫与下方跨模块
# CDefine 查找的 lookup_var 检查(L102)保持一致。
# CDefine 编译期常量:返回常量值(浮点 CDefine → double 常量,整数 → i32;否则 double 函数里 return 会生成 ret i32 0 报错)。注意同名变量优先——当前作用域已存在变量时不得被 CDefine 表条目遮蔽(vpi stub 注入会把依赖模块的 CDefine 注册进导入方表,可能误解析局部变量为常量),守卫与下方跨模块 CDefine 查找的 lookup_var 检查一致
cdef_val_d: double = self.Gen.TypeGenCDef.lookup_cdefine_double(nm_id)
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
if trans is None or self.Gen.VarGen.lookup_var(trans.SymTab, nm_id) is None:
@@ -644,15 +635,7 @@ class ExprGenName(ExprGenBase.ExprGenBase):
# 是 list 类型: load 出 list 对象指针
builder: llvmlite.IRBuilder | t.CPtr = trans._cur_builder
return llvmlite.build_load(builder, pointee, alloca)
# ============================================================
# _nonlocal_subscript_ptr - nonlocal 数组/指针的下标元素指针
#
# 嵌套函数(提升函数)体内访问 nonlocal 变量时,变量的实际存储是
# 运行时共享堆(env[ni] 存的堆地址),不能 lookup_var 找外层函数
# alloca(其 SSA 编号属于外层函数,当前函数引用错乱崩溃——
# ClosureTest2 Test 4 fill() 内 arr[i] 崩溃根因)。
# 正确路径:从 env 读堆地址(_load_raw_var_ptr)→ bitcast → GEP。
# ============================================================
# _nonlocal_subscript_ptr:nonlocal 数组/指针的下标元素指针——嵌套(提升)函数体内访问 nonlocal 变量时,其实际存储是运行时共享堆(env[ni] 存堆地址),不能 lookup_var 找外层函数 alloca(其 SSA 编号属外层函数,当前函数引用会错乱崩溃)。正确路径:从 env 读堆地址(_load_raw_var_ptr)→ bitcast → GEP
def _nonlocal_subscript_ptr(self, builder: llvmlite.IRBuilder | t.CPtr,
pool: rowptr[memhub.MemBuddy],
trans: HT.Translator | t.CPtr,
+2 -16
View File
@@ -315,14 +315,7 @@ class ExprGenOps(ExprGenBase.ExprGenBase):
return 1
return 0
if k == ast.ASTKind.Attribute:
# 结构体字段(如 viperio.Buf 的 self.capacity/length: sizet):
# 经接收者变量的 AnnotClassName 取类名,再按类名+字段名查字段注解,
# 判定无符号(CSizeT/uint*)。此前 Attribute 落到末尾 return 0 →
# `self.capacity > 0` / `self.length < self.capacity` 生成 icmp sgt/slt
# (viperio 字段比较误用有符号,T1 残留根因)。
# 注意:本函数在 translate_binop 对每个操作数调用,必须**只读失败返回 0**;
# 故不走 lookup_field_by_class(其模块限定名分支会 fatal_error),
# 改用 find_struct + 自遍历字段(未命中一律返回 0)。
# 结构体字段(如 viperio.Buf 的 self.capacity/length: sizet):经接收者变量的 AnnotClassName 取类名,再按类名+字段名查字段注解,判定无符号(CSizeT/uint*)。本函数在 translate_binop 对每个操作数调用,必须只读、失败返回 0——故不走 lookup_field_by_class(其模块限定名分支会 fatal_error),改用 find_struct + 自遍历字段(未命中一律返回 0)
at_u: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(node)
if at_u.value is not None and at_u.attr is not None and trans is not None:
cls_nm_u: str = None
@@ -655,14 +648,7 @@ class ExprGenOps(ExprGenBase.ExprGenBase):
# `x is not y` → 指针/值不等比较
predicate = llvmlite.ICMP_NE
# === 类型安全检查(BUG2 修复)===
# 规则:
# - 数值(0) vs 数值(0) → 允许(无关类型,按位宽 sext 对齐)
# - 一级指针(1) vs 一级指针(1) → 允许(无关 pointee 类型,地址比较)
# - 数值(0) vs 指针(1+) → 禁止(fast-fail,除 None 比较外)
# - 不同级指针(1 vs 2 等) → 禁止(fast-fail)
#
# None 比较跳过此检查(None 被翻译为 null 指针或 0,与任意指针/数值比较合法)
# 类型安全检查:数值 vs 数值 → 允许(按位宽 sext 对齐);一级指针 vs 一级指针 → 允许(地址比较);数值 vs 指针(1+) → 禁止(fast-fail,除 None 比较外);不同级指针(1 vs 2 等)→ 禁止。None 比较跳过此检查(None 译为 null 指针或 0,与任意指针/数值比较合法)
if cmp_with_none == 0:
lhs_ptr_level: int = self.get_ptr_level(lhs.Ty)
rhs_ptr_level: int = self.get_ptr_level(rhs.Ty)
+2 -2
View File
@@ -445,8 +445,8 @@ class FuncGenBase(CallGenBase.CallGenBase):
pool, arg.annotation, None, None, None, 0)
if resolved is None:
# [S4 fail-fast] 有注解但解析失败:不再静默用 i32。
# 本函数不传 trans/imports 上下文,解析失败即调用方
# 未补齐上下文(暴露上游,不由本函数兜底)。
# 本函数不传 trans/imports 上下文,解析失败即调用方未补齐
# 上下文(暴露上游,不由本函数兜底)。
self.Gen.TypeGenErr.fatal_error(
arg, f"函数 '{name}' 形参 '{arg.arg}' 类型注解解析失败", "NameError")
return None
+30 -66
View File
@@ -16,32 +16,21 @@ import lib.core.Gen.GenApi as GenApi
# ============================================================
# FuncGenDef - FuncGenMixin 拆分(阶段 D):函数声明 / translate_function_def / decorator wrapper
# FuncGenDef - 函数声明 / translate_function_def / decorator wrapper
#
# 继承 CallGenBase(1 级,TPV self 调用仅支持 1 级父类)。
# 跨 Mixin 调用统一走 HandlesExprCall 委托(经 _g_gen)。
# 继承链仅 1 级(TPV self 调用仅支持 1 级父类);跨 Mixin 调用统一走 HandlesExprCall 委托。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
class FuncGenDef(CallGenBase.CallGenBase):
"""函数声明 / translate_function_def / decorator wrapper"""
# ============================================================
# forward_declare_functions - 预扫描所有顶层 FunctionDef,创建前向声明
#
# 解决同模块内前向引用问题:如 viperlib.py 中 sprintf(行15) 调用
# vsnprintf(行41),但 vsnprintf 定义在后面。
#
# 对每个顶层 FunctionDef:
# 1. 推断返回类型和参数类型
# 2. 计算 mangled name(含 SHA1 前缀)
# 3. 创建 declare(IsDeclared=1)
# 4. 添加参数
# 5. 注册到函数表
#
# 后续 translate_function_def 会通过 find_func_in_module 找到已有的 declare,
# 复用之(清除 IsDeclared,跳过参数创建,直接添加函数体)。
# ============================================================
# 解决同模块内前向引用问题(如 sprintf 调用定义在后面的 vsnprintf)。
# 对每个顶层 FunctionDef: 1) 推断返回/参数类型 2) 计算 mangled name(含 SHA1 前缀)
# 3) 创建 declare(IsDeclared=1) 4) 添加参数 5) 注册到函数表。
# 后续 translate_function_def 经 find_func_in_module 复用该 declare(清除 IsDeclared,
# 跳过参数创建,直接添加函数体)。
def forward_declare_functions(self, trans: HT.Translator | t.CPtr,
tree: ast.AST | t.CPtr) -> int:
"""预扫描所有顶层 FunctionDef,创建前向声明"""
@@ -711,10 +700,9 @@ class FuncGenDef(CallGenBase.CallGenBase):
return 0
llvmlite.position_at_end(func_builder, entry_blk)
# @t.TLS 一次性初始化守卫(对齐 TPC HandlesFunctions.py L1763-1792):
# 首次调用执行函数体并置全局标志 __tls_init_done_{name}=1,后续调用
# 直接返回默认值。TPV 此前未实现 @t.TLS,_init_tls 每次 TlsAlloc 导致
# provider 栈跨调用丢失(FakeDuckTest Test 9 注入失败根因)。
# @t.TLS 一次性初始化守卫(对齐 TPC HandlesFunctions.py):首次调用执行函数体并置
# 全局标志 __tls_init_done_{name}=1,后续调用直接返回默认值。不实现则每次调用都
# TlsAlloc,provider 栈跨调用丢失。
if has_tls != 0:
tls_flag_name: str = pool.alloc(128)
if tls_flag_name is not None:
@@ -975,25 +963,18 @@ class FuncGenDef(CallGenBase.CallGenBase):
last_is_return = 1
if last_is_return == 0:
if llvmlite.builder_cur_block_is_terminated(func_builder) == 0:
# 追加89:编译器不替代码发明返回值——函数流程真实掉出末尾而
# 未显式 return 时 fail-fast(要求补 return,Python traceback
# 风格定位到函数)。三种合法收尾:
# 编译器不替代码发明返回值——函数流程真实掉出末尾而未显式 return 时 fail-fast
# (要求补 return,定位到函数)。三种合法收尾:
# ① void 函数掉出末尾 → ret void(无值可发明,Python 语义)
# ② 末句是控制流语句(match/if 全分支 return)且当前块
# 未 terminated → merge 块结构性不可达 → unreachable
# ③ 无返回类型注解的函数(fd.returns is None,如
# ast/base.vp _set_pos 过程风格赋值函数)→ 视为无返回值
# 函数,默认生成零值返回(用户决策:没有返回值默认不用
# return;ConstZero 按推断出的 ret_ty 构造,类型匹配)
# 有 -> 注解且末句是普通语句掉出末尾 → fail-fast(不得替代码
# 发明返回值,也不做 noreturn 函数名白名单——错误终止路径
# 也必须在源码层面保证块收尾,如显式 return 样板)
# 末句是否为控制流语句(If/Match/While/For/Try/With):
# 控制流语句翻译后当前块未 terminated 表示 merge 块结构性
# 不可达(所有分支均已 return/terminated)——对非 void 函数
# 生成 unreachable 而非 fail-fast(GCC/Clang 对 fall-through
# 视为 UB 同语义;追加89 修正 get_struct_name 全 case return
# 被错判"不可能抵达")
# ② 末句是控制流语句(match/if 全分支 return)且当前块未 terminated →
# merge 块结构性不可达 → unreachable
# ③ 无返回类型注解的函数(fd.returns is None,过程风格函数)→ 视为无返回值
# 函数,默认生成零值返回(ConstZero 按推断出的 ret_ty 构造,类型匹配)
# 有 -> 注解且末句是普通语句掉出末尾 → fail-fast(不得替代码发明返回值,也不做
# noreturn 函数名白名单——错误终止路径也必须在源码层面保证块收尾)。
# 末句是否为控制流语句(If/Match/While/For/Try/With):翻译后当前块未 terminated
# 表示 merge 块结构性不可达(所有分支均已 return/terminated)——对非 void 函数
# 生成 unreachable 而非 fail-fast(GCC/Clang 对 fall-through 视为 UB 同语义)。
last_is_ctrl: int = 0
if bn3 > 0:
last_stmt3: ast.AST | t.CPtr = body.get(bn3 - 1)
@@ -1010,32 +991,15 @@ class FuncGenDef(CallGenBase.CallGenBase):
_is_void = 1
case _:
pass
# 追加91:返回注解层面的"无返回值"判据(与上面的 ret_ty 类型判定
# 互为双保险,不改变任何非 void 分支)。
# 现场(误判):includes/atom.vp:22
# def __atomic_clear(ptr: t.CUInt64T | t.CPtr, order: t.CInt) -> t.CVoid:
# 函数体为 c.Asm 调用、流程真实掉出末尾;对 void 函数这是合法收尾
# (Python 语义:void 无需显式 return),却被下方 fail-fast 误判为
# "缺显式 return"。注意报文的"(返回类型 void)"**不能**当作"已识别
# 出 void"的依据——llvmlite.TypePrint 的 `case _` 兜底
# (includes/llvmlite/__types.vp:510-511)同样打印 "void",任何
# 未命中的类型都会显示 void。故必须由注解判据补足:LLVMType 的
# REnum class-pattern match 一旦退化(变体表/类名失配 →
# MatchGenMixin._handle_simple_match 不产出 case 值,case 体结构性
# 不可达),_is_void 恒 0,本检查就会把 void 函数误报成缺 return。
# 判据与注解→类型解析同口径(注解声明无返回值 ⇔ 该注解解析为
# LLVMType.Void),逐形式与先例:
# ① t.CVoid(Attribute 形式):TypeGenResolve.map_t_type
# "CVoid" → llvmlite.Void(TypeGenResolve.vp:1351-1352);
# 先例 includes/atom.vp:22、includes/vipersimd.vp:8
# ② 裸名 CVoid(from t import CVoid):同 TypeGenResolve.vp:1441-1442
# (_is_t_attr 的裸名分支注释明列 CVoid)
# ①②统一经既有判据 TypeGenResolve._is_cvoid_marker
# (TypeGenResolve.vp:1484-1493)判定——不新增名字白名单,
# 与 FuncGenDef.vp:256/516/625/784 的既有调用方式一致
# ③ -> None(CONST_NONE):注解解析即 Void
# (TypeGenResolve.vp:1996-1999);先例
# App/lib/core/Gen/GenApi.vp:25 set_temp_dir
# 返回注解层面的"无返回值"判据(与上面的 ret_ty 类型判定互为双保险,
# 不改变任何非 void 分支):LLVMType 的 REnum class-pattern match 一旦退化
# (变体表/类名失配 → _handle_simple_match 不产出 case 值),_is_void 恒 0,
# 会把 c.Asm 收尾的 void 函数(流程真实掉出末尾)误报成"缺显式 return";
# 报文的"(返回类型 void)"不能作为判据(TypePrint 的 `case _` 兜底同样打印
# void,任何未命中类型都显示 void)。故按注解判据补足,与注解→类型解析同口径
# (注解声明无返回值 ⇔ 解析为 LLVMType.Void):① t.CVoid(Attribute 形式)
# ② 裸名 CVoid(from t import CVoid)③ -> None(CONST_NONE);
# ①②统一经既有判据 TypeGenResolve._is_cvoid_marker 判定(不新增名字白名单)。
if _is_void == 0 and fd.returns is not None:
if self.Gen.TypeGenResolve._is_cvoid_marker(
fd.returns, imported_modules) != 0:
+13 -28
View File
@@ -477,20 +477,12 @@ class FuncGenMixin(CallGenBase.CallGenBase):
return 1
# ============================================================
# _closure_scan_captures - 递归扫描嵌套函数体中的隐式捕获变量
#
# Python 语义:嵌套函数读取外层函数局部变量无需显式 nonlocal。
# 此前嵌套 def 闭包仅在显式 `nonlocal` 语句时注册捕获;无 nonlocal
# 的 read 在翻译时会经 lookup_var 跨函数命中父函数 alloca(SSA 编号
# 属于父函数,提升函数内引用 → llc "use of undefined value '%X'")。
# lambda 已有 _lambda_scan_body 预扫描,这里对嵌套 def 做等价预扫描:
# 把"读取的外层函数局部变量"注册为 nonlocal(复用 env 共享堆机制),
# 使 read 走 _load_raw_var_ptr 从 env 读堆值。
# 注意:与 lambda 快照方案不同,嵌套 def 复用显式 nonlocal 的
# env 共享堆路径,因此注册顺序必须与 step 13 的 env 构建一致
# (本扫描在 body 翻译前执行,注册顺序稳定)。
# ============================================================
# Python 语义:嵌套函数读取外层函数局部变量无需显式 nonlocal。仅按显式 nonlocal 注册
# 捕获时,无 nonlocal 的 read 会在翻译时经 lookup_var 跨函数命中父函数 alloca(SSA 编号
# 属于父函数 → llc "use of undefined value")。故对嵌套 def 做等价预扫描:把"读取的外层
# 函数局部变量"注册为 nonlocal(复用 env 共享堆机制),使 read 走 _load_raw_var_ptr 从堆读。
# 注意:注册顺序必须与 step 13 的 env 构建一致(本扫描在 body 翻译前执行,顺序稳定)。
def _closure_scan_captures(self, trans: HT.Translator | t.CPtr,
node: ast.AST | t.CPtr) -> int:
"""递归扫描节点中的隐式捕获并注册为 nonlocal"""
@@ -856,17 +848,12 @@ class FuncGenMixin(CallGenBase.CallGenBase):
return 1
# ============================================================
# _type_byte_size - 计算 LLVM 类型占用的字节大小(递归)
#
# 用于 nonlocal 共享堆存储分配:数组(如 [4 x i32]=16 字节)不能
# 用固定 8 字节(越界写破坏 canary/邻块 → 运行时崩溃,
# ClosureTest2 Test 4 非local 数组崩溃根因)。
#
# 注:`case _`(Void/Struct/Func 等)仍返回 8 —— 这不是静默兜底掩盖错误,
# 而是**算不出**(结构体尺寸需查布局表,本函数只做 LLVMType 递归);
# 改 fatal 会拒绝 "nonlocal 结构体" 这一合法用法,需先补布局查询能力。
# ============================================================
# 用于 nonlocal 共享堆存储分配:数组(如 [4 x i32]=16 字节)不能用固定 8 字节
# (越界写破坏 canary/邻块 → 运行时崩溃)。
# 注:`case _`(Void/Struct/Func 等)仍返回 8——不是静默兜底,而是**算不出**
# (结构体尺寸需查布局表,本函数只做 LLVMType 递归);改 fatal 会拒绝
# "nonlocal 结构体"这一合法用法,需先补布局查询能力。
def _type_byte_size(self, pool: rowptr[memhub.MemBuddy],
ty: llvmlite.LLVMType | t.CPtr) -> int:
"""计算 LLVM 类型占用的字节大小(递归)"""
@@ -1048,11 +1035,9 @@ class FuncGenMixin(CallGenBase.CallGenBase):
gname: str = self._nl_heap_global_name(pool, trans, nl_name)
if gname is None:
continue
# 建堆(按类型字节大小)并拷贝当前值——每次本函数执行都新建共享堆,
# 并 store 到模块级映射(跨函数可见)。注意:不能因映射变量已存在
# 就跳过(前一个测试函数已把 @nlheap.* 加入模块——若跳过,本函数
# 运行时 @nlheap.* 保持上一函数的堆地址,env 读到错堆。
# ClosureTest2 Test 7 根因)。映射变量仅首次 add(模块去重),
# 建堆(按类型字节大小)并拷贝当前值——每次本函数执行都新建共享堆,并 store 到
# 模块级映射(跨函数可见)。不能因映射变量已存在就跳过:跳过会让本函数运行时
# @nlheap.* 保持上一函数的堆地址,env 读到错堆。映射变量仅首次 add(模块去重),
# store 指令每次照发。
st_size: int = self._type_byte_size(pool, nl_ty)
st_size_val: llvmlite.Value | t.CPtr = llvmlite.const_int64(pool, st_size)
+7 -13
View File
@@ -393,21 +393,15 @@ def is_module_imported(imported_modules: str, name: str) -> int:
# ============================================================
# SHA1 前缀识别的**唯一真源**(C10 / D3 收敛)
# SHA1 前缀识别的**唯一真源**:两种书写形态各给一个判定。
#
# 「名字带 SHA1 前缀」有两种书写形态,各自给一个判定:
# sha1_prefix_len(name) → 完整名 "{16位小写hex}.Class" ⇒ 返回 17(前缀字节数),
# 否则 0。判据三条缺一不可:长度 >= 17、第 17 字节是
# '.'、前 16 字节全是小写 hex(0-9a-f)。
# is_sha1_hex16(seg) → 单独的**模块段**("16位小写hex")⇒ 1/0。
# 用于已把 "mod.Class" 切成两段后判 mod 段(此时无从
# 判断 '.' 存在与否)。
# sha1_prefix_len(name) → 完整名 "{16位小写hex}.Class" ⇒ 返回 17(前缀字节数),否则 0。
# 判据三条缺一不可:长度 >= 17、第 17 字节是 '.'、前 16 字节全为小写 hex。
# is_sha1_hex16(seg) → 单独的模块段("16位小写hex")⇒ 1/0;用于已切成两段后判 mod 段。
#
# 为什么必须唯一:此前全仓有 8 份以上同义拷贝且**口径互不相同**——有的省略 hex
# 校验(只判 `len > 17 && s[16] == '.'`)、有的允许大写 A-F、有的只要 `len >= 16`
# 就盲取前 16 字节当 SHA1。同一名字在不同路径被判成"是/不是 SHA1 前缀",会产生
# 「重复加前缀 / 把 SHA1 当模块名再查一次(查不到)/ 该跳过 store 查询的没跳过」
# 等隐蔽不一致(跨模块同名类归属错乱、泛型实参解析退化均源于此族)。
# 必须唯一:多处同义拷贝若口径不同(省略 hex 校验 / 允许大写 / 只要 len >= 16 就盲取前 16 字节),
# 同一名字在不同路径会被判成"是/不是 SHA1 前缀",产生「重复加前缀 / 把 SHA1 当模块名再查 /
# 该跳过 store 查询的没跳过」等隐蔽不一致(跨模块同名类归属错乱、泛型实参解析退化)。
# ============================================================
def sha1_prefix_len(name: str) -> int:
"""名字若是 "{16位小写hex}." 形态返回 17(前缀字节数),否则返回 0"""
+4 -7
View File
@@ -4,8 +4,6 @@ import ast
import llvmlite
import memhub
# TypeSpec:统一类型描述符(Debugger/_Type.md 第 6 节设计)。
# S1 阶段仅 import 使该模块参与编译(尚未在任何条目上使用其字段);
# S2 起在 VarEntry/FieldEntry/FuncEntry 上加 Spec 字段并双写。
# 别名用 TSpec:模块内类名同为 TypeSpec,别名同名会被解析器误判为类静态方法。
import lib.core.Gen.Type.TypeSpec as TSpec
@@ -185,11 +183,10 @@ class FuncEntry:
Defaults: t.CPtr # defaults 列表节点指针 (list[AST | CPtr]),None=无默认值
DefaultCount: int # 默认值数量
ParamCount: int # 形参数量(不含 self)
# TypeSpec(S3 条目双写):Kind=TS_FUNC 的**整签名**(Ret/Params/ParamCount/
# IsVarArg 都在其内),由 update_global_func_spec 在形参 add_param 完成后
# 用 NewTypeFunc 构造(含 func.IsVarArg)。PermPool 驻留 ⇒ 跨 1b reset 安全。
# 说明:设计 D4 原写 RetSpec+ParamSpecs 两字段,此处合并为单个 Spec —— 单字段
# 即可完整表达签名,且与 VarEntry/FieldEntry 的 Spec 命名一致,避免参数数组重复。
# TypeSpec(S3 条目双写):Kind=TS_FUNC 的**整签名**(Ret/Params/ParamCount/IsVarArg
# 都在其内),由 update_global_func_spec 在形参 add_param 完成后用 NewTypeFunc 构造。
# PermPool 驻留 ⇒ 跨 1b reset 安全。单字段即可完整表达签名,与 VarEntry/FieldEntry
# 的 Spec 命名一致,避免参数数组重复。
Spec: TSpec.TypeSpec | t.CPtr
+29 -53
View File
@@ -17,14 +17,10 @@ import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
# ============================================================
# ImportsGenMixin - 导入处理 Mixin(对象化 P6-D,wiki/14-oo-refactor.md)
# ImportsGenMixin - 导入处理 Mixin
#
# 从 HandlesImports.vp 迁移:
# 全局状态(_g_reexport_sha1s/_g_reexport_funcs/_g_reexport_srcs/
# _g_reexport_count/_g_current_file)→ 本类字段
# 模块级函数(add_imported_module/is_module_imported/...)→ 本类方法
#
# @t.NoVTable:静态绑定(方法沿 MRO 直接调用 + this 调整,无 vtable 开销)。
# 职责:模块导入表(imported_modules/from_imports)维护、re-export 登记、
# 相对导入解析与模块 SHA1 校验。@t.NoVTable:静态绑定(MRO 直接调用)。
# ============================================================
@@ -52,11 +48,10 @@ class ImportsGenMixin:
# --- 导入查找索引(把 O(串长) 的逐字符扫描降为 O(名字长) 哈希查找) ---
# imported_modules/_from_imports 是空格拼接的扁平串("a b c" / "n:m[:orig]"),
# 长度可达数千字符,而每个 Name 节点都会多次查询。索引与「当前串指针」绑定:
# 调用方查询的 trans._imported_modules/_from_imports 总是 add_* 刚生成的当前串,
# 故命中索引快路径;其余来源(stub 期 stdlib malloc 缓冲等)指针不匹配 → 回退
# 原逐字符扫描,语义完全不变。索引表用 PermPool 分配(跨文件/Phase 存活),由
# 对应 add_* 增量维护(非每文件重建);key/value 均是指向该扁平串内部的指针,
# 串在文件级 reset 前始终有效。
# 调用方查询的总是 add_* 刚生成的当前串,故命中索引快路径;其余来源(stub 期
# stdlib malloc 缓冲等)指针不匹配 → 回退逐字符扫描,语义完全不变。
# 索引表用 PermPool 分配(跨文件/Phase 存活),由对应 add_* 增量维护;key/value
# 均为指向该扁平串内部的指针,串在文件级 reset 前始终有效。
CurImportedModulesPtr: str
CurImportedModulesNames: hashtable.HashTable | t.CPtr
CurFromImportsPtr: str
@@ -68,14 +63,11 @@ class ImportsGenMixin:
# set_current_file 与 CurrentFile 读取统一经 HandlesType 委托(延迟 import
# 规避 HandlesType↔HandlesImports 循环依赖)。
# ============================================================
# _index_imported_module - 增量维护 imported_modules 查找索引
#
# old_ptr 为追加前的串指针(None = 首个模块),new_ptr 为追加后的新串,
# name_ptr 指向新串内的新模块名(key 直接引用串内部,无需复制)。
# 仅当 old_ptr 恰为当前索引绑定的串时复用其索引(正常路径:add_* 始终以
# trans 的当前串为输入);否则放弃索引绑定并回退逐字符扫描(语义不变)。
# ============================================================
# 仅当 old_ptr 恰为当前索引绑定的串时复用其索引;否则放弃索引绑定并回退
# 逐字符扫描(语义不变)。
def _index_imported_module(self, old_ptr: str, new_ptr: str,
name_ptr: str) -> None:
"""增量更新 imported_modules 索引(复用旧索引或新建,失败则解除绑定)"""
@@ -106,14 +98,11 @@ class ImportsGenMixin:
self.CurImportedModulesPtr = new_ptr
self.CurImportedModulesNames = idx
# ============================================================
# _index_from_import - 增量维护 from_imports 查找索引
#
# entry_off 为新增 entry("local:module[:orig]")在新串内相对首字节的偏移。
# 模块表 key=local_name → 指向模块名切片;orig 表 key=local_name → 指向原始名
# 切片(无别名时写入 None,以"名存在但无原始名"区别于未命中)。
# 语义对齐逐字符扫描:精确名首次命中为准(不覆盖),star 模块名以最后一条为准。
# ============================================================
def _index_from_import(self, old_ptr: str, new_ptr: str, entry_off: sizet,
local_name: str, module_name: str,
original_name: str) -> None:
@@ -145,10 +134,10 @@ class ImportsGenMixin:
return
local_len: sizet = string.strlen(local_name)
mod_len: sizet = string.strlen(module_name)
# 哈希键必须是 NUL 结尾串:扁平串内 local 段以 ':' 结尾(非 NUL),若直接把
# 串内 local 段指针当键,FNV 会哈希到 "local:module..." 整段,与查询名
# "local" 永不相等 → 索引恒 miss(_op_head NameError 根因)。故用 PermPool
# 分配 NUL 结尾的稳定副本作键(与索引表同寿命),值仍指向扁平串内模块名切片。
# 哈希键必须是 NUL 结尾串:扁平串内 local 段以 ':' 结尾(非 NUL),直接当键会让
# FNV 哈希到 "local:module..." 整段,与查询名 "local" 永不相等 → 索引恒 miss。
# 故用 PermPool 分配 NUL 结尾的稳定副本作键(与索引表同寿命),值仍指向扁平串
# 内模块名切片。
key_ptr: str = perm.alloc(local_len + 1)
if key_ptr is None:
self.CurFromImportsPtr = None
@@ -722,7 +711,7 @@ class ImportsGenMixin:
stdio.printf(' File "%s", line %d\n', file_name, lineno)
# 显示行内内容(读源文件第 lineno 行的实际代码,便于快速定位)
if lineno > 0 and file_name is not None:
f_err: fileio.File | t.CPtr = fileio.File(file_name, fileio.MODE.R)
f_err: rowptr[fileio.File] = fileio.File(file_name, fileio.MODE.R)
if not f_err.closed:
line_buf_err: bytes = stdlib.malloc(512)
if line_buf_err is not None:
@@ -775,7 +764,7 @@ class ImportsGenMixin:
path_buf[sep_pos] = '\0'
string.strcat(path_buf, mod_name)
string.strcat(path_buf, ".vp")
fex: fileio.File | t.CPtr = fileio.File(path_buf, fileio.MODE.R)
fex: rowptr[fileio.File] = fileio.File(path_buf, fileio.MODE.R)
if not fex.closed:
fex.close()
stdlib.free(path_buf)
@@ -783,17 +772,10 @@ class ImportsGenMixin:
stdlib.free(path_buf)
return 0
# ============================================================
# _check_import_sha1 - 导入时校验模块 SHA1 是否已注册(fast-fail)
#
# 背景:若 includes 路径配置错误(如 project.json 指向不存在的
# ./includes),includes 扫描结果为 0,模块不会注册 SHA1。此前
# 错误延迟到实际调用 module.func() 时才报误导性的
# "跨模块调用 'bd.alloc' 找不到模块 SHA1"。
# 现在在 import 语句处立即检测并报错,快速定位配置问题。
#
# includes 路径配置错误时扫描结果为 0、模块不注册 SHA1,若延迟到实际跨模块调用才报错
# 会误导("跨模块调用 'bd.alloc' 找不到模块 SHA1"),故在 import 语句处立即检测。
# t/c 是内部库,不参与 includes 扫描,豁免检查。
# ============================================================
def _check_import_sha1(self, node: ast.AST | t.CPtr, mod_name: str) -> int:
"""导入时校验模块 SHA1 是否已注册,未注册则立即报错退出
@@ -846,7 +828,7 @@ class ImportsGenMixin:
if alias is not None and alias.name is not None:
# 导入时立即校验模块 SHA1 是否注册(fast-fail,仅全量翻译时检查)
# declare_only=1/2 是 struct 预注册/import 扫描阶段:此时 SHA1 表
# 尚未包含后续才追加的模块(如 App 源文件),检查会造成误报
# 尚未包含后续才追加的模块(如 App 源文件),检查会造成误报。
if trans._declare_only == 0:
self._check_import_sha1(node, alias.name)
pool_val: rowptr[memhub.MemBuddy] = trans.Pool
@@ -907,10 +889,9 @@ class ImportsGenMixin:
trans._imported_modules = self.add_imported_module(
trans.Pool, trans._imported_modules, resolved)
# from . import Y(impf.module is None):resolved 是包名(vpsdk),
# 各 name 是包的子模块 vpsdk.Y。须把完整子模块名也加入
# _imported_modules,否则别名 "syscall" 精确匹配不到包名 "vpsdk"
# → is_module_imported 返回 0 → syscall._syscall1 / syscall.LOAD_SO
# 跨模块解析失败(vpsdk/dynlib.vp 根因)。
# 各 name 是包的子模块 vpsdk.Y。须把完整子模块名也加入 _imported_modules,
# 否则别名 "syscall" 精确匹配不到包名 "vpsdk" → is_module_imported 返回 0
# → syscall._syscall1 / syscall.LOAD_SO 跨模块解析失败。
if impf.module is None:
names_m: list[ast.AST | t.CPtr] | t.CPtr = impf.names
if names_m is not None:
@@ -925,14 +906,11 @@ class ImportsGenMixin:
string.strcpy(jb_m, resolved)
jb_m[rl_m] = '.'
string.strcpy(jb_m + rl_m + 1, alias_m.name)
# from . import Y 的 Y 不一定是包子模块文件:
# 可能是包 __init__.vp 中定义的符号(类/常量,如
# json/__init__.vp 的 JsonValue),无独立文件、不注册
# SHA1。此前无条件按子模块校验 → 误报"模块 'json.JsonValue'
# 未注册 SHA1"。仅当完整名已注册 SHA1(真子模块,如
# vpsdk.syscall)或是本地文件时才校验+登记;否则跳过,
# 符号映射由 translate_import_from_names 的 from-import
# / re-export 处理。
# from . import Y 的 Y 不一定是包子模块文件:可能是包
# __init__.vp 中定义的符号(类/常量,如 JsonValue),无独立
# 文件、不注册 SHA1。仅当完整名已注册 SHA1(真子模块)或是
# 本地文件时才校验+登记;否则跳过,符号映射由
# translate_import_from_names 的 from-import / re-export 处理。
if (StubMergerStore.LookupSha1ByModName(jb_m) is not None
or self._is_local_module_file(alias_m.name) != 0):
if trans._declare_only == 0:
@@ -971,11 +949,9 @@ class ImportsGenMixin:
local_name = alias.asname
orig_name = alias.name
# from . import Y(impf.module is None):_resolve_relative_module
# 返回的是包名(如 vpsdk),但 Y 是包的子模块 vpsdk.Y
# (vpsdk/syscall.vp)。此前直接映射 local_name → 包名,
# 导致 syscall.LOAD_SO 跨模块 CDefine / syscall._syscall1
# 跨模块调用在包里找不到(vpsdk/dynlib.vp `from . import
# syscall as syscall` 根因)。
# 返回的是包名(如 vpsdk),但 Y 是包的子模块 vpsdk.Y。须映射
# local_name → 完整子模块名,否则 syscall.LOAD_SO 跨模块 CDefine /
# syscall._syscall1 跨模块调用在包里找不到。
fi_target: str = resolved
if impf.module is None and resolved is not None:
fl_t: sizet = string.strlen(resolved)
+47 -81
View File
@@ -22,9 +22,7 @@ MAX_MATCH_CASES: t.CDefine = 32
SCOPE_MATCH: t.CDefine = 6
# match 翻译最大递归深度(match → case body → translate_value → 嵌套 match)。
# AST 自引用环/翻译 bug 可致无限递归 → ASan stack-overflow(崩溃点实测为
# translate_match 内 subject kind() 虚调用,栈 2MB 耗尽)。入口计数出口递减,
# 超限即 fail-fast(对齐 TypeGenResolve 深度守卫,防栈溢出)。
# 入口计数出口递减,超限即 fail-fast:AST 自引用环可致无限递归 → 栈溢出。
MATCH_TRANSLATE_MAX_DEPTH: t.CDefine = 64
@@ -32,22 +30,12 @@ MATCH_TRANSLATE_MAX_DEPTH: t.CDefine = 64
class MatchGenMixin:
"""match 语句翻译 Mixin(P6 方法化)"""
# ============================================================
# _case_tail_unreachable - case 体末尾结构性不可达判定
#
# case 体翻译后当前块若未终止,且末句是控制流语句(If/While/For/Match)
# 且其 merge 结构性不可达(全分支 return,如 case Int(bits): if ... return)
# 时,当前块是控制流的 merge(无 terminator),与 HandlesIf L309-315/
# ClassGenVTable L1084 同语义补 unreachable。返回 1= 已补 unreachable
# (调用方不应 br end_bb);0= 正常收尾(应 br end_bb)。
#
# 根因(ClassGenEnum._get_type_align):case 体末句 If 全 return 时,If
# 处理器把 builder 停在无 terminator 的 merge 块,case 循环的
# is_terminated 检查误判"未终止" → 补 br end_bb → rm_merge_reach=1 →
# 全 case return 的 match 被误判 merge 可达 → 函数收尾 fail-fast
# "流程掉出末尾"。不含 Try/With:其处理器不维护 _ctrl_merge_reachable,
# 避免残留值误判。
# ============================================================
# case 体翻译后当前块未终止、且末句是控制流语句(If/While/For/Match)且其
# merge 结构性不可达(全分支 return)时,当前块即该 merge(无 terminator),
# 与 HandlesIf L309-315 / ClassGenVTable L1084 同语义补 unreachable。
# 返回 1=已补 unreachable(调用方不应 br end_bb);0=正常收尾(应 br end_bb)。
# 不含 Try/With:其处理器不维护 _ctrl_merge_reachable,避免残留值误判。
Gen: Generator.Generator | t.CPtr
def _case_tail_unreachable(self, trans: HT.Translator | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
@@ -97,11 +85,9 @@ class MatchGenMixin:
if builder is None or func is None:
return 0
# 追加90:match merge 可达性重置(分派前初始化为 0;各处理函数
# renum/sequence/simple 内部用各自的局部标志记录 br end_bb 并在
# 收尾覆盖,见 _handle_renum_match/_handle_sequence_match/
# _handle_simple_match——切勿把标志声明在 Handle 这里(分派函数),
# 否则子函数引用的同名变量会退化为隐式全局,跨 match 互相污染)。
# match merge 可达性重置(分派前初始化为 0)。标志必须由各处理函数
# (_handle_renum_match/_handle_sequence_match/_handle_simple_match)
# 内部声明并收尾覆盖:声明在 Handle 这里会退化为隐式全局,跨 match 污染。
trans._ctrl_merge_reachable = 0
m_node: ast.Match | t.CPtr = (ast.Match | t.CPtr)(node)
@@ -392,9 +378,9 @@ class MatchGenMixin:
sw_tail: llvmlite.SwitchCase | t.CPtr = None
sw_count: int = 0
# 修复: 应提交 case 的臂计数(非 default 且模式必须产出 case 值:
# MatchClass 必有变体 tag / MatchValue 必有常量值)。与 sw_count 不等
# 即"静默丢 case" —— build_switch 前 fail-fast(见 step 5 前的硬校验)。
# 应提交 case 的臂计数(非 default 且模式必须产出 case 值:MatchClass
# 必有变体 tag / MatchValue 必有常量值);与 sw_count 不等即静默丢 case,
# build_switch 前 fail-fast(见 step 5 前的硬校验)。
need_case: int = 0
# 修复: 未取到 case 值的臂诊断串(供硬校验报错打印 臂序号/变体名/tag)
# 容量 = 8 条 × 单条上限(diag_tmp 128,strcat 无边界检查 → 必须够大)
@@ -405,15 +391,10 @@ class MatchGenMixin:
self.Gen.TypeGenErr.fatal_error(node, "match 诊断缓冲区分配失败", "TypeError")
miss_buf[0] = '\0'
miss_cnt: int = 0
# 修复: 变体表 RenumClassNames 按**裸类名**注册
# (ClassGenEnum.L976-982 存 ClassDef.name,如 "LLVMType"),而 2a 路径的
# renum_name 来自变量注解的书写形态(Spec.SourceName;
# TypeGenResolve.extract_class_name_from_annotation L1648-1682 对 Attribute
# 返回模块限定名 "llvmlite.LLVMType")。检测侧 _is_renum_by_entry →
# find_struct(StructGenMixin L770-830 支持模块限定名 + 裸名回退)通过,
# 但精确 strcmp 的 _lookup_renum_variant_in_class 失配 → 恒 -1 →
# 该臂被静默丢弃(TPV1 产物空 case 列表根因,FuncGenDef.vp:993-996 同款记载)。
# 去模块前缀:复用 find_struct 同款 L1078 括号外取点(泛型下标内的点不算)。
# 变体表 RenumClassNames 按裸类名注册(ClassDef.name),而 renum_name 来自
# 注解书写形态(Spec.SourceName,Attribute 会带模块限定名 "mod.LLVMType")→
# 精确 strcmp 失配恒 -1 → 该臂被静默丢弃。故先去模块前缀(复用 find_struct
# 同款「括号外取点」判定,泛型下标内的点不算)。
tag_cls_bare: str = renum_name
rn_dot: str = self.Gen.StructGen._last_dot_outside_brackets(renum_name)
if rn_dot is not None and rn_dot[1] != '\0':
@@ -444,10 +425,9 @@ class MatchGenMixin:
if pk == ast.ASTKind.MatchClass:
vnm = self._extract_variant_name_from_cls(pat)
if vnm is not None:
# 修复: 先用裸类名查表(与模式侧 2b 路径同口径,变体表就是
# 按裸类名注册的);失败再用注解书写形态重试(泛型特化
# mangled 名等确需带点的场景)。两者都失败 → tag=-1 →
# step 5 前的硬校验 fail-fast,不再静默丢 case。
# 先用裸类名查表(变体表按裸类名注册,与模式侧 2b 路径同口径);
# 失败再用注解书写形态重试(泛型特化 mangled 名确需带点)。
# 两者都失败 → tag=-1 → step 5 前的硬校验 fail-fast。
tag = self.Gen.ClassGenEnum._lookup_renum_variant_in_class(
tag_cls_bare, vnm)
if tag < 0 and string.strcmp(tag_cls_bare, renum_name) != 0:
@@ -506,9 +486,9 @@ class MatchGenMixin:
# 缓存 mc 指针(pass2 复用,避免重索引)
mc_arr[collected] = sizet(mc)
collected = collected + 1
# 修复: 计入"必须提交 case"的臂(MatchClass 必有变体 tag、
# MatchValue 必有常量值)。注意必须在 MAX_MATCH_CASES 上限判定
# 之后计数——超上限的臂已 break,不参与校验(与旧行为一致)。
# 计入"必须提交 case"的臂(MatchClass 必有变体 tag、MatchValue 必有
# 常量值);必须在 MAX_MATCH_CASES 上限判定之后计数(超上限的臂已
# break,不参与校验)。
if is_def == 0 and pat is not None:
pkm: int = pat.kind()
if pkm == ast.ASTKind.MatchClass or pkm == ast.ASTKind.MatchValue:
@@ -526,9 +506,9 @@ class MatchGenMixin:
sw_tail.Next = sc
sw_tail = sc
sw_count = sw_count + 1
# 修复: 记录"应产出 case 值但未产出"的臂(MatchClass 变体 tag 查表失败
# / MatchValue 常量翻译失败)。此处只收集诊断信息,是否致命由 step 5
# 前的硬校验统一判定(保证判定口径唯一,便于将来扩展 MatchOr 等)。
# 记录"应产出 case 值但未产出"的臂(MatchClass 变体 tag 查表失败 /
# MatchValue 常量翻译失败)。此处只收集诊断,是否致命由 step 5 前的
# 硬校验统一判定(口径唯一)。
if is_def == 0 and cv is None and miss_cnt < 8 and pat is not None:
pkm_d: int = pat.kind()
if pkm_d == ast.ASTKind.MatchClass or pkm_d == ast.ASTKind.MatchValue:
@@ -540,13 +520,11 @@ class MatchGenMixin:
i = i + 1
# 5. 发射 switch 指令
# 修复: **禁止静默丢 case** —— 必须提交 case 的臂数(MatchClass/MatchValue,
# 非 default)必须等于实际提交给 build_switch 的 sw_count。不等即说明该臂
# 的 case 值没算出来(REnum 变体 tag 查表失败最常见),此时 build_switch
# 会发射**空 case 列表** → 臂体照样生成但结构性不可达(TPV1 产物 57% 空
# case 列表;FuncGenDef.vp:993-996 记载的 _is_void 恒 0 即此)。
# 修复前:无任何校验,静默发射;修复后:fail-fast 并打印类名/查表名/
# 应提交数/实际数/各未取到值臂的 序号(变体名,tag),便于定位变体表失配。
# 禁止静默丢 case:必须提交 case 的臂数(MatchClass/MatchValue,非 default)
# 必须等于实际提交给 build_switch 的 sw_count。不等即该臂 case 值未算出
# (REnum 变体 tag 查表失败最常见)→ build_switch 发射空 case 列表、臂体
# 结构性不可达。故不等即 fail-fast,打印类名/查表名/应提交数/实际数/
# 各未取到值臂的 序号(变体名,tag),便于定位变体表失配。
if need_case != sw_count:
self.Gen.TypeGenErr.fatal_error(
node,
@@ -716,9 +694,8 @@ class MatchGenMixin:
# 字段名查找作为回退(若字段名与 MatchAs 名一致可直接命中)
if payload_idx >= renum_entry.FieldCount:
# class_name 用 CSizeT 指针值比较:必须传 renum_entry.Name
# (注册时 _update_renum_alias_class_ptr 统一后的 perm_pool
# 指针);renum_name(AnnotClassName 等)是不同指针恒不匹配
# → 恒 -1 → fail-fast 误报(追加73d 同款指针陷阱)
# (注册时 _update_renum_alias_class_ptr 统一后的 perm_pool 指针);
# renum_name(AnnotClassName 等)是不同指针,恒不匹配 → 误报 fail-fast。
alt_idx: int = self.Gen.ClassGenEnum._lookup_renum_field_alias(
renum_entry.Name, bind_name)
if alt_idx >= 1:
@@ -756,19 +733,13 @@ class MatchGenMixin:
sub_pat,
f"REnum 变体字段 '{bind_name}' 地址计算失败",
"TypeError")
# 变体成员实际类型(对齐 TPC class_members L300-307/L376-382):
# 注册时别名表存了成员注解解析出的类型(perm_pool 深拷贝)。
# 用它把槽位地址 bitcast 成 Ptr(成员类型),使绑定变量按成员
# 类型读 payload——Struct.Name: char|t.CPtr = i8*,否则按
# 槽位 i64 读 → is 比较/下标/strlen 等字符串操作翻译失败
# (CallGenMixin L4631 type_name_sp is not None 根因)。
# 注意:必须按 (class, 变体名, 位置) 定位——MatchAs 名是局部
# 变量名(type_name_sp),不是变体字段名(Name),按字段名
# 查恒不匹配(追加76 第一次修复失效根因);同一 payload 位置
# 跨变体类型也不同(Int.Bits=i32 vs Struct.Fields=Ptr)。
# 自引用类型(如 Pointee: LLVMType|t.CPtr)注册期解析失败 →
# 查不到类型 → 回退槽位类型(原始位宽在嵌套 match 中经
# subject bitcast 仍可用)。
# 变体成员实际类型(对齐 TPC class_members):注册时别名表存了成员
# 注解解析出的类型(perm_pool 深拷贝),用它把槽位地址 bitcast 成
# Ptr(成员类型),使绑定变量按成员类型读 payload(否则按槽位 i64 读 →
# is 比较/下标/strlen 等字符串操作翻译失败)。
# 必须按 (class, 变体名, 位置) 定位:MatchAs 名是局部变量名而非字段名,
# 按字段名查恒不匹配;同一 payload 位置跨变体类型也不同。
# 自引用类型注册期解析失败 → 查不到类型时回退槽位类型。
mem_ty_m: llvmlite.LLVMType | t.CPtr = None
vnt_m: str = self._extract_variant_name_from_cls(pat)
if vnt_m is not None and renum_entry is not None \
@@ -913,11 +884,10 @@ class MatchGenMixin:
i: int = 0
while i < case_count:
# 容量防线(与 _translate_match_simple / match-list 路径的
# `collected < MAX_MATCH_CASES` 守卫对齐):默认 case(case _)
# 分支此前**不校验** collected 就写 check_arr/body_arr/def_arr/
# nb_arr/guard_arr[collected],case 数超过 MAX_MATCH_CASES 时
# 越界写穿 pool 相邻块(可破坏相邻空闲块的 free-list next 指针)。
# 不静默丢弃 case(会产出漏 case 的错误代码),直接 fail-fast。
# `collected < MAX_MATCH_CASES` 守卫对齐):默认 case(case _)分支也须校验
# collected 再写 check_arr/body_arr/def_arr/nb_arr/guard_arr[collected],
# 否则 case 数超 MAX_MATCH_CASES 会越界写穿 pool 相邻块。超限直接
# fail-fast,不静默丢弃 case。
if collected >= MAX_MATCH_CASES:
self.Gen.TypeGenErr.fatal_error(
node,
@@ -1292,14 +1262,10 @@ class MatchGenMixin:
builder, pool, trans.Module, mv2.value,
trans._funcs, trans._func_count, trans)
elif pk == ast.ASTKind.MatchClass:
# 修复: 本路径(非 REnum 分派)不为 class-pattern 臂产出任何
# case 值 → 该臂被静默丢弃、臂体生成但不可达。TPC 侧
# (HandlesMatch.py:132-160)对"REnum 检测失败但 case 是
# MatchClass"有变体名→tag 兜底,本侧没有 ⇒ 静默不一致。
# 若该 class-pattern 确实指向已注册 REnum(_is_renum_class_name /
# _is_renum_by_entry 命中),说明是"REnum 分派失配"
# (FuncGenDef.vp:993-996 记载的 _is_void 恒 0 即此),必须
# fail-fast 暴露,不得静默丢 case(禁止兜底掩盖)。
# 本路径(非 REnum 分派)不为 class-pattern 臂产出 case 值 → 该臂被
# 静默丢弃、臂体不可达。若该 class-pattern 确实指向已注册 REnum
# (_is_renum_class_name / _is_renum_by_entry 命中),说明是 REnum
# 分派失配,必须 fail-fast 暴露,不得静默丢 case(禁止兜底掩盖)。
rnm_nr: str = self._extract_renum_name_from_cls(pat)
if rnm_nr is not None and (
self.Gen.ClassGenEnum._is_renum_class_name(rnm_nr) != 0
+11 -19
View File
@@ -841,10 +841,9 @@ class StmtGenCifMixin:
llvmlite.build_br(builder, merge_bb)
else_branches_merge = 1
# 7. merge 块可达性(追加90,末句控制流收尾判定用):
# 无 else 时 cond_br 直接跳 merge(恒可达);有 else 时任一分支
# br 到 merge 即可达;全分支 return(无 br)才结构性不可达。
# 经 HandlesBase 模块级全局传递(避免依赖 Translator 字段表)
# 7. merge 块可达性(末句控制流收尾判定用):无 else 时 cond_br 直接跳 merge(恒可达);
# 有 else 时任一分支 br 到 merge 即可达;全分支 return(无 br)才结构性不可达。
# 经 HandlesBase 模块级全局传递(避免依赖 Translator 字段表)。
trans._ctrl_merge_reachable = 1
if has_else == 1 and then_branches_merge == 0 and else_branches_merge == 0:
trans._ctrl_merge_reachable = 0
@@ -1258,11 +1257,9 @@ class StmtGenCifMixin:
if skc_r.int_val < 0:
step_neg_const = 1
# 3.5 循环变量存储位宽:随 range 参数类型(uint64→i64 / uint16→i16 /
# uint8→i8 / 其余→i32),不固定 i32。此前固定 i32 alloca,使
# uint64 上界的循环变量被截断(syscall MMAP 的
# range(heap_end, new_end, 4096) 生成 alloca i32 + trunc i64→i32
# → 高 32 位丢失 → 映射地址错误)。仅当循环变量尚未声明时生效;
# 3.5 循环变量存储位宽随 range 参数类型(uint64→i64 / uint16→i16 / uint8→i8 / 其余→i32),
# 不固定 i32:固定 i32 alloca 会截断 uint64 上界(range(heap_end, new_end, 4096)
# 生成 trunc i64→i32 → 高 32 位丢失 → 映射地址错误)。仅当循环变量尚未声明时生效;
# 已声明变量沿用其 alloca 元素类型(见下方 loop_var_ty 解引用)。
loop_store_ty: llvmlite.LLVMType | t.CPtr = i32_ty
range_arg_bits: int = 0
@@ -1407,12 +1404,9 @@ class StmtGenCifMixin:
if cur_bits < stop_bits:
cmp_lhs = llvmlite.build_sext(builder, cur_i, stop_val.Ty)
else:
# 循环变量位宽 > stop 位宽(如 sizet 循环变量 + range(i32)):
# 应把 cur_i 截断到 stop_val 类型(低向高才 sext/zext,高向低用
# trunc)。原实现反了——把小的 stop_val trunc 到大的 loop_var_ty,
# 生成 `trunc i32 to i64` 扩宽非法 IR,llc 报
# "invalid cast opcode for cast from 'i32' to 'i64'"
# (VpiReader.vp range(arg_count) 追加86 根因)。
# 循环变量位宽 > stop 位宽(如 sizet 循环变量 + range(i32)):应把 cur_i 截断到
# stop_val 类型(低向高才 sext/zext,高向低用 trunc)。反过来把小的 stop_val
# 扩宽会生成非法 IR(llc "invalid cast opcode for cast from 'i32' to 'i64'")。
cmp_lhs = llvmlite.build_trunc(builder, cur_i, stop_val.Ty)
# 有符号基准谓词:step 为负时条件反转(i > stop,SGT);正/缺省 i < stop(SLT)
range_cmp_op: int = llvmlite.ICMP_SLT
@@ -2052,10 +2046,8 @@ class StmtGenCifMixin:
self.Gen.TypeGenErr.fatal_error(for_node, "数组迭代: 无法解析数组类型")
# 数组值 → 临时 alloca(数组值不是指针,需拷贝后 GEP 迭代)
# 必须在 entry 块创建:若在 for 语句所在块(非 entry)alloca,
# 旧版 LLVM 会将其作为动态 alloca → 运行时 sub rsp 大数组,
# 调用栈深时 rsp 骤降踩未映射页 → 概率性 #PF
# (devfs_directory.find 的 for i in result: result=[256 x str] 实测崩溃 rip 0x103002)。
# 必须在 entry 块创建:非 entry alloca 会被 LLVM 当动态 alloca → 运行时 sub rsp 大数组,
# 调用栈深时 rsp 骤降踩未映射页 → 概率性 #PF。
arr_tmp: llvmlite.Value | t.CPtr = self.Gen.VarGen._alloca_at_entry(builder, arr_ty)
if arr_tmp is None:
self.Gen.TypeGenErr.fatal_error(for_node, "数组迭代: 临时存储分配失败")
+5 -6
View File
@@ -481,12 +481,11 @@ class StmtGenDeclMixin(StmtGenCifMixin.StmtGenCifMixin):
string.strcpy(name_buf, name)
entry.Name = name_buf
# BaseTy 深拷贝:LLVMType 是 pool 分配,StdlibAlloc=1(Phase2 mark2 后)
# 时 reset 回收悬空 → Phase B 枚举访问 ConstInt(悬空 BaseTy) 生成 void
# 值(DecoratorMarkerTest `store void 2` llc 报错根因)。Phase1 1a
# (StdlibAlloc=0,mark1 前)保持 pool 存活,无需拷贝。
# 注意:StdlibAlloc 是 StructGenMixin 字段,本 Mixin 方法体不能直接
# self.StdlibAlloc(NoVTable 按本 Mixin 字段布局编译),走模块级委托。
# BaseTy 深拷贝:LLVMType 是 pool 分配,StdlibAlloc=1 时 reset 回收悬空 → Phase B
# 枚举访问 ConstInt(悬空 BaseTy) 生成 void 值(llc 报错)。Phase1 1a(mark1 前)
# 保持 pool 存活,无需拷贝。
# 注意:StdlibAlloc 是 StructGenMixin 字段,本 Mixin 方法体不能直接 self.StdlibAlloc
# (NoVTable 按本 Mixin 字段布局编译),走模块级委托。
if self.Gen.StructGen.get_stdlib_alloc() != 0:
base_ty_copy: llvmlite.LLVMType | t.CPtr = llvmlite.DeepCopyType(
CompilerFactory.factory.PermPool, base_ty)
+5 -7
View File
@@ -131,9 +131,8 @@ class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
if del_at is not None:
self.Gen.CallGenProp._try_property_deleter(
pool, builder, mod, del_at, trans)
# del obj[key] → __delitem__(obj, key)
# (对齐 TPC HandlesDelete.py L14-25;此前 Subscript 目标被静默
# 忽略,HashTableTest Test 11 的 del ht3[del_buf] 未执行 → len 不变)
# del obj[key] → __delitem__(obj, key)(对齐 TPC HandlesDelete.py)。
# 不能静默忽略 Subscript 目标——否则 del 不生效(len 不变)。
elif tgt.kind() == ast.ASTKind.Subscript:
dl_sub: ast.Subscript | t.CPtr = (ast.Subscript | t.CPtr)(tgt)
if dl_sub is not None and dl_sub.value is not None and dl_sub.slice is not None:
@@ -313,10 +312,9 @@ class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
hname_t = ((ast.Name | t.CPtr)(htype_t)).id
elif htype_t.kind() == ast.ASTKind.Attribute:
hname_t = ((ast.Attribute | t.CPtr)(htype_t)).attr
# 匹配编码缓冲:必须是 int*(i32 步长)——collect_descendant_codes
# 按 out_codes[i](4 字节)写入;若用 sizet*(8 字节)读,
# 多码时读出 (code[i+1]<<32)|code[i] 的 64 位拼接垃圾(单码时
# 高位恰为 0 掩盖;Test 2/3/8 父类多码捕获失败的根因)。
# 匹配编码缓冲:必须是 int*(i32 步长)——collect_descendant_codes 按
# out_codes[i](4 字节)写入;用 sizet*(8 字节)读会读出 64 位拼接垃圾
# (多码时 code[i+1]<<32|code[i];单码时高位恰为 0 掩盖)。
codes_buf_t: int | t.CPtr = pool.alloc(64 * 4)
if codes_buf_t is None:
return 0
+48 -84
View File
@@ -17,14 +17,9 @@ import lib.core.Gen.Type.TypeSpec as TSpec
import lib.core.Gen.GenApi as GenApi
# ============================================================
# StructGenMixin - 结构体注册/字段查找 Mixin(对象化 P2-2,wiki/14-oo-refactor.md)
#
# 从 HandlesStruct.vp 迁移:
# 全局状态(_struct_table/_struct_count/_g_stdlib_alloc/
# _visible_structs/_strict_visibility)→ 本类字段
# 模块级函数(register_struct/find_struct/lookup_field/...)→ 本类方法
#
# StructGenMixin - 结构体注册/字段查找 Mixin
#
# 职责:struct 表注册与查找(名/类型索引)、字段查找、跨模块归属匹配。
# @t.NoVTable:静态绑定(方法沿 MRO 直接调用 + this 调整,无 vtable 开销)。
# ============================================================
@@ -35,9 +30,8 @@ import lib.core.Gen.GenApi as GenApi
STRUCT_MAX: t.CDefine = 512
# 名 | 类型 下标索引槽数(2 的幂且 > 2*STRUCT_MAX ⇒ 负载 < 0.5,探测必然终止)
STRUCT_IDX_CAP: t.CDefine = 1024
# 字段槽位上限制(与 HandlesStruct 保持一致;Generator 43 Mixin 展平 ~120 字段,
# 2026-08-16 追加78:64 截断导致第 9+ 父类字段(StmtGenMixin.DiagStmtCount 等)
# 丢失 → __init__ 写 self.DiagStmtCount "属性写入失败")
# 字段槽位上限制(与 HandlesStruct 保持一致):Generator 43 Mixin 展平后约 120 字段,
# 上限取 64 会导致第 9+ 父类字段丢失 → __init__ 写属性失败。
FIELD_MAX: t.CDefine = 256
FIELD_NAME_MAX: t.CDefine = 64
# 多继承(MRO)支持:单类最大父类数 / MRO 链最大长度(Generator 43 父类;
@@ -222,17 +216,12 @@ class StructGenMixin:
addr: uint64 = uint64(self.StructTable) + i * entry_size
return (GenTypes.StructEntry | t.CPtr)(t.CVoid(addr, t.CPtr))
# ============================================================
# 名 | 类型 索引(开放寻址 | 线性探测;槽存 index+1,0=空槽)
#
# 目的:把 find_struct/find_struct_by_module(名)与 find_struct_by_type
# (类型指针)从 O(n) 全表扫描降为期望 O(1)。索引在注册处同步维护;
# 冲突逐键比较(名用 strcmp,类型用 Ty 指针值),不能只比哈希。
# 索引"可信"条件 = StructIdxBuilt == StructCount(每次注册后同步);
# 不可信/不可用时查找一律回退原线性扫描,保证语义逐字节一致。
# 条目被中和(Name[0]=0 / Ty=None,见 ClassGenDef/ClassGenEnum)后索引槽成为
# 失效键:查找探测时键不等 → 继续探测/判定未命中,不会返回失效条目。
# ============================================================
# 目的:把 find_struct/find_struct_by_module(名)与 find_struct_by_type(类型指针)
# 从 O(n) 全表扫描降为期望 O(1)。索引在注册处同步维护,冲突逐键比较(名用
# strcmp,类型用 Ty 指针值),不能只比哈希。
# 可信条件 = StructIdxBuilt == StructCount;不可信/不可用时回退线性扫描。
# 条目被中和(Name[0]=0 / Ty=None)后索引槽成失效键,探测时键不等即判定未命中。
def _struct_entry_name(self, i: int) -> str:
"""取第 i 个条目的 Name(供索引探测比较),越界/空返回 None"""
e: GenTypes.StructEntry | t.CPtr = self._get_struct_entry(i)
@@ -420,10 +409,8 @@ class StructGenMixin:
return None
string.memset(entry.Fields, 0, field_size * FIELD_MAX)
# 复制类名(1b/Phase2(StdlibAlloc=1)用永久池跨 reset 存活;
# 1a(StdlibAlloc=0)用主 pool,mark1 前不回收同样存活。
# 此前 1a 用 pool、1b 用 stdlib,find_struct_by_module 的 strcmp
# 曾读悬空 Name 越界 → G3 LinkedNode.prepend 挂起根因)
# 复制类名(1b/Phase2(StdlibAlloc=1)用永久池跨 reset 存活;1a 用主 pool,
# mark1 前不回收同样存活)。不复制则 find_struct_by_module 的 strcmp 读悬空 Name。
name_pool: rowptr[memhub.MemBuddy] = pool
if self.StdlibAlloc != 0:
name_pool = CompilerFactory.factory.PermPool
@@ -576,9 +563,8 @@ class StructGenMixin:
if fe is None:
return -1
# 复制字段名(StdlibAlloc=1 用永久池跨 reset 存活,
# 否则 lookup_field 的 strcmp(fe.Name, ...) 读悬空越界挂起,
# 与 register_struct 的 Name 悬空同根因)
# 复制字段名(StdlibAlloc=1 用永久池跨 reset 存活);否则 lookup_field 的
# strcmp(fe.Name, ...) 读悬空越界。
fname_pool: rowptr[memhub.MemBuddy] = pool
if self.StdlibAlloc != 0:
fname_pool = CompilerFactory.factory.PermPool
@@ -591,14 +577,11 @@ class StructGenMixin:
fe.Name = name_buf
fe.Index = idx
# 1b 阶段(StdlibAlloc=1)字段类型必须深拷贝到 stdlib:
# register_struct 已迁 Fields/Name/ModuleSha1/Ty(translate_class_def L2566
# DeepCopyType),但字段级 Ty 仍存 pool 指针,本文件 reset_to(mark1) 回收后,
# 跨文件读取(字段展平、多继承 this 调整、_apply_struct_defaults 的
# build_gep_struct 用 fe.Ty 作 GEP 结果类型)解引用悬空内存 → 崩溃点漂移
# (json/string/builtins 之间)。
# 注:注解类名(原 fe.AnnotClassName)已并入 Spec.SourceName,Spec 一律
# PermPool 分配 ⇒ 天然跨 reset 存活,无需在此深拷贝。
# 1b 阶段(StdlibAlloc=1)字段类型必须深拷贝到 stdlib:字段级 Ty 若仍存 pool
# 指针,本文件 reset_to(mark1) 回收后跨文件读取(字段展平、多继承 this 调整、
# _apply_struct_defaults 的 build_gep_struct 用 fe.Ty 作 GEP 结果类型)会解引用
# 悬空内存。
# 注:注解类名已并入 Spec.SourceName,Spec 一律 PermPool 分配 ⇒ 天然跨 reset 存活。
if self.StdlibAlloc != 0:
dp_pool_sg: rowptr[memhub.MemBuddy] = CompilerFactory.factory.PermPool
if dp_pool_sg is None:
@@ -860,11 +843,9 @@ class StructGenMixin:
if dot_pos is not None:
class_name: str = dot_pos + 1
if class_name is not None and class_name[0] != '\0':
# 同名唯一性检查(对齐上方限定名路径 L543-554):跨模块同名类
# (如 VKernel gfx 与 App gfx 都定义 Scene3D)存在时不得返回
# 先注册的错误 entry——否则模块段无法解析的限定名(如
# "9aab.Scene3D")被劫持到错误模块 → 返回类型解析查错 text.ll
# → Phase A _deps_pending 死锁(Scene3D ret_sha1=0ae 根因)。
# 同名唯一性检查(对齐上方限定名路径):跨模块同名类(如 VKernel gfx
# 与 App gfx 都定义 Scene3D)存在时不得返回先注册的错误 entry——否则
# 模块段无法解析的限定名被劫持到错误模块 → 返回类型解析查错 text.ll。
same_cnt_b: int = 0
same_entry_b: GenTypes.StructEntry | t.CPtr = None
for i in range(self.StructCount):
@@ -940,10 +921,9 @@ class StructGenMixin:
entry.MethodsEmitted = 0
reset_count += 1
continue
# 泛型特化类按触发文件 sha1 匹配:特化类 ModuleSha1 是模板 sha1
# (方法名带模板前缀),无法匹配当前作废文件。不重置则特化方法体
# 残留 MethodsEmitted=1,下遍不补译 → text.ll 只有 call 无 define
# → ld.lld undefined symbol(list[ast.ClassDef|t.CPtr].* 根因)
# 泛型特化类按触发文件 sha1 匹配:特化类 ModuleSha1 是模板 sha1(方法名
# 带模板前缀),无法匹配当前作废文件。不重置则特化方法体残留
# MethodsEmitted=1,下遍不补译 → text.ll 只有 call 无 define。
if entry.SpecTriggerSha1 is not None:
if string.strcmp(entry.SpecTriggerSha1, sha1) == 0:
entry.MethodsEmitted = 0
@@ -1036,15 +1016,11 @@ class StructGenMixin:
# 第二遍: 名称回退(is 未匹配到)
ty_name: str = self._extract_struct_name(struct_ty)
if ty_name is not None:
# 加强 SHA1 命名空间隔离:类型名形如 "3d4afd9cdffeba6d.Thread",
# 提取 16 位 hex 前缀与 entry.ModuleSha1 前 16 字符精确比较,
# 禁止 strstr 裸名跨模块抓同名类(sched.Thread 被 includes/
# vthreading.vp 同名 Thread 顶掉根因:strstr("sha1.Thread",
# "Thread") 命中 vthreading 的 entry → 其 arg@3 被当成
# sched.Thread.arg(应 4)→ th.func 被 th.arg=None 覆盖 →
# _thread_entry call 0 → #UD/#GP)。strncmp 不分配临时缓冲。
# 前缀判定走唯一真源 GenApi.sha1_prefix_len(C10 收敛:此前只判
# 首个 '.' 前恰 16 字节、不校验 hex 形态)。
# 加强 SHA1 命名空间隔离:类型名形如 "3d4afd9cdffeba6d.Thread",提取 16 位
# hex 前缀与 entry.ModuleSha1 前 16 字符精确比较,禁止 strstr 裸名跨模块抓
# 同名类(sched.Thread 被 includes/vthreading.vp 同名 Thread 顶掉 → 字段错位
# → _thread_entry call 0)。strncmp 不分配临时缓冲。
# 前缀判定走唯一真源 GenApi.sha1_prefix_len。
ty_has_sha1_lf: int = 0
if GenApi.sha1_prefix_len(ty_name) != 0:
ty_has_sha1_lf = 1
@@ -1152,14 +1128,11 @@ class StructGenMixin:
return fe
return None
# 模块限定名(module.Class,module 非 t/c 内置):按模块归属解析,
# 禁止裸名回退抓跨模块同名类(根因:VKernel sched.Thread 被 includes/
# vthreading.vp 同名 Thread 顶掉 → 'tid' 字段缺失)。
# 模块名经 StubMergerStore sha1 映射表解析为模块 sha1,再精确匹配
# name+sha1(vpi stub 注入 / 定义侧注册的裸名 entry)。
# 注意:泛型特化名含下标(numpy.ndarray[t.CDouble]),取点必须跳过
# 下标内部(_last_dot_outside_brackets),否则模块名截成
# 'numpy.ndarray[t' → sha1 无法解析(NumpyTest FAIL 根因)。
# 模块限定名(module.Class,module 非 t/c 内置):按模块归属解析,禁止裸名回退
# 抓跨模块同名类(根因:VKernel sched.Thread 被 includes/vthreading.vp 同名
# Thread 顶掉 → 'tid' 字段缺失)。模块名经 StubMergerStore sha1 映射表解析为
# 模块 sha1,再精确匹配 name+sha1(vpi stub 注入 / 定义侧注册的裸名 entry)。
# 注意:泛型特化名含下标,取点必须走 _last_dot_outside_brackets,否则模块名截错。
dot_pos_q: str = self._last_dot_outside_brackets(class_name)
if dot_pos_q is not None and dot_pos_q[1] != '\0':
bare_q: str = dot_pos_q + 1
@@ -1196,10 +1169,9 @@ class StructGenMixin:
mod_entry_q: GenTypes.StructEntry | t.CPtr = \
self.find_struct_by_module(bare_q, mod_sha1_q)
if mod_entry_q is not None:
# 依赖未就绪容忍(对齐裸名路径 L926):FieldCount==0 的
# 空类(注册不完整/源模块未编译)→ 返回 None 让上层标记
# _deps_pending 下遍重译,不在此 fatal(WIN32_FIND_DATAA
# 字段访问 cFileName 报 FieldCount=0 根因之一)。
# 依赖未就绪容忍(对齐裸名路径):FieldCount==0 的空类
# (注册不完整/源模块未编译)→ 返回 None 让上层标记
# _deps_pending 下遍重译,不在此 fatal。
if mod_entry_q.FieldCount == 0:
stdlib.free(mod_buf_q)
return None
@@ -1210,12 +1182,10 @@ class StructGenMixin:
if string.strcmp(fe_q.Name, field_name) == 0:
stdlib.free(mod_buf_q)
return fe_q
# REnum 变体字段别名回退(与裸名路径 L950-963 一致):
# 模块限定访问(llvmlite.LLVMType.Pointee)类命中后字段
# 未找到时,直接查变体别名(REnum 字段是 _p1/_p2 槽位,
# Pointee 是 Ptr 变体成员)。此前该回退只在裸名路径,
# 模块限定路径此处 fatal 先触发 → 永远走不到
# (CallGenDispatch.vp L1005 rk_pointee.Pointee 根因)。
# REnum 变体字段别名回退(与裸名路径一致):模块限定访问
# (llvmlite.LLVMType.Pointee)类命中后字段未找到时,直接查
# 变体别名(REnum 字段是 _p1/_p2 槽位)。此前该回退只在裸名
# 路径,模块限定路径此处 fatal 先触发 → 永远走不到。
rn_cls_q: str = mod_entry_q.Name
if rn_cls_q is None:
rn_cls_q = bare_q
@@ -1270,15 +1240,11 @@ class StructGenMixin:
if fe is not None and fe.Name is not None:
if string.strcmp(fe.Name, field_name) == 0:
return fe
# REnum 变体字段别名回退:REnum 类(如 LLVMType)的变体内部字段
# (Ptr.Pointee 等)不是普通字段,而是 _p1/_p2 槽位别名。此前在调用方
# (ExprGenMixin 按需过滤 L5046/L5500)回退,但本函数字段未找到的
# VLogger.error 立即 sys.exit(1),调用方回退永远执行不到("LLVMType 已注册
# 但字段 Pointee 未找到"四次依旧的根因)。此处直接查变体别名,所有调用方
# 统一受益。注意:_lookup_renum_field_alias 用 class_name 指针值比较,
# 必须传 entry.Name(register_struct 复制 + _update_renum_alias_class_ptr
# 统一后的池指针),传调用方 class_name(AnnotClassName 等不同指针)
# 恒不匹配 → 返回 -1 → 回退失效。
# REnum 变体字段别名回退:REnum 类(如 LLVMType)的变体内部字段(Ptr.Pointee
# 等)不是普通字段,而是 _p1/_p2 槽位别名。若留给调用方回退,本函数字段未找到
# 时的 fatal 会先 sys.exit(1),调用方回退永远执行不到,故此处直接查变体别名。
# 注意:_lookup_renum_field_alias 用 class_name 指针值比较,必须传 entry.Name
# (register_struct 复制 + _update_renum_alias_class_ptr 统一后的池指针)。
rn_cls_l: str = entry.Name
if rn_cls_l is None:
rn_cls_l = class_name
@@ -1908,10 +1874,8 @@ class StructGenMixin:
if pointee is not None:
llvmlite.module_ensure_opaque_for_type(mod, pool, ty)
case llvmlite.LLVMType.Array(elem_ty, acount):
# 数组类型:元素如果是值类型结构体,需要完整定义
# (如 zhuff_decode_tree = {MemManager*, [576 x zhuff_decode_node],
# i32, i32}——Array 元素 node 只递归字段不发射定义 → llc "use of
# undefined type named '...zhuff_decode_node'",ZlibTest 根因)
# 数组类型:元素如果是值类型结构体,需要完整定义(Array 元素只递归
# 字段不发射定义 → llc "use of undefined type named '...'")。
if elem_ty is not None:
match elem_ty:
case llvmlite.LLVMType.Struct(_, _, _):
+22 -37
View File
@@ -16,17 +16,12 @@ import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.core.Gen.Generator as Generator
# ============================================================
# Gen/Translator.vp - 翻译器状态管理 + 主入口(自 HandlesTranslator 迁入)
# Gen/Translator.vp - 翻译器状态管理 + 主入口
#
# 负责:
# 1. Translator 类 - 状态管理(Module, 变量表, 函数表, 导入)
# 2. translate() - 主翻译入口
# 3. dump_ir() - IR 输出
#
# global/nonlocal 名称管理委托函数已下沉 lib.core.Gen.GenApi
# (add_global_name/is_global_name/add_nonlocal_name/get_nonlocal_type/
# is_nonlocal_name/get_nonlocal_index/clear_scope_names),本模块不再保留
# _g_gen 单例;Handles 目录迁移完成后整体删除。
# 负责: 1) Translator 类(状态管理:Module/变量表/函数表/导入)
# 2) translate() 主翻译入口 3) dump_ir() IR 输出
# global/nonlocal 名称管理委托函数在 lib.core.Gen.GenApi,
# TranslatorGenMixin 侧另有同名一行委托。
# ============================================================
@@ -142,9 +137,8 @@ class Translator:
_cur_builder: llvmlite.IRBuilder | t.CPtr
# 当前赋值语句的注解节点(AnnAssign 翻译 rhs 前设置,Assign 翻译前清空)。
# 供 c.Deref(i8*) 推断读取宽度:v: int = c.Deref(p) → bitcast i32* load,
# 对齐 TPC HandlesSpecialCall._infer_deref_target_type(DictTest 500→-12 根因:
# i8* Deref 固定读 1 字节 + sext 截断)。
# 供 c.Deref(i8*) 推断读取宽度:v: int = c.Deref(p) → bitcast i32* load
# (对齐 TPC HandlesSpecialCall._infer_deref_target_type;否则固定读 1 字节 + sext 截断)。
CurAssignAnnot: ast.AST | t.CPtr
# 当前赋值语句的 RHS 节点(AnnAssign 翻译 rhs 前设置,rhs 翻译后清除)。
@@ -498,8 +492,7 @@ class Translator:
self._translate_module_level(pool, mod, tree)
elif has_user_main == 0:
# 无用户 main → 只翻译模块级语句,不创建 wrapper main
# 修复:避免每个文件都生成 define i32 @main() 导致链接时 main 冲突
# 只有包含 def main() 的入口文件才会有 define i32 @main()
# (避免每个文件都生成 define i32 @main() 导致链接冲突;只有 def main() 的入口文件才生成)
self._translate_module_level(pool, mod, tree)
else:
# 用户已定义 main → 委托 HandlesMain 翻译模块级
@@ -553,13 +546,11 @@ class Translator:
# `d: dict` 注解 resolve_annotation_type 时 is_struct_visible=0
# 解析失败(DictTest 的 "变量类型注解解析失败")。
self._inject_auto_container_visibility(pool, tree)
# 扫描当前模块的 CTypedef 声明已由 Phase1 1b 文本扫描注册
# (RegisterCTypedefFromSource,见 Phase1Main.vp)——AST 字符串字段
# token 零拷贝指向 src_buf,跨阶段内容不稳定,此处 AST 扫描读 attr
# 会 0xC0000005(stdint.vp 崩溃根因),故移除。
# 跨模块 .vpi 注入:加载依赖模块的 .vpi 文件,
# 将函数 declare 声明注入到当前模块,解决跨模块函数调用找不到函数的问题
# 严格保证 declare_only == 0 时才注入(避免 Phase A-pre-inc 死锁)
# CTypedef 声明由 Phase1 1b 文本扫描注册(RegisterCTypedefFromSource)——
# AST 字符串字段 token 零拷贝指向 src_buf,跨阶段内容不稳定,此处 AST 扫描
# 读 attr 会 0xC0000005,故移除。
# 跨模块 .vpi 注入:加载依赖模块的 .vpi,把函数 declare 注入当前模块
# (严格保证 declare_only == 0 时才注入,避免 Phase A-pre-inc 死锁)。
if self._imported_modules is not None:
# 错误上下文帧:当前模块触发依赖 stub 加载的 import 位置
GenApi.push_trace(GenApi.get_current_file(), cur_imp_lineno)
@@ -586,19 +577,14 @@ class Translator:
GenApi.forward_declare_functions(self, tree)
added: int = GenApi.translate_children(self, tree)
# ============================================================
# 容器模块自动可见性注入(对齐 TPC _inject_auto_imports)
#
# TPC 在翻译前自动注入 `import _list / import _dict (+json)` 到源文件,
# 使 list/dict 内建容器类在符号表中可见。TPV 用可见性机制等效替代:
# _list.vp/_dict.vp 已由 Phase1 注册(可达集合含它们),这里只需在
# 当前文件翻译时把容器类名标记为可见结构体,裸名注解/构造即可解析。
#
# TPC 在翻译前自动注入 `import _list / import _dict (+json)`,使 list/dict 内建容器类
# 在符号表中可见;TPV 用可见性机制等效替代:_list.vp/_dict.vp 已由 Phase1 注册,
# 这里只需在当前文件翻译时把容器类名标记为可见结构体,裸名注解/构造即可解析。
# 检测模式(与 TPC Phase2Translator._inject_auto_imports 一致):
# - AnnAssign 注解裸名 Name(list/dict):d: dict
# - 构造调用 func 裸名 Name(list/dict):dict(bd)
# - Subscript 的 value 裸名 Name(list/dict):list[int]
# ============================================================
def _inject_auto_container_visibility(self, pool: rowptr[memhub.MemBuddy],
tree: ast.AST | t.CPtr):
"""检测 list/dict 容器使用,注入容器类可见性"""
@@ -735,13 +721,12 @@ class Translator:
if ag_s.annotation is not None:
self._check_container_type_node(
ag_s.annotation, need_list_box, need_dict_box)
# [FIX 2026-08-30] `[...]`/`{}` 字面量不再无条件触发 _list/_dict 导入:
# 字面量广泛用于编译期常量列表(c.Asm 的 op/inp/out CLOBBER 参数、
# 数组数据初始化),并非 list 容器使用。list/dict 容器的真实使用由
# 类型位置检测覆盖:`x: list` / `list[...]` 注解、`list(...)`/`dict(...)`
# 构造(_check_container_type_node 的 AnnAssign.annotation / Call.func /
# Subscript.value 分支)。无注解 `x = [...]` 若意图 list,编译器在
# translate_list_literal 阶段会 fail-fast 提示 _list 未注册(需显式注解)。
# `[...]`/`{}` 字面量不触发 _list/_dict 导入:字面量广泛用于编译期常量列表
# (c.Asm 的 op/inp/out CLOBBER 参数、数组数据初始化),并非 list 容器使用。
# list/dict 容器的真实使用由类型位置检测覆盖:`x: list` / `list[...]` 注解、
# `list(...)`/`dict(...)` 构造(_check_container_type_node 的 AnnAssign.annotation /
# Call.func / Subscript.value 分支)。无注解 `x = [...]` 若意图 list,会在
# translate_list_literal 阶段 fail-fast 提示 _list 未注册(需显式注解)。
# 递归语句块子节点
if node.children is not None:
ch_s: list[ast.AST | t.CPtr] | t.CPtr = node.children
+66 -129
View File
@@ -13,15 +13,11 @@ import lib.core.Gen.GenTypes as GenTypes
# ============================================================
# TranslatorGenMixin - 翻译驱动 Mixin(对象化 P6-E)
# TranslatorGenMixin - 翻译驱动 Mixin
#
# 从 HandlesMain.vp / HandlesTranslator.vp 迁移:模块级纯函数
# (trans 首参)→ 类方法。包含:
# - 模块级翻译入口(translate_children/create_wrapper_main/
# handle_module_level_var 等,原 HandlesMain.vp)
# - global/nonlocal 名称管理辅助函数(原 HandlesTranslator.vp)
#
# 常量(MAX_VARS/MAX_FUNCS/MAX_GLOBAL_NAMES)与原模块保持一致。
# 职责:模块级翻译入口(translate_children/create_wrapper_main/
# handle_module_level_var 等)与 global/nonlocal 名称管理辅助。
# 常量 MAX_VARS/MAX_FUNCS/MAX_GLOBAL_NAMES 与 Handles 侧一致。
# @t.NoVTable:静态绑定(方法沿 MRO 直接调用 + this 调整)。
# ============================================================
@@ -117,10 +113,9 @@ class TranslatorGenMixin:
elif trans._declare_only == 1:
self._register_cexport_from_funcdef(trans, child)
# 预解析函数签名类型,触发泛型类特化(如 numpy 的 _empty_like
# 签名 ndarray[t.CDouble] → 特化结构体注册)。
# 1a 阶段在 mark1 之前,特化结构体的 Ty/Fields 分配在 mark1 前,
# 不被 1b 的 mark/reset 回收 → 避免 Phase2 翻译 App 文件时 UAF
# (Phase1 1b 才触发特化时,结构体 Ty/Fields 在 pool 被 reset 覆写)。
# 签名 ndarray[t.CDouble] → 特化结构体注册)。1a 阶段在 mark1 之前,
# 特化结构体的 Ty/Fields 分配在 mark1 前,不被 1b 的 mark/reset 回收,
# 避免 Phase2 翻译 App 文件时读悬空结构体(UAF)。
# 仅触发特化,不验证结果(1b 全量翻译时仍会重新解析)。
self._prescan_funcdef_signatures(trans, child)
elif kd == ast.ASTKind.ClassDef:
@@ -230,9 +225,7 @@ class TranslatorGenMixin:
return None
elts_count: sizet = len(elts)
# 元素数超过声明长度 → fail-fast(此前在下方循环中按声明长度截断、静默丢弃
# 多余元素:apic._trampoline_bin 声明 [128 x i8] 却给出 138 个字节 → 末尾
# mov rax,[0x7F28]; jmp rax 被丢弃,AP trampoline 失效)。
# 元素数超过声明长度 → fail-fast(不按声明长度截断、静默丢弃多余元素)。
if sizet(arr_count) < elts_count:
self.Gen.TypeGenErr.fatal_error(
list_node,
@@ -240,18 +233,13 @@ class TranslatorGenMixin:
"OverflowError")
return None
# 非整数元素类型(struct/指针等聚合):不存在 "%s %lld" 文本展开。
# 仅当列表全零(元素缺失按零填充)时返回整体 "zeroinitializer"——
# 对任意聚合类型合法(devfs.directories: t.CArray[devfs_directory, N]
# = [0] 此前生成 [i8 0, ...] 与 [N x %devfs_directory] 类型不匹配,
# llc "constant expression type mismatch" 根因)。含非零/非整数常量
# 无法文本表达 → None → 调用方 fail-fast。
# 非整数元素类型(struct/指针等聚合):不存在 "%s %lld" 文本展开,仅当列表
# 全零(元素缺失按零填充)时返回整体 "zeroinitializer"——对任意聚合类型合法
# (否则会生成与声明类型不匹配的 [i8 0, ...] → llc type mismatch)。
# 含非零/非整数常量无法文本表达 → None → 调用方 fail-fast。
if is_int_elem == 0:
# 嵌套数组元素(t.CArray[t.CArray[uint8, 16], 256] = [{...}, ...]):
# elem_ty 是 Array → 递归构建子数组初始化器文本。内层类型省略
# (由外层全局类型推断),llc 合法。vga.vp font 8x16 点阵字库根因:
# 此前只支持 Int 元素,嵌套数组走 zeroinitializer-only 检查,花括号
# 行(ast.Set)含非零常量 → None → "初始化器构建失败" fail-fast。
# 嵌套数组元素(如 t.CArray[t.CArray[uint8, 16], 256]):elem_ty 是 Array →
# 递归构建子数组初始化器文本,内层类型省略(由外层全局类型推断),llc 合法。
is_nested_arr: int = 0
match elem_ty:
case llvmlite.LLVMType.Array(_, _):
@@ -354,17 +342,12 @@ class TranslatorGenMixin:
if ek == ast.ASTKind.Constant:
ec: ast.Constant | t.CPtr = (ast.Constant | t.CPtr)(elem_node)
# CONST_CHAR 字符字面量(如 `_ps2_set1 = ['\x1b', '1', ...]`):
# int_val 即字符码,此前只认 CONST_INT → 字符元素被跳过全编成
# 0(128 字节 PS/2 扫描码表全 0 → 键盘输入失效根因)。
# int_val 即字符码(不识别则字符元素全编成 0)。
if ec is not None and (ec.const_kind == ast.CONST_INT or ec.const_kind == ast.CONST_CHAR):
elem_val = ec.int_val
# CONST_STR 单字符(双引号如 "'" / '"'):parser 把双引号
# 一律判为 CONST_STR(见 ast/parser.vp 字符串常量判定),
# 此前只认 CONST_INT/CONST_CHAR → 双引号单字符元素被编成 0
# (keyboard.vp scancode_set1[40]='\'' 、scancode_set1_shift[40]
# ='"' 丢失根因;A2 的修复只覆盖了单引号字符)。
# strlen == 1 时取首字符码;多字符字符串无法表达为整型元素
# → 保持 0(由下方按声明长度补零)。
# CONST_STR 单字符(双引号如 "'" / '"'):parser 把双引号一律判为
# CONST_STR,strlen == 1 时取首字符码;多字符字符串无法表达为整型
# 元素 → 保持 0(由下方按声明长度补零)。
elif ec is not None and ec.const_kind == ast.CONST_STR:
if ec.str_val is not None and string.strlen(ec.str_val) == 1:
elem_val = int64(ec.str_val[0])
@@ -382,15 +365,10 @@ class TranslatorGenMixin:
viperlib.snprintf(buf + pos, buf_size - pos, "]")
return buf
# ============================================================
# _build_string_array_initializer_text - 字符串字面量初始化数组全局
#
# t.CArray[char, N] = "..." → [i8 b0, i8 b1, ..., i8 0, ...]
# (字符串字节 + 零填充到数组长度;空串返回 zeroinitializer)。
# 此前统一走字符串指针 GEP 初始化器 → @g = global [N x i8] <ptr>
# 类型不匹配(llc constant expression type mismatch,
# pyzlib_error_msg = global [512 x i8] <GEP ptr> 根因)。
# ============================================================
# t.CArray[char, N] = "..." → [i8 b0, i8 b1, ..., i8 0, ...](字符串字节 +
# 零填充到数组长度;空串返回 zeroinitializer)。
# 不能用字符串指针 GEP 初始化器:会导致 @g = global [N x i8] <ptr> 类型不匹配。
def _build_string_array_initializer_text(self, pool: rowptr[memhub.MemBuddy],
var_ty: llvmlite.LLVMType | t.CPtr,
str_val: str) -> str:
@@ -528,16 +506,11 @@ class TranslatorGenMixin:
viperlib.snprintf(buf, 48, "%s %lld", ty_str, dv_int)
return buf
# ============================================================
# _build_stg_struct_body_text - storage struct 字段元素文本(不含大括号)
#
# 生成 "elem0, elem1, ..." 形式(含字段默认值折叠,缺失处按类型零值兜底),
# 供两处复用:
# 生成 "elem0, elem1, ..."(含字段默认值折叠,缺失处按类型零值兜底),供两处复用:
# - _build_stg_struct_init_text:外包 "{ ... }" 作为完整初始化器;
# - vtable 对象 storage:首元素为 vtable 指针,其后追加本函数结果
# (此时 start_idx=1 跳过字段 0)。
# - vtable 对象 storage:首元素为 vtable 指针,其后追加本函数结果(start_idx=1 跳过字段 0)。
# 无任何非零默认值时返回 None,调用方据此保持原零值路径(避免无谓文本)。
# ============================================================
def _build_stg_struct_body_text(self, pool: rowptr[memhub.MemBuddy],
se: GenTypes.StructEntry | t.CPtr,
start_idx: int) -> str:
@@ -584,15 +557,10 @@ class TranslatorGenMixin:
return None
return buf
# ============================================================
# _build_stg_struct_init_text - storage struct 完整常量初始化器(含字段默认值)
#
# 用于模块级对象全局 `X: T = T()`:此前 storage 一律 zeroinitializer,
# `__before_init__`/字段默认(如 fat32.volinfo 的 sector_size=512、
# root_cluster=2、free_clusters=0xFFFFFFFF、backup_sector=6)全部丢失。
# 现按字段默认值生成完整 struct 常量(LLVM 要求列全每个字段)。
# 用于模块级对象全局 `X: T = T()`:按字段默认值生成完整 struct 常量
# (LLVM 要求列全每个字段);不生成则字段默认值全丢。
# 无任何非零默认值时返回 None,由调用方保持 zeroinitializer(避免无谓文本)。
# ============================================================
def _build_stg_struct_init_text(self, pool: rowptr[memhub.MemBuddy],
se: GenTypes.StructEntry | t.CPtr,
start_idx: int) -> str:
@@ -635,13 +603,9 @@ class TranslatorGenMixin:
pool, nm_cd.id, aa_cd.value)
return 0
# CTypedef 注解(如 posix.vp mode_t: t.CTypedef = uint32):
# 不创建全局变量——否则生成 @mode_t 符号 → 链接 duplicate
# symbol warning。预注册阶段(forward_declare_functions
# 之前)按需注册到 CTypedef 表:翻译期表已被 clear_ctypedefs
# 清空(每文件隔离),若不在此时注册,S_ISREG(mode: mode_t)
# 等函数参数注解在 FuncGenDef 正式翻译时查表失败 →
# NameError: mode_t(AstTest/posix.vp 根因;模块级 AnnAssign
# 经本函数分派,不经过 AnnAssignHandle.Handle 的注册分支)。
# 不创建全局变量(否则生成 @mode_t 符号 → 链接 duplicate symbol)。
# 预注册阶段按需注册到 CTypedef 表:翻译期表已被 clear_ctypedefs 清空
# (每文件隔离),不注册则函数参数注解查表失败 → NameError。
if self.Gen.TypeGenCType._is_ctypedef_annotation(aa_cd.annotation) != 0:
ctd_str: str = self.Gen.TypeGenResolve._get_type_name_from_annotation(
pool, aa_cd.value)
@@ -665,9 +629,8 @@ class TranslatorGenMixin:
init_list_node: ast.List | t.CPtr = None
# = None 显式初始化(如 tokens.vp `_kw_head: KwEntry | t.CPtr = None`):
# 对象类全局的懒初始化指针语义——必须保持 NULL,不能创建 BSS storage
# (storage 会让 @g = &g.stg 恒非 NULL,破坏 `if _kw_head != None` 懒初始化
# 守卫 → _init_keywords 永不执行 → _kw_lookup 遍历零存储 strcmp(NULL) 崩溃,
# 二代自举 1.log ASan 根因;_op_head/_kw_pool 同款模式一并修复)。
# (storage 会让 @g = &g.stg 恒非 NULL,破坏 `if _kw_head != None` 守卫,
# 懒初始化代码永不执行)。
is_none_init: int = 0
annot_node: ast.AST | t.CPtr = None # 类型注解节点(检测 t.CExport 标记)
@@ -685,28 +648,23 @@ class TranslatorGenMixin:
resolved: llvmlite.LLVMType | t.CPtr = self.Gen.TypeGenResolve.resolve_annotation_type(
pool, aa.annotation, trans._imported_modules, trans._from_imports, trans, 0)
if resolved is not None:
# 对象类模块级变量(@t.Object/@t.CVTable:对象即指针)→ Ptr(struct)。
# 裸类名注解 resolve 返回 struct 值(参数层 FuncGenDef 会包装 Ptr,
# 模块级变量此处缺包装)→ 若建成值实例,跨模块字段访问/虚表调用
# 按指针处理读首 8 字节当 vtable → 垃圾地址 #PF(devfs.vp
# serial0_file.open = serial0_open 根因)。纯 t.CStruct(无方法
# 无虚表无 __new__,如 pci_device)保持值实例。
# 对象类模块级变量(@t.Object/@t.CVTable:对象即指针)→ Ptr(struct):
# 裸类名注解 resolve 返回 struct 值,模块级变量此处需包装 Ptr;若建成
# 值实例,跨模块字段访问/虚表调用会按指针处理读首 8 字节当 vtable → 崩溃。
# 纯 t.CStruct(无方法无虚表无 __new__)保持值实例。
if resolved.kind() == llvmlite.LLVMType.Struct:
gv_entry: GenTypes.StructEntry | t.CPtr = self.Gen.StructGen.find_struct_by_type(resolved)
if gv_entry is not None \
and (gv_entry.HasNew != 0 or gv_entry.HasVTable != 0 or gv_entry.IsOOP != 0):
# 对象类全局(裸类名注解 → 对象即指针):记录原 Struct 值类型,
# 用于创建静态 storage 全局承载对象实例(BSS)。
# 否则 @g = global T* zeroinitializer → NULL → 对象字段落在
# 物理页 0(IVT+BDA 混叠),devfs/fat32/sched 全部对象失效根因。
# 用于创建静态 storage 全局承载对象实例(BSS);否则 @g 为 NULL,
# 对象字段落在物理页 0。
resolved_stg_ty = resolved
resolved_stg_entry = gv_entry
resolved = llvmlite.Ptr(pool, resolved)
elif resolved.kind() == llvmlite.LLVMType.Ptr:
# 对象类联合注解(T | t.CPtr → T*,如 Gargantua `_v3_pool:
# memhub.MemPool | t.CPtr`、`CAM: V3 | t.CPtr`):同样创建 storage。
# 否则 @g = global T* null → 入口/方法虚调用 load @g 得 NULL →
# 读 [NULL] 拿 vtable → #PF cr2=0x0(Gargantua/Terminal 启动即崩根因)。
# 对象类联合注解(T | t.CPtr → T*):同样创建 storage;否则入口/方法
# 虚调用 load @g 得 NULL → 读 [NULL] 拿 vtable → #PF。
# var_ty 保持 Ptr(联合注解语义:指针全局),storage 承载对象实例。
pointee_uv: llvmlite.LLVMType | t.CPtr = resolved.Pointee
if pointee_uv is not None and pointee_uv.kind() == llvmlite.LLVMType.Struct:
@@ -718,8 +676,8 @@ class TranslatorGenMixin:
var_ty = resolved
else:
# [S4 fail-fast] 模块级变量有注解但解析失败 → 不再静默用 i32
# (类型错 → 全局存储尺寸/字段访问/ABI 全错,静默错到运行期)。
# 若此处因「前向引用未注册类」触发,应收敛为多遍注册而非回退 i32。
# (类型错会使全局存储尺寸/字段访问/ABI 全错);前向引用未注册类
# 应收敛为多遍注册而非回退 i32。
self.Gen.TypeGenErr.fatal_error(
aa.annotation, f"模块级变量 '{var_name}' 类型注解解析失败", "NameError")
return 0
@@ -737,17 +695,13 @@ class TranslatorGenMixin:
init_str_val = cn.str_val
init_kind = 3
elif cn.const_kind == ast.CONST_FLOAT:
# 浮点字面量(如 torch `LCG_M: t.CDouble = 2147483647.0`):
# 此前无此分支 → init_kind 保持 0 → 全局退化成
# zeroinitializer,初始化值整体丢失(负数 -1 补在 UnaryOp
# 分支是同一类漏项)。数值不在此处取 AST 的 float_val,
# 见下方 init_kind==4 的源文件恢复逻辑。
# 浮点字面量(如 torch `LCG_M: t.CDouble = 2147483647.0`):必须设
# init_kind,否则全局退化成 zeroinitializer、初始化值整体丢失。
# 数值不在此处取 AST 的 float_val,见下方 init_kind==4 的源文件恢复逻辑。
init_kind = 4
elif val_kind == ast.ASTKind.UnaryOp:
# 负数/按位取反字面量(如 `_vblank_ok: int = -1`):
# AST 是 UnaryOp(USub, Constant),此前未处理 → init_kind 保持 0
# → 全局编译成 zeroinitializer(desktop 7 个 -1 初始化全部丢失
# → _drag_idx=0 无条件拖拽窗口 0 / _focused_win=0 事件错路由根因)。
# 负数/按位取反字面量(如 `_vblank_ok: int = -1`):AST 是
# UnaryOp(USub, Constant),不处理则全局编成 zeroinitializer、初始化值丢失。
init_val = self.Gen.AssignGen.extract_cdefine_int_value(aa.value)
init_kind = 1
elif val_kind == ast.ASTKind.List:
@@ -790,24 +744,20 @@ class TranslatorGenMixin:
init_list_node = (ast.List | t.CPtr)(asgn.value)
init_kind = 2
elif val_kind2 == ast.ASTKind.Attribute:
# 模块级 re-export 别名(如 fat32_core.vp `_read32 = types._read32`、
# fat32_dir.vp/fat32_file.vp 的同型 shim):RHS 是函数/实体引用,
# 无值语义可落入 LLVM 全局。此前未识别 → var_ty 默认 i32 →
# 生成假符号 `global i32 zeroinitializer`(被引用即调用地址 0)。
# 与 __all__ 同类:别名/元数据实体不产出 LLVM 全局(详见 L693)。
# 模块级 re-export 别名(如 fat32_core.vp `_read32 = types._read32`):
# RHS 是函数/实体引用,无值语义可落入 LLVM 全局。不识别则 var_ty 默认 i32
# → 生成假符号 `global i32 zeroinitializer`(被引用即调用地址 0)。
# 与 __all__ 同类:别名/元数据实体不产出 LLVM 全局。
# 真正的函数别名机制(可调用 + 可 re-export)属独立特性,待后续实现。
return 0
if var_name is None:
return 0
# __all__ 是 Python 模块元数据(公开名列表),无运行时语义:
# StubGen 的 .vpi 不为其生成 stub 声明(无注解模块级赋值不产出 vpi
# 条目),此处同样跳过全局创建——否则无注解 list-of-strings 全局
# 类型推断缺失(var_ty 默认 i32)→ _build_array_initializer_text
# 返回 None → fail-fast "初始化器构建失败" 误报
# (Projectrans/__init__.vp 追加83 根因)。与 CDefine/CTypedef
# 同类:编译期/元数据实体不落入 LLVM 全局。
# __all__ 是 Python 模块元数据(公开名列表),无运行时语义:StubGen 的 .vpi 不为其
# 生成 stub 声明,此处同样跳过全局创建——否则无注解 list-of-strings 全局类型推断
# 缺失(var_ty 默认 i32)→ _build_array_initializer_text 返回 None → 误报
# "初始化器构建失败"。与 CDefine/CTypedef 同类:编译期/元数据实体不落入 LLVM 全局。
if string.strcmp(var_name, "__all__") == 0:
return 0
@@ -897,9 +847,8 @@ class TranslatorGenMixin:
self.Gen.TypeGenErr.fatal_error(node, f"模块级变量 '{var_name}' 初始化器构建失败", "TypeError")
elif init_kind == 3:
# 字符串字面量初始化
# 数组类型(t.CArray[char, N]):字节数组初始化器(字符串内容 + 零填充)
# ——此前统一走字符串指针 GEP 初始化器 → @g = global [N x i8] <ptr> 类型
# 不匹配(llc constant expression type mismatch,pyzlib_error_msg 根因)
# 数组类型(t.CArray[char, N]):字节数组初始化器(字符串内容 + 零填充)。
# 不能用字符串指针 GEP 初始化器(@g = global [N x i8] <ptr> 类型不匹配)。
is_arr_g: int = 0
match var_ty:
case llvmlite.LLVMType.Array(_, _):
@@ -924,12 +873,9 @@ class TranslatorGenMixin:
self.Gen.TypeGenErr.fatal_error(
node, f"模块级变量 '{var_name}' 初始化器构建失败", "TypeError")
elif init_kind == 4:
# 浮点初始值:LLVM IR 的浮点常量必须写成位模式文本(0x%016llx / 0x%08x)。
# 浮点初始值:LLVM IR 的浮点常量必须写成位模式文本(0x%016llx / 0x%08x)——
# 十进制文本经 llc 解析对部分值有 ±1 ulp 误差(见 llvmlite.ConstFloat 注释)。
# 此前 init_kind 没有浮点分支 → 模块级 t.CDouble 全局一律退化成
# zeroinitializer(torch 的 LCG_A/LCG_M/_rng_state 全变 0 →
# _next_rand() 得到 0.0/0.0 = NaN → Kaiming 初始化写入全 NaN 权重 →
# 推理 softmax 分母 NaN → 十个概率全为 0 的根因链)。
# 浮点分支缺失时模块级 t.CDouble 全局会退化成 zeroinitializer(值全变 0)。
fc_g: llvmlite.Value | t.CPtr = llvmlite.ConstFloat(pool, var_ty, init_fval)
if fc_g is not None and fc_g.Name is not None:
gv.Initializer = fc_g.Name
@@ -955,9 +901,8 @@ class TranslatorGenMixin:
# 值类型 storage 需要完整 struct 定义(跨模块类默认 opaque →
# @g.stg = global %T zeroinitializer 报 "use of undefined type")
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, resolved_stg_ty)
# @t.CVTable 对象类:storage 首字段(vtable 指针,偏移 0)初始化为 vtable 地址。
# 否则 zeroinitializer → vtable=0 → 模块级全局对象(如用户程序 vpsdk 对象)
# 入口虚调用 call [vtable+0x10] → 跳 0x0 #PF(HelloWorld 启动即崩根因)。
# @t.CVTable 对象类:storage 首字段(vtable 指针,偏移 0)初始化为 vtable 地址,
# 否则 zeroinitializer → 入口虚调用 call [vtable+0x10] → 跳 0x0 #PF。
# @t.Object(无 CVTable)/普通对象:保持 zeroinitializer(无 vtable 需求)。
stg_init_text: str = "zeroinitializer"
if resolved_stg_entry is not None and resolved_stg_entry.Name is not None \
@@ -965,15 +910,10 @@ class TranslatorGenMixin:
vt_name_buf: str = pool.alloc(128)
if vt_name_buf is not None:
viperlib.snprintf(vt_name_buf, 128, "%s.vtable", resolved_stg_entry.Name)
# [FIX 2026-08-31] vtable 符号前缀必须用类定义模块的 sha1
# (对齐 CallGenLeafKit._make_vtable_ref_i8)。此前用当前模块
# _mangle_name → 跨模块对象类全局(如 Translator 模块的
# _mbuddy: memhub.MemBuddy)storage 初始化引用
# "@当前sha1.MemBuddy.vtable",而 vtable 实为定义模块
# 的 "@7e4672b992208969.MemBuddy.vtable" → llc 报
# "use of undefined value"(eba110fc7acac3e6.ll:7999 根因)。
# resolved_stg_entry.ModuleSha1 即定义模块,比 get_struct_sha1
# 裸名反查更精确(避免跨模块同名类歧义)。
# vtable 符号前缀必须用类定义模块的 sha1(对齐 CallGenLeafKit._make_vtable_ref_i8):
# 用当前模块 _mangle_name 会使跨模块对象类全局 storage 初始化引用
# "@当前sha1.Class.vtable",而 vtable 实为定义模块的符号 → llc "use of
# undefined value"。resolved_stg_entry.ModuleSha1 即定义模块,比裸名反查精确。
vt_sha1: str = None
if resolved_stg_entry.ModuleSha1 is not None:
vt_sha1 = resolved_stg_entry.ModuleSha1
@@ -1022,12 +962,9 @@ class TranslatorGenMixin:
viperlib.snprintf(init_full + pos_v, init_full_len - pos_v, " }")
stg_init_text = init_full
elif resolved_stg_entry is not None:
# 无 vtable 的对象全局(@t.Object / 普通类):按字段默认值生成常量
# 初始化器(如 fat32.volinfo:sector_size=512、root_cluster=2、
# free_clusters=0xFFFFFFFF、backup_sector=6、winsect/fat_winsect=-1…)。
# 此前一律 zeroinitializer → 字段默认全部丢失(模块级 X: T = T() 的
# 构造调用不发射,见第十三节)。无任何非零默认值时返回 None →
# 保持 zeroinitializer(drive_lock 等零值等价场景零开销)。
# 无 vtable 的对象全局(@t.Object / 普通类):按字段默认值生成常量初始化器;
# 一律 zeroinitializer 会丢掉全部字段默认值(模块级 X: T = T() 的构造调用
# 不发射)。无任何非零默认值时返回 None → 保持 zeroinitializer(零开销)。
stg_default_text: str = self._build_stg_struct_init_text(
pool, resolved_stg_entry, 0)
if stg_default_text is not None:
+9 -13
View File
@@ -22,10 +22,9 @@ import lib.core.Gen.Type.TypeSpec as TSpec
# ============================================================
# TypeGenBase - TypeGenMixin 拆分(阶段 D):字段 + InitTypeGen + trace/临时目录/generic 上下文
# TypeGenBase - 类型表基础设施:字段 + InitTypeGen + trace/临时目录/generic 上下文
#
# 继承 TypeGenBase(1 级,TPV self 调用仅支持 1 级父类)。
# 跨 Mixin 调用统一走 HandlesType 委托(经 _g_gen)。
# 继承链仅 1 级(TPV self 调用仅支持 1 级父类);跨 Mixin 调用统一走 HandlesType 委托。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
@@ -64,9 +63,8 @@ class TypeGenBase:
CDefineNameIdxBuilt: int
# --- 跨模块 CDefine 查找缓存(perm_pool 永久分配,跨文件复用) ---
# 2026-08-14:跨模块 CDefine 查找(_lookup_cross_module_cdefine)结果缓存到
# perm_pool,reset_to(mark2)/clear_cdefine_constants 不清除 → 跨文件复用,
# CONST_INT 递归查找只做一次(NewAstTest OOM 根因)。
# 缓存 reset_to(mark2)/clear_cdefine_constants 不清除 → 跨文件复用,
# CONST_INT 递归查找只做一次。
XmodCdefNames: list[str] | t.CPtr
XmodCdefValues: list[str] | t.CPtr
XmodCdefDoubleFlags: list[int] | t.CPtr
@@ -103,10 +101,9 @@ class TypeGenBase:
LastBuiltinsTypedefNameBuf: bytes
LastBuiltinsTypedefResultBuf: bytes
# builtins import 别名缓冲(stdlib.malloc,格式 "alias\0target\0" 连续段):
# _lookup_builtins_typedef 扫描 builtins.vp import 行填充;库 typedef 模块
# 限定引用按别名还原真实模块名(__stdint.size_t → stdint.size_t)。
# 用 malloc 而非 pool list——pool 每文件 reset_to 后 list 悬空(clear_ctypedefs
# 未覆盖的路径读到已释放内存 → strcmp 崩溃,1.log 根因);malloc 跨 pool
# _lookup_builtins_typedef 扫描 builtins.vp import 行填充;库 typedef 模块限定
# 引用按别名还原真实模块名(__stdint.size_t → stdint.size_t)。
# 用 malloc 而非 pool list——pool 每文件 reset_to 后 list 悬空;malloc 跨 pool
# 生命周期存活,clear_ctypedefs 释放置空。
ImportAliasBuf: bytes
@@ -114,9 +111,8 @@ class TypeGenBase:
# 库模块 typedef 缓存(PermPool 永久分配,跨文件/Phase 存活):
# LibTypedefScannedMods: key=库模块名 → 已扫描标记(命中=该模块源已读并入缓存);
# LibTypedefTypeMap: key="模块名|typedef 名" → 源中原始类型表达式。
# 原实现每次查询都读 64KB 库源全文扫描(每个 Name 注解兜底连试 stdint/
# w32.win32base 两次)→ 单文件 translate 7–12s 根因。缓存按模块名(内容)
# 索引,库源单次编译内不变,故命中结果与逐次读源等价。| 不用全局裸表变量。
# 每次查询都读 64KB 库源全文扫描会使单文件 translate 耗时 7–12s;库源单次编译内
# 不变,故缓存命中结果与逐次读源等价。
LibTypedefScannedMods: hashtable.HashTable | t.CPtr
LibTypedefTypeMap: hashtable.HashTable | t.CPtr
+3 -5
View File
@@ -244,11 +244,9 @@ class TypeGenCDef(TypeGenBase.TypeGenBase):
if self.CDefineStrFlags is not None:
self.CDefineStrFlags.append(1)
# 同步设置"最近注册/查表结果为字符串"标志(is_cdefine_str() 读取)。
# 跨模块字符串 CDefine 读回(_lookup_cross_module_cdefine 字符串分支只调
# 本函数 + 尾部 set_cdefine_found,无 lookup_cdefine_string 回查)→ 若此处
# 不设标志,属性路径 is_cdefine_str()==0 → 退化为整数分支返回 0 →
# IR 生成 inttoptr i32 0(NULL)→ 运行时报 (null-) + strlen(NULL-) 崩溃
# (test_pyzlib.vp pyzlib.ZLIB_VERSION 根因)。
# 跨模块字符串 CDefine 读回只调本函数 + 尾部 set_cdefine_found;不设标志会让
# 属性路径 is_cdefine_str()==0 → 退化为整数分支返回 0 → IR 生成 inttoptr i32 0
# → 运行期按 NULL 字符串处理崩溃。
self.CDefineIsStr = 1
self.CDefineFound = 1
# ============================================================
+15 -22
View File
@@ -44,10 +44,9 @@ def _ctypedef_idx_hash(name: str) -> uint64:
# ============================================================
# TypeGenCType - TypeGenMixin 拆分(阶段 D):CTypedef 表(register/lookup/ScanCTypedefDeclarations/_eval_const_int_expr)
# TypeGenCType - CTypedef 表(register/lookup/ScanCTypedefDeclarations/_eval_const_int_expr)
#
# 继承 TypeGenBase(1 级,TPV self 调用仅支持 1 级父类)。
# 跨 Mixin 调用统一走 HandlesType 委托(经 _g_gen)。
# 继承链仅 1 级(TPV self 调用仅支持 1 级父类);跨 Mixin 调用统一走 HandlesType 委托。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
@@ -182,10 +181,9 @@ class TypeGenCType(TypeGenBase.TypeGenBase):
"""
if text is None:
return None
# [D4·截断纪律] 旧实现固定 TYPEDEF_DIAG_BUF(256) + `strncpy(buf, text, 255)`:
# 诊断串超长被**静默截断** ⇒ 报错文本丢掉关键名字("变量类型注解解析失败"
# 的归因信息失效)。改为按需 realloc 到**精确长度**,缓冲仍为自有字段
# (生命周期不变:仍在 reset/析构时统一 stdlib.free)。
# 诊断串按需 realloc 到精确长度:固定 TYPEDEF_DIAG_BUF(256) + strncpy(buf, text, 255)
# 会静默截断超长诊断串 ⇒ 报错文本丢掉关键名字(归因信息失效)。
# 缓冲仍为自有字段(生命周期不变:仍在 reset/析构时统一 stdlib.free)。
need: sizet = string.strlen(text) + 1
if which == 0:
if self.LastBuiltinsTypedefNameBuf is None:
@@ -275,15 +273,12 @@ class TypeGenCType(TypeGenBase.TypeGenBase):
self.CTypedefFound = 1
return ts
return None
# ============================================================
# 符号性真源(**S3 起无符号判定的读入口**;S2 阶段曾用作双写比对基准)
# 符号性真源(无符号判定的读入口)
#
# 旧实现(现降级为「真源不表态时的兜底」):strstr 子串扫描 + CTypedef 只展开一层
# ⇒ 两类错判:A4 `BYTEPTR` 含 `BYTE` 被判无符号;A5 sha1 前缀名含 `uint32` 命中;
# 多层别名(WCHAR→WORD→uint16)因只展开一层而漏判。
# 本实现:精确名白名单(TSpec.SpecSignFromTypeNameSpan)+ 别名链逐层展开,
# 不表态时返回 TS_SIGN_UNKNOWN(由调用方决定是否回退旧路径)。
# ============================================================
# 实现:精确名白名单(TSpec.SpecSignFromTypeNameSpan)+ 别名链逐层展开;
# 不表态时返回 TS_SIGN_UNKNOWN(由调用方决定是否回退旧路径)。
# 不用 strstr 子串扫描 + 单层展开:会误判 BYTEPTR(含 BYTE)、sha1 前缀名(含
# uint32),且多层别名(WCHAR→WORD→uint16)漏判。
def spec_sign_of_annot(self, text: str, depth: int = 0) -> int:
"""按注解文本判定其整数值符号性(独立真源),未覆盖返回 TS_SIGN_UNKNOWN"""
if text is None or depth > 16:
@@ -459,16 +454,14 @@ class TypeGenCType(TypeGenBase.TypeGenBase):
if k_v != ast.ASTKind.Attribute:
return 0
at: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(annotation)
# 字段访问改用 ast 模块访问器(跨模块 stub 字段访问陷阱防护:
# TypeGenCType 直接访问 at.attr 可能按 stub 类型生成错误 GEP,
# 读垃圾地址 → strcmp 0xC0000005 崩溃,stdint.vp 根因)。
# 字段访问改用 ast 模块访问器(直接访问 at.attr 可能按 stub 类型生成错误 GEP,
# 读垃圾地址 → strcmp 崩溃)。
at_attr_v: str = ast.attribute_get_attr(at)
if at_attr_v is None:
return 0
# 限长比较替代 strcmp:attr 是零拷贝引用 src_buf 的 token 字符串,
# 可能无 NUL 结尾("CTypedef" 后紧跟空格/其他字符),strcmp 会
# 无限读穿到页边界 → 0xC0000005(stdint.vp 崩溃根因)。strncmp
# 只读 8 字节,再校验第 9 字节为终止/分隔符(防 "CTypedefXXX" 误匹配)。
# 限长比较替代 strcmp:attr 是零拷贝引用 src_buf 的 token 字符串,可能无 NUL
# 结尾("CTypedef" 后紧跟空格/其他字符),strcmp 会读穿到页边界 → 崩溃。
# strncmp 只读 8 字节,再校验第 9 字节为终止/分隔符(防 "CTypedefXXX" 误匹配)。
if string.strncmp(at_attr_v, "CTypedef", 8) != 0:
return 0
sep_ch_d: char = at_attr_v[8]
+10 -18
View File
@@ -21,10 +21,9 @@ import lib.core.Gen.Type.TypeSpec as TSpec
# ============================================================
# TypeGenErr - TypeGenMixin 拆分(阶段 D):错误上报/源行读取/类型推断(fatal_error/infer_* 等)
# TypeGenErr - 错误上报/源行读取/类型推断(fatal_error/infer_* 等)
#
# 继承 TypeGenBase(1 级,TPV self 调用仅支持 1 级父类)。
# 跨 Mixin 调用统一走 HandlesType 委托(经 _g_gen)。
# 继承链仅 1 级(TPV self 调用仅支持 1 级父类);跨 Mixin 调用统一走 HandlesType 委托。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
@@ -35,7 +34,7 @@ class TypeGenErr(TypeGenBase.TypeGenBase):
"""从源文件读取第 lineno 行内容"""
if file_path is None or lineno <= 0:
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
# 按文件实际大小分配缓冲区:固定 256KB 会截断大源文件,末尾行号读不到 →
@@ -254,13 +253,9 @@ class TypeGenErr(TypeGenBase.TypeGenBase):
if stmt is not None and stmt.kind() == ast.ASTKind.Pass:
return 1
return 0
# ============================================================
# 获取 LLVM 整数类型的位宽
#
# [S4 切读·位宽块] 实现已收拢到 `TSpec.SpecTypeBits`(该块在 TypeSpec 模块中的
# **唯一实现**),此处只做委托,不再保留同码副本(ExprGenBase.get_llvm_type_bits
# 同样委托到它)。私有调用点(self.get_llvm_type_bits)保持原样不受影响。
# ============================================================
# 实现已收拢到 TSpec.SpecTypeBits(该块在 TypeSpec 模块中的唯一实现),此处只做委托。
# 私有调用点(self.get_llvm_type_bits)保持原样不受影响。
def get_llvm_type_bits(self, ty: llvmlite.LLVMType | t.CPtr) -> int:
"""获取 LLVM 整数类型的位宽,非整数类型返回 0(委托 SpecTypeBits)"""
return TSpec.SpecTypeBits(ty)
@@ -283,9 +278,8 @@ class TypeGenErr(TypeGenBase.TypeGenBase):
nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(node)
if nm.id is not None and param_types is not None:
# 在 param_types 中查找: "name:bits" 格式
# [D5·截断纪律] 旧实现固定 64 缓冲 + snprintf("%s:"):参数名超长被
# 静默截断 ⇒ 截断后的键在 param_types 里与**别的参数前缀**匹配成功
# ⇒ 取到错误的位宽(类型误判)。改为按精确长度分配。
# 键按精确长度分配:固定 64 缓冲 + snprintf("%s:") 会静默截断超长参数名,
# 截断后的键可能与**别的参数前缀**匹配成功 ⇒ 取到错误的位宽(类型误判)。
id_len: sizet = string.strlen(nm.id)
search_key: str = pool.alloc(id_len + 2)
if search_key is not None:
@@ -406,12 +400,10 @@ class TypeGenErr(TypeGenBase.TypeGenBase):
if an == 0:
return None
# 1) 上界估长(**只扫参数名**,不解析类型也不需要暂存结果指针):
# 1) 上界估长(**只扫参数名**,不解析类型,也不暂存结果指针):
# 每条 = name + ':' + 位宽(int32 十进制最长 11 字符含符号)+ ',';再加 NUL。
# ⚠ 这里必须"上界"而非"精确":此前版本把每条目指针存进 sizet 数组、
# 再取出转回指针 strcat,自举出的二进制上取回了 -1(0xffffffffffffffff)
# ⇒ ASan: access-violation in strcat(build_param_types_str)。
# 改为上界缓冲后**不需要数组往返**,每条目就地格式化、立即拼接。
# 必须"上界"而非"精确":把条目指针存进 sizet 数组再取出转回指针 strcat 会出错;
# 上界缓冲不需要数组往返,每条目就地格式化、立即拼接。
bound: sizet = 1 # 末尾 NUL
pre_i: sizet
for pre_i in range(an):
+2 -4
View File
@@ -16,11 +16,9 @@ import lib.core.Gen.Type.TypeGenBase as TypeGenBase
# ============================================================
# TypeGenMixin - 类型映射与解析 Mixin(对象化 P2,wiki/14-oo-refactor.md)
#
# 500~1500 行/文件架构重构阶段 D:全部方法已拆至
# TypeGenBase/CDef/CType/Resolve/Err,本文件保留类声明(Generator 继承链入口)。
# TypeGenMixin - 类型映射与解析 Mixin(Generator 继承链入口)
#
# 方法已全部拆至 TypeGenBase/CDef/CType/Resolve/Err,本文件保留类声明。
# @t.NoVTable:静态绑定(方法沿 MRO 直接调用 + this 调整,无 vtable 开销)。
# ============================================================
+57 -112
View File
@@ -906,7 +906,7 @@ class TypeGenResolve(TypeGenBase.TypeGenBase):
if b_buf is None:
stdlib.free(b_path)
return None
bf: fileio.File | t.CPtr = fileio.File(b_path, fileio.MODE.R)
bf: rowptr[fileio.File] = fileio.File(b_path, fileio.MODE.R)
stdlib.free(b_path)
if bf.closed:
stdlib.free(b_buf)
@@ -1172,7 +1172,7 @@ class TypeGenResolve(TypeGenBase.TypeGenBase):
if m_buf is None:
stdlib.free(m_path)
return None
mf: fileio.File | t.CPtr = fileio.File(m_path, fileio.MODE.R)
mf: rowptr[fileio.File] = fileio.File(m_path, fileio.MODE.R)
stdlib.free(m_path)
if mf.closed:
stdlib.free(m_buf)
@@ -1748,14 +1748,10 @@ class TypeGenResolve(TypeGenBase.TypeGenBase):
return self.extract_class_name_from_annotation(
sub.slice, imported_modules, pool, from_imports)
return None
# [D25 根因修复] 递归取 base 时必须把 `from_imports` 一并下传:
# 此前这里丢掉了第 4 个参数,导致同一份注解在「派发名(本函数)/
# 查表键(_resolve_generic_subscript_ty)/ 注册键(_specialize_generic_class)」
# 三处算出的 mangled 名可能不同 —— 例如 `list[ast.AST | t.CPtr]` 的
# 元素段在带/不带 from_imports 时规范化结果不一致,进而出现
# 「alloca 是裸 list*、pass1 按 list[ast.ASTptr] 解释、pass2 按
# list[ast.ClassDefptr] 解释」的分裂(MatchGenMixin._handle_renum_match
# 崩溃根因,见 Debugger/_Type.md D25)。其余调用点(1625、1650 等)均已传。
# 递归取 base 时必须把 from_imports 一并下传:否则同一份注解在派发名/查表键/
# 注册键三处算出的 mangled 名不一致(如 list[ast.AST | t.CPtr] 的元素段),
# 出现「alloca 裸 list* / pass1 按 list[ast.ASTptr] / pass2 按 list[ast.ClassDefptr]」
# 的分裂崩溃。其余调用点均已传。
base_name: str = self.extract_class_name_from_annotation(
sub.value, imported_modules, pool, from_imports)
if base_name is None:
@@ -1916,11 +1912,9 @@ class TypeGenResolve(TypeGenBase.TypeGenBase):
# 查找已特化的 struct(特化由实例化调用 list[int](pool) 触发)
gen_spec_ty: llvmlite.LLVMType | t.CPtr = self.Gen.StructGen.get_struct_type(gen_mangled)
if gen_spec_ty is not None:
# 治本修复:Phase B 补译泛型特化类方法体
# Phase 1a(declare_only==1)特化时只注册结构体不译方法体(MethodsEmitted==0),
# Phase B(declare_only==0)解析字段类型命中已注册结构体时本会早返回,
# 导致方法体永远不生成 → GSList[Line].append 等符号只有 declare 无 define → 链接失败。
# 此处 Phase B 且方法体未译时,调用 _specialize_generic_class 走其已注册分支补译方法体。
# Phase B 补译泛型特化类方法体:Phase 1a 特化时只注册结构体不译方法体,
# Phase B 解析字段类型命中已注册结构体时本会早返回 → 方法体永不生成
# → 符号只有 declare 无 define → 链接失败。故这里走补译分支。
if trans is not None and trans._declare_only == 0:
gen_spec_entry: GenTypes.StructEntry | t.CPtr = self.Gen.StructGen.find_struct(gen_mangled)
if gen_spec_entry is not None and gen_spec_entry.MethodsEmitted == 0:
@@ -1948,15 +1942,11 @@ class TypeGenResolve(TypeGenBase.TypeGenBase):
self.Gen.TypeGenBase.pop_trace()
if spec_name_chk is not None:
gen_spec_ty = self.Gen.StructGen.get_struct_type(gen_mangled)
# [D31] gen_mangled 由本函数自行拼接(_get_type_name_from_annotation
# + _norm_type_arg_name),而 _specialize_generic_class 内部还会
# 对每个实参再走一次规范化,两处命名可能不一致:如元素注解
# `ast.AST | t.CPtr` 本函数拼出 "list[<sha1>.AST|t.CPtr]",
# 特化侧规范化为 "list[<sha1>.ASTptr]"(联合折叠为指针名)。
# 此时按 gen_mangled 重查恒 miss → 静默退化为裸 list* →
# 之后 list.get() 误分派(返回 i32)→ inttoptr 截断指针 →
# 读垃圾 AST 节点崩溃(D21/D25 崩溃族根因之一)。
# spec_name_chk 是特化侧返回的**权威特化名**,必须按它重查。
# gen_mangled 由本函数自行拼接,而 _specialize_generic_class 内部还会对
# 每个实参再规范化一次,两处命名可能不一致(如元素注解 `ast.AST | t.CPtr`
# 本函数拼 "list[<sha1>.AST|t.CPtr]",特化侧规范化 "list[<sha1>.ASTptr]")
# → 按 gen_mangled 重查恒 miss → 静默退化为裸 list* → .get() 误分派截断。
# spec_name_chk 是特化侧返回的权威特化名,必须按它重查。
# 注:非联合实参(如 list[int])两侧命名一致,本步恒 miss 不改变行为。
if gen_spec_ty is None:
gen_spec_ty = self.Gen.StructGen.get_struct_type(spec_name_chk)
@@ -1970,14 +1960,10 @@ class TypeGenResolve(TypeGenBase.TypeGenBase):
VLogger.info(f"[GENERIC-DBG] 模板未注册: {gen_cls_name}", "Phase1")
else:
VLogger.info(f"[GENERIC-DBG] trans 为 None: {gen_cls_name}", "Phase1")
# [D32 治本 fail-fast] Phase B(declare_only==0)全量翻译时,**泛型模板类**的
# 下标仍解析不出特化结构体 ⇒ 上游注解必然退化:
# * BinOp(`list[X] | t.CPtr`) 分支会落到 `Ptr(i8)`(丢类型);
# * 其它路径可能退化到裸基类 `list*`。
# 二者后续的 `.get()` 都会按错误元素类型分派(实测 list[int].get 返回 i32)
# → inttoptr 截断指针 → 读垃圾 AST 节点崩溃(D21/D25/D31/D32 崩溃族)。
# 禁止静默退化:报出查表键与实际模板名,直接暴露退化点。
# 仅当基名确为泛型模板时才触发——避免误伤 rowptr/CArray 等 builtin 下标。
# Phase B(declare_only==0)全量翻译时,泛型模板类的下标仍解析不出特化结构体
# ⇒ 上游注解必然退化(BinOp 分支落到 Ptr(i8) 丢类型,或退化到裸基类 list*),
# 后续 .get() 按错误元素类型分派 → inttoptr 截断崩溃。禁止静默退化:报出查表键
# 与实际模板名。仅当基名确为泛型模板时才触发(避免误伤 rowptr/CArray 等 builtin)。
if trans is not None and trans._declare_only == 0 \
and self.Gen.ClassGenGeneric._find_generic_template(gen_cls_name) is not None:
self.Gen.TypeGenErr.fatal_error(
@@ -2051,9 +2037,8 @@ class TypeGenResolve(TypeGenBase.TypeGenBase):
# 直接返回 None 让上层回落(fail-fast:宁可类型退化
# 也不静默卡死)。
if string.strcmp(gp_ta, gp_tpn) == 0:
# 泛型类型参数自映射(T→T):编译期报错,不静默退化。
# 此前 return None 让上层静默退化(i32/Ptr(i8)),
# 运行时数据损坏难定位(ListTest/DictTest 根因之一)。
# 泛型类型参数自映射(T→T):编译期报错,不静默退化
# (静默退化会让运行时数据损坏难定位)。
self.Gen.TypeGenErr.fatal_error(
nm, f"泛型类型实参污染:类型参数 '{gp_tpn}' 自映射(实参='{gp_ta}'),无法特化", "TypeError")
polluted: int = 0
@@ -2144,15 +2129,11 @@ class TypeGenResolve(TypeGenBase.TypeGenBase):
if own_entry.HasNew != 0:
return llvmlite.Ptr(pool, own_entry.Ty)
return own_entry.Ty
# from-import 归属查找:注解里的裸名(或 import 别名)经 from_imports
# 定位到定义模块,再按 (name, sha1) 精确取类。这是 stub 注入场景的
# 正解——此刻 trans.ModuleSha1 是"正在翻译的文件",而注解里的类归属
# **stub 源模块**(from_imports 即该 stub 的名字→模块映射)。
# 反例(D26 根因):ast/__init__.vpi 的
# `parse_expression -> rowptr[Expression]`,Expression 定义在
# ast.stmts;仅靠"当前模块 sha1 + 全局可见性"在 App 严格模式下查不到
# (AsyncGenMixin 并未 import ast.Expression)⇒ 返回类型解析失败
# fail-fast。from_imports 里的 `Expression:ast.stmts` 是精确归属依据。
# from-import 归属查找:注解里的裸名(或 import 别名)经 from_imports 定位到
# 定义模块,再按 (name, sha1) 精确取类——这是 stub 注入场景的正解:此刻
# trans.ModuleSha1 是"正在翻译的文件",而注解里的类归属 stub 源模块
# (from_imports 即该 stub 的名字→模块映射)。仅靠"当前模块 sha1 + 全局可见性"
# 在 App 严格模式下会查不到(如 ast.stmts.Expression 经 ast.__init__.vpi 注入)。
if trans is not None and from_imports is not None:
fi_mod: str = StubMergerStore.LookupModNameByAlias(from_imports, nm.id)
if fi_mod is not None:
@@ -2367,10 +2348,8 @@ class TypeGenResolve(TypeGenBase.TypeGenBase):
if self.Gen.StmtGenDecl.is_enum_class(at.attr) == 1:
return self.Gen.StmtGenDecl.get_enum_base_type(at.attr)
# 模块限定 typedef(posix.DIR = t.CTypedef = t.CVoid|t.CPtr 等):
# 与 Name 分支(L1986-2009)同一套 lookup_ctypedef + 递归解析。
# 追加20只补了嵌套 Attribute(mod.sub.Typedef);单层
# Module.Typedef(value=Name 非 t/c)同样缺失——shutil.vp
# L190 `dirp: posix.DIR` 变量注解解析失败根因。
# 与 Name 分支同一套 lookup_ctypedef + 递归解析(单层 Module.Typedef
# 与嵌套 Attribute 都要支持)。
if self.Gen.TypeGenCType.CTypedefResolveDepth < TypeGenMixin.MAX_CTYPEDEF_DEPTH:
mod_td_str: str = self.Gen.TypeGenCType.lookup_ctypedef(at.attr)
if mod_td_str is not None and self.Gen.TypeGenCType.CTypedefFound != 0:
@@ -2399,9 +2378,8 @@ class TypeGenResolve(TypeGenBase.TypeGenBase):
if self.Gen.StmtGenDecl.is_enum_class(at.attr) == 1:
return self.Gen.StmtGenDecl.get_enum_base_type(at.attr)
# 嵌套模块限定的 typedef(w32.win32base.HANDLE = t.CTypedef = VOIDPTR):
# 与 Name 分支(L1986-2009)同一套 lookup_ctypedef + 递归 + map_t_type 兜底。
# 此前只查 struct/enum → w32.win32base.HANDLE 解析失败(fail-fast 报
# NameError: 'w32.win32base.HANDLE' is not defined,FileIoTest 根因)。
# 与 Name 分支同一套 lookup_ctypedef + 递归 + map_t_type 兜底
# (不查则解析失败 → NameError)。
if self.Gen.TypeGenCType.CTypedefResolveDepth < TypeGenMixin.MAX_CTYPEDEF_DEPTH:
nested_td_str: str = self.Gen.TypeGenCType.lookup_ctypedef(at.attr)
if nested_td_str is not None and self.Gen.TypeGenCType.CTypedefFound != 0:
@@ -2522,32 +2500,16 @@ class TypeGenResolve(TypeGenBase.TypeGenBase):
string.strcmp(other_nm_b.id, "float") == 0 or \
string.strcmp(other_nm_b.id, "double") == 0:
keep_other_ty = 1
# typedef 别名保真(判据=“解析是否成功”,非白名单扩名):
# sizet/uint64/CSizeT 等别名经 resolve_annotation_type 已
# 成功解析出标量,却因“不是已注册 struct 且不在白名单”被
# 丢弃 → other_ty=None → 下方 get_struct_type("sizet") miss
# → 联合静默退化为 i8*:哈希槽 slot[0] 只做 1 字节读写、
# 指针被截断成低字节 0xd0,随后被当 KwEntry* 解引用(TPV2
# Phase1 includes/ast/tokens.vp _kw_index_put ASan 读地址
# 0xd0 崩根因;TPC 侧同 sha 为 alloca i64*/load i64/store i64)。
# 用统一口径 resolve_typedef_or_map(CTypedef 动态表 →
# builtins → stdint/w32.win32base 库源 → map_t_type)判定
# 该裸名是否是可解析的 typedef/别名;命中即保留本次已解析出
# 的类型,参与“基类型 + 指针层数”合并(对齐 TPC
# HandlesTypeMerge._MergeAllComponentTypes L887-946:
# sizet | t.CPtr ⇒ 基类型 i64 + 1 层指针 = i64*)。
# 指针层数口径(本处 / VpiReader 注入侧 / 调用侧三处必须同步):
# `| t.CPtr` 恒为"追加 1 层指针",唯一例外是 struct 引用语义
# (struct 变量本身即指针)时吸收这 1 层:
# VOIDPTR(= CVoid|CPtr → i8*)| t.CPtr ⇒ i8**(不吸收)
# CVoid|CPtr 裸用 ⇒ i8*(见上方 CVoid 分支)
# CChar|t.CPtr ⇒ i8*、CUInt64T|t.CPtr ⇒ i64*(标量 + 1 层)
# 类(__new__ → Ptr(struct))| t.CPtr ⇒ Ptr(struct)(吸收 1 层)
# 依据 TPC TransPyC/lib/core/Handles/HandlesTypeMerge.py
# L940-946:Result.PtrCount += cptr_count,仅
# `IsStruct and base_ptr_count > 0` 时再减 1(吸收)。
# 判据 = Ptr 的 pointee 是否为**已注册 struct**
# (find_struct_by_type 命中 ⇒ 类引用语义 ⇒ 吸收)。
# typedef 别名保真(判据=解析是否成功,非白名单扩名):
# sizet/uint64/CSizeT 等别名经 resolve_annotation_type 已解析出标量,
# 若因"不是已注册 struct 且不在白名单"被丢弃 → 联合静默退化为 i8*
# (哈希槽只做 1 字节读写、指针被截断)。用统一口径 resolve_typedef_or_map
# (CTypedef 动态表 → builtins → stdint/w32.win32base 库源 → map_t_type)
# 判定该裸名是否可解析,命中即保留已解析类型参与「基类型 + 指针层数」合并
# (对齐 TPC HandlesTypeMerge._MergeAllComponentTypes)。
# 指针层数口径(本处 / VpiReader 注入侧 / 调用侧三处必须同步):`| t.CPtr`
# 恒为"追加 1 层指针",唯一例外是 struct 引用语义时吸收这 1 层
# (判据 = Ptr 的 pointee 是否为已注册 struct,find_struct_by_type 命中即吸收)。
if keep_other_ty == 0 and other_node is not None and other_node.kind() == ast.ASTKind.Name:
other_nm_td: ast.Name | t.CPtr = (ast.Name | t.CPtr)(other_node)
if other_nm_td.id is not None:
@@ -2593,12 +2555,10 @@ class TypeGenResolve(TypeGenBase.TypeGenBase):
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
return llvmlite.Ptr(pool, other_ty)
# t.CPtr[...] 下标指针标记(t.CPtr[T] 非普通 CPtr 标记,_is_cptr_marker
# 不识别 Subscript): X | t.CPtr[T] → 指针层级合并(对齐 TPC 联合合并
# 规则 HandlesTypeMerge.py L864-884:CPtr 侧贡献 PtrCount,另一侧提供
# BaseType)。如 uint8 | t.CPtr[t.CPtr] → u8**。此前左右都不是
# CPtr 标记 → 落下方"无 CPtr 标记"分支返回左值 i8 → c.Deref(out) 报
# "参数不是指针类型"(zdeflate.vp compress out 根因)。
# t.CPtr[...] 下标指针标记(普通 _is_cptr_marker 不识别 Subscript):
# X | t.CPtr[T] → 指针层级合并(对齐 TPC HandlesTypeMerge.py L864-884:
# CPtr 侧贡献 PtrCount,另一侧提供 BaseType),如 uint8 | t.CPtr[t.CPtr] → u8**。
# 不处理会落到"无 CPtr 标记"分支返回左值 i8 → c.Deref 报"参数不是指针类型"。
cptr_sub_side: ast.AST | t.CPtr = None
cptr_other: ast.AST | t.CPtr = None
if self._is_cptr_subscript(bop.left) != 0:
@@ -2662,12 +2622,9 @@ class TypeGenResolve(TypeGenBase.TypeGenBase):
if sub.value.kind() == ast.ASTKind.Name:
gen_nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(sub.value)
if gen_nm.id is not None:
# 裸名 CArray(from t import * 场景,如 core.vp 的
# `__python_suffix__: CArray[str] = [...]):与 t.CArray
# 语义等价。否则走泛型模板查找失败(CArray 不是泛型类模板)→
# 字段类型退化 Ptr(i8) → __before_init__ 默认值 List 字面量误走
# 动态 list 构造(list[str].__new__/append 符号未生成 → 链接
# undefined reference,Termination4K3 链接失败根因)。
# 裸名 CArray(from t import * 场景):与 t.CArray 语义等价——否则走泛型
# 模板查找失败 → 字段类型退化 Ptr(i8) → 默认值 List 字面量误走动态 list
# 构造(符号未生成 → 链接 undefined reference)。
if string.strcmp(gen_nm.id, "CArray") == 0 or string.strcmp(gen_nm.id, "rawarray") == 0:
# 单参数: CArray[elem_ty] → Ptr(elem_ty)(静态推导长度数组)
if sub.slice.kind() != ast.ASTKind.Tuple:
@@ -2699,9 +2656,8 @@ class TypeGenResolve(TypeGenBase.TypeGenBase):
return None
return llvmlite.Array(pool, elem_ty_n, count_n)
# rowptr[T] 内建裸指针:等价 t.CPtr[T] → Ptr(T)(T 自身可含指针层级)。
# 与 t.CPtr[T](下方 L2375 附近)语义一致;此前 rowptr[T] 走泛型特化
# / builtins 兜底失败 → "函数返回类型解析失败:注解不可解析"
# (ast.__init__.parse -> rowptr[AST] 根因)。
# 与 t.CPtr[T] 语义一致;不处理则泛型特化/builtins 兜底失败 →
# "函数返回类型解析失败:注解不可解析"。
if string.strcmp(gen_nm.id, "rowptr") == 0:
if sub.slice.kind() != ast.ASTKind.Tuple:
rp_elem: llvmlite.LLVMType | t.CPtr = self.resolve_annotation_type(
@@ -2766,11 +2722,8 @@ class TypeGenResolve(TypeGenBase.TypeGenBase):
and self.Gen.ImportsGen.is_module_imported(imported_modules, arr_val_name.id) == 0:
self.Gen.TypeGenErr.fatal_type_error(node, "t.Callable 模块未导入")
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
# 指针类型: t.CPtr[T] → Ptr(T)(指向 T 的指针,T 自身可含指针层级)
# t.CPtr[t.CPtr] → Ptr(Ptr(CPtr=i8*)) = i8**(双指针)
# 指针类型: t.CPtr[T] → Ptr(T)(T 自身可含指针层级):t.CPtr[t.CPtr] → i8**,
# t.CPtr[int] → i32*(DeclarationGenerator.py L1014-1016 文档化语法)。
# 此前缺失 → uint8 | t.CPtr[t.CPtr] 联合退化为左值 i8 →
# c.Deref(out) 报"参数不是指针类型"(zdeflate.vp compress out 根因)
if string.strcmp(arr_at.attr, "CPtr") == 0:
if string.strcmp(arr_val_name.id, "t") != 0 and string.strcmp(arr_val_name.id, "c") != 0 \
and self.Gen.ImportsGen.is_module_imported(imported_modules, arr_val_name.id) == 0:
@@ -2778,14 +2731,10 @@ class TypeGenResolve(TypeGenBase.TypeGenBase):
inner_cptr_ty: llvmlite.LLVMType | t.CPtr = self.resolve_annotation_type(
pool, sub.slice, imported_modules, from_imports, trans, depth + 1)
if inner_cptr_ty is None:
# 元素类型依赖未就绪(如 t.CPtr[类] 中类尚未注册):返回 None
# 交上层多遍收敛,不在此 fatal 中断 Phase 1a 预注册。
# 根因:阶段2 删 map_t_type stdint 硬编码后,UINT 等只经 CTypedef
# 动态表解析;Phase 1a(declare_only=1)无 vpi 注入 → 依赖 typedef
# 的类(zhuff_code)被跳过 → t.CArray[t.CPtr?]/t.CPtr[类] 元素
# 解析失败被误 fatal(zhuff.vp 根因)。与裸名 CArray 路径(L1760)
# 及 ClassGenDef 字段 pending 机制保持一致;真实类型错误由 Phase A
# 收敛检查暴露("Phase A 无法完成翻译")。
# 元素类型依赖未就绪(如 t.CPtr[类] 中类尚未注册):返回 None 交上层多遍
# 收敛,不在此 fatal 中断 Phase 1a 预注册(UINT 等只经 CTypedef 动态表解析,
# Phase 1a 无 vpi 注入时依赖 typedef 的类会被跳过)。与裸名 CArray 路径及
# ClassGenDef 字段 pending 机制一致;真实类型错误由 Phase A 收敛检查暴露。
return None
return llvmlite.Ptr(pool, inner_cptr_ty)
if string.strcmp(arr_at.attr, "CArray") != 0:
@@ -2818,14 +2767,10 @@ class TypeGenResolve(TypeGenBase.TypeGenBase):
elem_ty: llvmlite.LLVMType | t.CPtr = self.resolve_annotation_type(
pool, elem_node, imported_modules, from_imports, trans, depth + 1)
if elem_ty is None:
# 元素类型依赖未就绪(如 t.CArray[类, N] 中类尚未注册):返回 None
# 交上层多遍收敛,不在此 fatal 中断 Phase 1a 预注册。
# 根因:阶段2 删 map_t_type stdint 硬编码后,UINT 等只经 CTypedef
# 动态表解析;Phase 1a(declare_only=1)无 vpi 注入 → 依赖 typedef
# 的类(zhuff_code)被跳过 → t.CArray[zhuff_code, N] 元素解析失败
# 被误 fatal(zhuff.vp L19 根因)。与裸名 CArray 路径(L1760)及
# ClassGenDef 字段 pending 机制保持一致;真实类型错误由 Phase A
# 收敛检查暴露("Phase A 无法完成翻译")。
# 元素类型依赖未就绪(如 t.CArray[类, N] 中类尚未注册):返回 None 交上层多遍
# 收敛,不在此 fatal 中断 Phase 1a 预注册(UINT 等只经 CTypedef 动态表解析,
# Phase 1a 无 vpi 注入时依赖 typedef 的类会被跳过)。与裸名 CArray 路径及
# ClassGenDef 字段 pending 机制一致;真实类型错误由 Phase A 收敛检查暴露。
return None
# 解析 count(支持整数常量、None、CDefine 常量名、常量表达式如 2*MAX)
# t.CArray[elem_ty, None] 等价于单参数形式 → Ptr(elem_ty)
+43 -78
View File
@@ -11,28 +11,14 @@ import lib.core.Gen.GenApi as GenApi
# ============================================================
# TypeSpec - 统一类型描述符(对齐 TPC lib/core/TypeSpec.py,去 Python 化)
#
# 背景(见 Debugger/_Type.md 第 6 节设计):
# TPV 原有类型信息散落在「字符串 + 零散布尔」里 —— VarEntry.AnnotClassName /
# FieldEntry.AnnotClassName(注解类名串当影子类型系统,S4 已删除并并入本结构
# 的 SourceName 字段)、VarEntry.IsPtrElement(单一布尔,只覆盖一种 8 字节
# 步长情形,S4 已删除并并入本结构的 ElemBits 元素语义三元组)、
# FuncEntry.TypeStr(函数类型存文本,S4 已改由本结构的 SpecToLLVMText 从
# Spec 派生),而 llvmlite.LLVMType 只保留解析结果、已抹平指针层级/数组
# 维度/原始注解。
# 结果是「是否无符号/指针几层/数组几维/函数签名」到处重复解析字符串
# (同一值被末段类名/完整限定名/精确名/mangled 前缀/截断串五种语义解释)。
#
# 阶段:S1 只提供结构与构造/序列化;S2 各解析处「双写」;S3 逐块切读;
# S4 删旧字符串路径 —— FuncEntry.TypeStr、VarEntry/FieldEntry.AnnotClassName、
# VarEntry.IsPtrElement 均已删除,仅「容器种类判断」(list/dict/CPtr 的
# 字符串子串判定)待后续。
#
# 生命周期(设计 D1):实例与内部数组一律 `CompilerFactory.factory.PermPool`
# 分配(契约见 CompilerFactory.vp:跨 mark 持久、从不 reset),故跨文件/跨模块
# 可直接传指针,无需序列化。代价是 PermPool 只增不减 ⇒ 由 InternSpec 驻留去重。
#
# 不用全局表:驻留表挂在 TypeSpecArena 实例上,由调用方(S2 起为 Generator)
# 持有并传入,避免模块级可变态。
# 用途:替代散落的「字符串 + 零散布尔」类型信息(注解类名串 / IsPtrElement 布尔 /
# 函数类型文本),集中表达符号性/指针层级/数组维度/函数签名,避免同一值被多种
# 语义重复解析。详见 Debugger/_Type.md 第 6 节。
# 生命周期(设计 D1):实例与内部数组一律由 CompilerFactory.factory.PermPool 分配
# (契约:跨 mark 持久、从不 reset),故跨文件/跨模块可直接传指针,无需序列化;
# 代价是 PermPool 只增不减 ⇒ 由 InternSpec 驻留去重。
# 不用全局表:驻留表挂在 TypeSpecArena 实例上,由调用方(S2 起为 Generator)持有
# 并传入,避免模块级可变态。
# ============================================================
@@ -70,20 +56,14 @@ SPEC_TEXT_MAX: t.CDefine = 256
# ============================================================
# 符号性精确名表 - 按「名字切片」判定(**不做子串扫描**)
#
# 为什么必须精确匹配:旧实现 `_annot_str_unsigned` 用 strstr 子串扫描,
# 已确认两类误判(Debugger/_Type.md A4/A5):
# - `BYTEPTR`(本质 i8* 指针)含子串 `BYTE` → 被判为无符号整数;
# - 任何含 `uint32` 子串的名字(含 sha1 前缀名)都会命中。
#
# 覆盖范围界定:本模块**只认 t/c 核心原语名与少数内建名**;stdint 的别名
# (DWORD/WORD/QWORD/size_t/uint32_t/u32/…)由调用方沿 CTypedef 链**逐层展开**
# 后再落到本函数(链展开需要 CTypedef 表,不在本模块做,避免反向依赖)。
#
# 规范性修正(相对旧表):
# - `SSizeT` 是**有符号**(旧 `_type_name_unsigned` 把它列为无符号,为 A3 缺陷);
# - `BOOL` 按 stdint.vp 定义是 `int`(**有符号**),并非布尔无符号;
# - 指针名(`BYTEPTR/UINTPTR/INTPTR/CHARPTR/…`)在此返回 UNKNOWN ——
# 指针的"无符号"语义由 PtrLevel 表达,与整数符号性无关。
# 必须精确匹配:strstr 子串扫描会误判 `BYTEPTR`(含 `BYTE`)以及任何含 `uint32`
# 子串的名字(含 sha1 前缀名)。
# 覆盖范围:本模块只认 t/c 核心原语名与少数内建名;stdint 别名(DWORD/WORD/size_t/
# uint32_t/…)由调用方沿 CTypedef 链逐层展开后再落到本函数(链展开需 CTypedef 表,
# 不在本模块做,避免反向依赖)。
# 口径:SSizeT 为**有符号**;BOOL 按 stdint.vp 定义是 int(**有符号**);
# 指针名(BYTEPTR/UINTPTR/INTPTR/CHARPTR/…)返回 UNKNOWN——指针的"无符号"语义
# 由 PtrLevel 表达,与整数符号性无关。
# ============================================================
@@ -223,12 +203,10 @@ def SpecSignFromTypeName(name: str) -> int:
# ============================================================
# TypeSpec - 类型描述符
#
# 元素语义三字段说明(ElemBits/IsElemPtr/DerefBase):
# 它们替代旧的 VarEntry.IsPtrElement 单一布尔。原因(_Type.md D7-4):
# `str | t.CPtr`、`bytes | t.CPtr`、泛型 `T | t.CPtr`、`64 位标量 | t.CPtr`
# 退化后的 LLVM 类型**都是单层 Ptr(i8)**,即 PtrLevel=1 同时对应两种相反语义,
# 仅靠指针层级无法区分;区分靠的是注解内容 ⇒ 需要「元素宽度 + 元素是否指针 +
# 基址是否需二次解引用」三元组。
# 元素语义三字段(ElemBits/IsElemPtr/DerefBase):`str | t.CPtr`、`bytes | t.CPtr`、
# 泛型 `T | t.CPtr`、`64 位标量 | t.CPtr` 退化后的 LLVM 类型都是单层 Ptr(i8),
# PtrLevel=1 同时对应两种相反语义,仅靠指针层级无法区分 ⇒ 靠「元素宽度 + 元素是否
# 指针 + 基址是否需二次解引用」三元组区分(替代旧的单一布尔 IsPtrElement)。
# ============================================================
@t.NoVTable
class TypeSpec:
@@ -240,21 +218,16 @@ class TypeSpec:
Elem: TypeSpec | t.CPtr # Ptr/Array 的指向或元素(None=无)
ArrayCount: int # Array 的元素个数(0=未知/动态)
BaseName: str # 结构体/枚举/类名(带模块 sha1;PermPool 深拷贝)
# SourceName: 注解侧「来源名」(等价旧 VarEntry/FieldEntry.AnnotClassName)。
#
# 与 BaseName 的**本质区别**是命名域不同:
# BaseName = LLVM 结构体注册名(跨模块/特化时形如 {16hex sha1}.Class 或
# list[f467978339422f32.AST|t.CPtr]),由 SpecFromLLVMType 反推;
# SourceName = 注解**书写形态**的名字(裸名 MemBuddy / 模块限定名
# memhub.MemBuddy / t.XXX 裸 attr 名 uint32 / Subscript mangled 名),
# 由注解侧 SpecWithSource 填写。
#
# 为何需要它:`X | t.CPtr` 联合被简化为 Ptr(i8) 后,BaseName 恒为 None(Spec 从
# LLVM 反推,类名已丢),而属性访问/方法分派/结构体查找仍要靠注解侧名字恢复。
#
# SourceName: 注解侧「来源名」(替代 AnnotClassName)。
# 与 BaseName 的区别是命名域:BaseName = LLVM 结构体注册名(跨模块/特化时形如
# {16hex sha1}.Class 或 list[...|t.CPtr]),由 SpecFromLLVMType 反推;
# SourceName = 注解**书写形态**的名字(裸名 / 模块限定名 / t.XXX 裸 attr 名 /
# Subscript mangled 名),由注解侧 SpecWithSource 填写。
# 为何需要:`X | t.CPtr` 联合被简化为 Ptr(i8) 后 BaseName 恒为 None(类名已丢),
# 而属性访问/方法分派/结构体查找仍要靠注解侧名字恢复。
# **不参与驻留键**:注解名种类远多于类型种类(每个泛型 mangled 名/别名各一份),
# 若入键会让 InternSpec 条目暴增且线性查找变慢;且驻留本意只是跨 reset 存活,
# 而 PermPool 分配天然满足。故 SpecWithSource 返回**独立副本、不进表**。
# 入键会让 InternSpec 条目暴增且线性查找变慢;驻留本意只是跨 reset 存活,
# 而 PermPool 分配天然满足 ⇒ SpecWithSource 返回独立副本、不进表。
SourceName: str
Ret: TypeSpec | t.CPtr # Func:返回类型
Params: sizet | t.CPtr # Func:参数数组(连续 TypeSpec* 指针,ParamCount 个)
@@ -289,16 +262,16 @@ class TypeSpecArena:
# 驻留哈希索引 FNV-1a 64 位常量(与 hashtable.vp 同款)
_SPEC_FNV_OFFSET: t.CUInt64T = 0xcbf29ce484222325
_SPEC_FNV_PRIME: t.CUInt64T = 0x100000001b3
_SPEC_FNV_OFFSET: uint64 = 0xcbf29ce484222325
_SPEC_FNV_PRIME: uint64 = 0x100000001b3
def _spec_key_hash(key: str) -> uint64:
"""规范文本键的 FNV-1a 64 位哈希(同一键必得同一值;仅用于槽定位)"""
h: t.CUInt64T = _SPEC_FNV_OFFSET
h: uint64 = _SPEC_FNV_OFFSET
k: str = key
while k[0] != 0:
h = h ^ t.CUInt64T(k[0])
h = h ^ uint64(k[0])
h = h * _SPEC_FNV_PRIME
k += 1
return uint64(h)
@@ -691,14 +664,10 @@ def _spec_to_text(sp: TypeSpec | t.CPtr, buf: str, buf_size: sizet,
return SpecBaseText(sp, buf, buf_size)
# ============================================================
# _spec_text_append - 有界尾部追加(返回新的写入位置)
#
# **溢出哨兵**:返回 `buf_size` 表示"装不下"(合法位置恒 < buf_size)。
# 调用方**必须**据此返回失败 —— 绝不静默截断:驻留键被截断会让后续
# strcmp 撞上别的类型,从而返回**错误的 Spec 实例**(2026-09-11 实测:
# 长函数签名键被截断 ⇒ 某函数拿到别的函数的签名 Spec,格式化闸门报 ret 不符)。
# ============================================================
# **溢出哨兵**:返回 `buf_size` 表示"装不下"(合法位置恒 < buf_size)。调用方必须
# 据此返回失败——绝不静默截断:驻留键被截断会让后续 strcmp 撞上别的类型,返回**错误的
# Spec 实例**(如长函数签名键被截断 ⇒ 某函数拿到别的函数的签名 Spec,格式化闸门误报)。
def _spec_text_append(buf: str, buf_size: sizet, pos: sizet, text: str) -> sizet:
"""把 text 追加到 buf 的 pos 处;装不下返回 buf_size(哨兵),否则返回新位置"""
if buf is None or text is None:
@@ -939,10 +908,9 @@ def InternSpec(arena: TypeSpecArena | t.CPtr,
return None
return (TypeSpec | t.CPtr)(t.CVoid(uint64(slot_e2[0]), t.CPtr))
i += 1
# 未命中:追加。**表满 = fail-fast**(Debugger/_Type.md D14 风险 2):
# 不能返回 None —— 调用链会把 None 一路传到 entry.Spec,条目**静默失去类型信息**
# (如 _entry_spec_unsigned 恒返回 0 ⇒ zext/sext 方向取错),属静默数据损坏,
# 与「不兜底 / fail-fast」原则直接冲突(原注释声称 fail-fast,实现却只返回 None)。
# 未命中:追加。**表满 = fail-fast**:不能返回 None——调用链会把 None 一路传到
# entry.Spec,条目静默失去类型信息(如符号性判定恒返回 0 ⇒ zext/sext 方向取错),
# 属静默数据损坏,与「不兜底 / fail-fast」原则直接冲突。
if arena.Count >= arena.Cap:
GenApi.fatal_error(
None,
@@ -1152,17 +1120,14 @@ def SpecBitWidth(sp: TypeSpec | t.CPtr) -> int:
return sp.Bits
# ============================================================
# SpecTypePtrLevel - LLVM 类型 → 指针层级(**指针层级块的唯一实现**)
#
# S3 起由 ExprGenBase.get_ptr_level 委托调用(原实现是 Gen 层里的同码副本,
# 现收拢到本模块,成为该块切读 TypeSpec 的入口)。
# 由 ExprGenBase.get_ptr_level 委托调用(原为 Gen 层同码副本,现收拢到本模块)。
# 返回 0=非指针 / 1=一级指针 / 2=二级指针 / 3+…
#
# ⚠️ 规避自举 match codegen 缺陷(原 get_ptr_level 注释所记):`case` 体内
# **直接更新外层 while 条件变量**会把赋值写进 subject 快照槽,while 条件读旧槽
# ⇒ 死循环。故此处 `case` 只绑定局部 `nxt`,循环变量在 match **外**更新。
# ============================================================
# ⚠️ 规避自举 match codegen 缺陷:`case` 体内直接更新外层 while 条件变量会把赋值
# 写进 subject 快照槽,while 条件读旧槽 ⇒ 死循环。故此处 `case` 只绑定局部 `nxt`,
# 循环变量在 match **外**更新。
def SpecTypePtrLevel(ty: llvmlite.LLVMType | t.CPtr) -> int:
"""计算指针层级,0=非指针,1=一级指针,2=二级指针…"""
if ty is None:
+26 -64
View File
@@ -10,15 +10,9 @@ import stdlib
# ============================================================
# VarGenMixin - 变量/作用域符号表 Mixin(P6 对象化,wiki/14-oo-refactor.md)
#
# 从 HandlesVar.vp 迁移:模块级函数(init_vars/find_var/find_var_entry/
# lookup_var_entry/set_var_annot_class_name/set_var_ptr_element/add_var/
# _alloca_at_entry/get_or_create_var/_create_scope/init_symbol_table/
# enter_scope/exit_scope/define_var/define_module_var/lookup_var/
# lookup_current/lookup_module_var/is_module_scope/get_or_create_sym)
# → 本类方法。
# VarGenMixin - 变量/作用域符号表 Mixin
#
# 职责:变量注册/查找、作用域进出(模块/函数/match)与 alloca 创建。
# @t.NoVTable:静态绑定(方法沿 MRO 直接调用 + this 调整,无 vtable 开销)。
# ============================================================
@@ -140,9 +134,7 @@ class VarGenMixin:
if entry is None:
return 1
if class_name is None:
# 无注解名:不写入。原实现无条件 `entry.AnnotClassName = None` 会清空,
# 但 VarEntry 槽位不复用(add_var 只递增),首次设置时原值恒为 None,
# 故二者等价;重复调用且第二次为 None 的差异不存在于现有调用点。
# 无注解名:不写入(VarEntry 槽位不复用,原值恒为 None,与清空等价)。
return 0
arena_sn: TSpec.TypeSpecArena | t.CPtr = self.Gen.TypeGenBase.ensure_spec_arena()
if arena_sn is None:
@@ -170,18 +162,13 @@ class VarGenMixin:
return None
return TSpec.SpecSourceName(entry.Spec)
# ============================================================
# set_var_ptr_element — 标记变量为「指针元素(ptr-element)」语义
#
# 注解为 bytes|t.CPtr / str|t.CPtr / T|t.CPtr / 64位标量|t.CPtr 的变量,
# 其 LLVM 类型退化为 i8*(Ptr(Int8)),但下标访问应按 **8 字节槽步长**
# (i8** 语义),而非 1 字节步长(i8* 语义)。
#
# [S4] 原 VarEntry.IsPtrElement 布尔字段已删除,改由 Spec.ElemBits 承载:
# 上述四类在 LLVM 层都是 Ptr(Int8)(ElemBits=8,无法区分),故注解侧把
# 元素槽宽覆盖为 8 字节;消费点经 var_is_ptr_element 读取。
# 注解为 bytes|t.CPtr / str|t.CPtr / T|t.CPtr / 64位标量|t.CPtr 的变量,其
# LLVM 类型退化为 i8*(Ptr(Int8)),但下标访问应按 8 字节槽步长(i8** 语义),
# 而非 1 字节步长(i8* 语义)。
# 该语义由 Spec.ElemBits 承载:上述四类在 LLVM 层都是 Ptr(Int8)(ElemBits=8,
# 无法区分),故注解侧把元素槽宽覆盖为 8 字节,消费点经 var_is_ptr_element 读取。
# 由 AnnAssign 按注解形式(_is_ptr_element_annotation)调用。
# ============================================================
def set_var_ptr_element(self, symtab: GenTypes.SymbolTable | t.CPtr,
name: str) -> int:
"""标记变量为 ptr-element(Spec.ElemBits=64),返回 0=成功 / 1=失败"""
@@ -201,14 +188,10 @@ class VarGenMixin:
entry.Spec = TSpec.SpecWithElemBits(entry.Spec, 64)
return 0
# ============================================================
# var_is_ptr_element - 变量是否「指针元素」语义(原 VarEntry.IsPtrElement)
#
# 判据(三元组 ElemBits 承载):PtrLevel>=1 且元素位宽为 8(LLVM 层退化后的
# i8*)且 ElemBits==64(注解侧覆盖的 8 字节槽)。
# 后两个条件缺一不可:真实 i64* 变量 ElemBits 也为 64,但其 Elem.Bits=64,
# 不会被误判;纯 str/bytes/char|t.CPtr 变量 ElemBits=8,同样不命中。
# ============================================================
# var_is_ptr_element - 变量是否「指针元素」语义(Spec.ElemBits 承载)
# 判据:PtrLevel>=1 且元素位宽为 8(LLVM 层退化后的 i8*)且 ElemBits==64
# (注解侧覆盖的 8 字节槽)。后两个条件缺一不可:真实 i64* 变量 ElemBits 也为 64
# 但 Elem.Bits=64,不命中;纯 str/bytes/char|t.CPtr 变量 ElemBits=8,同样不命中。
def var_is_ptr_element(self, entry: GenTypes.VarEntry | t.CPtr) -> int:
"""变量是否为 ptr-element 语义(1=是 / 0=否)"""
if entry is None or entry.Spec is None:
@@ -237,13 +220,11 @@ class VarGenMixin:
entry_size: sizet = GenTypes.VarEntry.__sizeof__()
entry_addr: uint64 = uint64(vars_ptr) + var_count * entry_size
entry: GenTypes.VarEntry | t.CPtr = (GenTypes.VarEntry | t.CPtr)(t.CVoid(entry_addr, t.CPtr))
# [D25 根因] 变量名必须**深拷贝**:原实现 entry.Name 直接存 AST 节点
# Name.id 的指针(指向 tree 的 pool)。翻译推进过程中该段内存可能被后续
# 分配覆盖 ⇒ find_var 的 strcmp 静默失配 ⇒ Handle 阶段找不到 PreScan 已
# 建的同名 alloca → 另建一个类型退化的 alloca(D25 实证:body_list 的
# 特化 alloca 成死代码、被引用的是稍后建的裸 list* alloca;随后
# body_list.get 误分派到无关特化 list[str].get)。深拷贝到 stdlib
# (跨 pool reset 存活),使符号表条目不再依赖 AST tree 生命周期。
# 变量名必须深拷贝到 stdlib(跨 pool reset 存活):entry.Name 直接存 AST
# 节点 Name.id 指针时,该段内存被后续分配覆盖会使 find_var 的 strcmp 静默
# 失配 → Handle 阶段找不到 PreScan 已建的同名 alloca → 另建类型退化的
# alloca(随后 .get 等按错误元素类型分派)。深拷贝后符号表条目不再依赖
# AST tree 生命周期。
name_len: sizet = string.strlen(name)
name_copy: bytes = stdlib.malloc(name_len + 1)
if name_copy is None:
@@ -276,15 +257,9 @@ class VarGenMixin:
return None
return TSpec.SpecFromLLVMType(arena, ty)
# ============================================================
# _alloca_at_entry - 在函数入口块生成 alloca(确保支配性)
#
# LLVM IR 要求指令支配所有使用点。如果 alloca 在条件分支内生成,
# 但在其他分支使用,会违反支配性(Instruction does not dominate all uses)。
# 标准做法:所有 alloca 在函数入口块生成。
#
# 临时切换 builder.CurBlock 到入口块,生成 alloca,然后恢复。
# ============================================================
# LLVM IR 要求指令支配所有使用点,故所有 alloca 须在函数入口块生成。
# 实现:临时切换 builder.CurBlock 到入口块,生成 alloca 后恢复。
def _alloca_at_entry(self, builder: llvmlite.IRBuilder | t.CPtr,
ty: llvmlite.LLVMType | t.CPtr) -> llvmlite.Value | t.CPtr:
"""在函数入口块生成 alloca,返回 alloca 值
@@ -296,9 +271,7 @@ class VarGenMixin:
return None
return llvmlite.build_alloca_at_entry(builder, ty)
# ============================================================
# get_or_create_var — 查找或创建变量 alloca(旧版兼容)
# ============================================================
def get_or_create_var(self, pool: rowptr[memhub.MemBuddy],
builder: llvmlite.IRBuilder | t.CPtr,
vars_ptr: GenTypes.VarEntry | t.CPtr,
@@ -478,22 +451,13 @@ class VarGenMixin:
return 1
return 0
# ============================================================
# _lookup_through_match_scope — 穿越 match case 作用域查找变量
#
# match case 作用域(SCOPE_MATCH)仅用于"变体字段绑定隔离"(case 绑定的
# 模式变量互不泄漏)。case 体内对"外层普通变量"的赋值(如 FuncGenDef
# 参数 match 的 param_ty2 = Ptr(...),或 translate_subscript 内层 case 的
# list_struct_name = lsn)语义上应写回外层 alloca,而非新建 case 局部槽
# ——否则外层变量恒为初始值。自举证据:gen1 编译出的 gen2 其 FuncGenDef
# 参数 match 把赋值写进 case 局部槽、build_alloca 读初始 i32_ty,导致
# 指针参数 alloca 退化为 i32 → FuncTest main.vp:32 `p[0] = p[0] + val`
# 的 translate_subscript 读 i32 非指针返回 None("赋值右侧为 None
# [kind=11]" 根因)。
# 遍历规则:逐级向上查找;SCOPE_MATCH 边界可穿越(可命中外层 case 声明的
# 变量),遇到第一个非 match 作用域(函数/模块)后停止——不跨函数边界
# (闭包 nonlocal 语义由 is_nonlocal_name 单独处理)。
# ============================================================
# match case 作用域(SCOPE_MATCH)仅用于"变体字段绑定隔离"(case 绑定的模式
# 变量互不泄漏);case 体内对"外层普通变量"的赋值语义上应写回外层 alloca,
# 而非新建 case 局部槽——否则外层变量恒为初始值。
# 遍历规则:逐级向上查找;SCOPE_MATCH 边界可穿越(可命中外层 case 声明的变量),
# 遇到第一个非 match 作用域(函数/模块)后停止——不跨函数边界(闭包 nonlocal
# 语义由 is_nonlocal_name 单独处理)。
def _lookup_through_match_scope(self, symtab: GenTypes.SymbolTable | t.CPtr,
name: str) -> llvmlite.Value | t.CPtr:
"""从当前作用域向上,仅穿越 SCOPE_MATCH 作用域查找变量 alloca"""
@@ -534,9 +498,7 @@ class VarGenMixin:
return None
if self.define_var(symtab, name, alloca) != 0:
# fail-fast: 变量注册失败(如超过 MAX_VARS)必须立即终止。
# 此前静默忽略返回值:alloca 已创建但未入符号表,后续读取该变量
# 报"未定义名称"(NewAstTest main() 289 个局部变量 > MAX_VARS=256
# 的 NameError 根因),误导排查。变量表超限暴露上游规模问题。
# 静默忽略返回值会使 alloca 已建但未入符号表,后续读取报"未定义名称"。
self.Gen.TypeGenErr.fatal_error(
None,
f"变量 '{name}' 注册失败(作用域变量数超过上限 MAX_VARS)",
+8 -30
View File
@@ -13,12 +13,8 @@ import lib.core.VLogger as VLogger
# ============================================================
# IncludesScanner - includes 目录递归扫描
#
# 递归扫描 includes 目录,收集所有 .vp/.py 文件路径和 SHA1,
# 为 Phase1 stub 生成和 Phase2 stub 合并提供基础数据。
#
# 使用 Win32 FindFirstFileA/FindNextFileA 实现目录遍历。
# IncludesScanner - includes 目录递归扫描(Win32 FindFirstFileA/FindNextFileA)
# 递归收集 .vp 文件路径与 SHA1,供 Phase1 stub 生成 / Phase2 stub 合并使用
# ============================================================
# 全局 mbuddy 指针
@@ -142,11 +138,7 @@ def add_file_entry(result: ScanResult | t.CPtr,
# ============================================================
# compute_file_sha1 - 读取文件内容并计算 SHA1
#
# 与 Projectrans.py 一致:CRLF → LF 转换后计算 SHA1。
# Projectrans.py 用 Python 文本模式读取(自动 CRLF→LF),
# IncludesScanner 用二进制模式读取,需手动去除 \r。
# compute_file_sha1 - 读取文件内容并计算 SHA1(CRLF→LF 后计算,与 Projectrans.py 一致)
# ============================================================
def compute_file_sha1(pool: rowptr[memhub.MemBuddy],
file_path: str) -> str:
@@ -154,9 +146,7 @@ def compute_file_sha1(pool: rowptr[memhub.MemBuddy],
if pool is None or file_path is None:
return None
# 读取文件内容(stdlib.malloc 分配,避免 mbuddy 池耗尽)
# BUF_SIZE 与翻译侧 SRC_BUF_SIZE(1MB)一致:若截断到 128KB,
# 超过 128KB 的文件尾部内容变更不会改变 SHA1 → 增量缓存永不失效。
# 读取文件内容(stdlib.malloc 分配,避免 mbuddy 池耗尽);BUF_SIZE 须与翻译侧 SRC_BUF_SIZE(1MB)一致,否则尾部变更不改 SHA1 导致缓存永不失效
BUF_SIZE: sizet = 1048576
buf: bytes = Utils.read_file_to_buf(file_path, BUF_SIZE)
if buf is None:
@@ -194,10 +184,7 @@ def compute_file_sha1(pool: rowptr[memhub.MemBuddy],
# ============================================================
# scan_directory_recursive - 递归扫描目录
#
# 使用 FindFirstFileA/FindNextFileA 遍历目录树,
# 对每个 .vp/.py 文件计算 SHA1 并添加到结果中。
# scan_directory_recursive - 递归扫描目录(FindFirstFileA/FindNextFileA 遍历目录树)
# ============================================================
def scan_directory_recursive(pool: rowptr[memhub.MemBuddy],
root_dir: str,
@@ -217,9 +204,7 @@ def scan_directory_recursive(pool: rowptr[memhub.MemBuddy],
return 1
viperlib.snprintf(pattern, root_len + 4, "%s/*", root_dir)
# 使用 FindFirstFileA 开始搜索
# find_data 缓冲 +16 余量:TPV 的 WIN32_FIND_DATAA.__sizeof__() 可能小于
# Windows 实际写入(对齐差异),越界写会破坏相邻堆块(对齐 Phase2 的 +16)。
# find_data 缓冲 +16 余量:TPV 的 WIN32_FIND_DATAA.__sizeof__() 小于 Windows 实际写入,越界写会破坏相邻堆块
fd_size_s: sizet = win32file.WIN32_FIND_DATAA.__sizeof__()
find_data: win32file.WIN32_FIND_DATAA | t.CPtr = stdlib.malloc(fd_size_s + 16)
if find_data is None:
@@ -313,9 +298,7 @@ def scan_directory_recursive(pool: rowptr[memhub.MemBuddy],
# ============================================================
# scan_includes - 扫描 includes 目录入口
#
# 扫描指定的 includes 目录,返回所有 .vp/.py 文件的路径和 SHA1。
# scan_includes - 扫描 includes 目录入口(返回所有 .vp 文件路径与 SHA1)
# ============================================================
def scan_includes(pool: rowptr[memhub.MemBuddy],
includes_dirs: str) -> ScanResult | t.CPtr:
@@ -463,12 +446,7 @@ def _TryResolveModuleFile(inc_dir: str, rel_seg: bytes, rel_len: sizet,
# ============================================================
# ResolveModuleToPath - 模块名→文件路径(按文件系统按需解析)
#
# 依赖图按需扫描:BFS 遇到模块名(如 ast.parser)时,直接在
# includes 目录下解析对应文件路径,不做全量目录遍历(未引用的
# includes 根本不扫描)。相对导入(from . import X)已在解析阶段
# resolve 成绝对模块名后再调用本函数。
# ResolveModuleToPath - 模块名→文件路径(按文件系统按需解析,不做全量目录遍历)
#
# 解析规则(多 includes 目录逐个尝试,模块优先于包):
# {inc_dir}/{mod.replace('.','/')}.vp (文件模块)
+4 -27
View File
@@ -11,19 +11,7 @@ import w32.win32base as win32base
# ============================================================
# VLogger - 原生日志系统(Windows 控制台彩色输出)
#
# 输出格式与 TPC (lib/core/VLogger.py) 对齐:
# ├ INFO: message
# ├ INFO[category]: message
# ├ WARN: message
# ├ ERROR: message
# ├ SUCCESS: message
# ├ DEBUG: message
# ╠ CRITICAL: message (红底白字,致命错误)
# ╠ CRITICAL[category]: message
#
# 分段着色:前缀符号(├/╠) 用级别色,LEVEL 标签用亮色,消息用默认色
# CRITICAL 级别使用 ╠ 前缀和红底白字标签,与 TPC critical 对齐
# 输出格式: ├ LEVEL: message / ├ LEVEL[category]: message(与 TPC lib/core/VLogger.py 对齐)
# ============================================================
# 日志级别
@@ -81,13 +69,8 @@ class Logger:
return 0
# ============================================================
# _log - 核心日志输出(分段着色)
#
# _log - 核心日志输出(分段着色:前缀符号 ├ / LEVEL 标签 / 消息内容)
# 格式: ├ LEVEL: msg 或 ├ LEVEL[category]: msg
# 分三段着色:
# 1. 前缀符号 ├ (sym_color)
# 2. LEVEL 标签 (level_color)
# 3. 消息内容 (默认白色)
# ============================================================
def _log(self, level: int, prefix_sym: str, level_str: str, msg: str,
sym_color: WORD, level_color: WORD,
@@ -207,18 +190,12 @@ def init_logger():
# ============================================================
# --nowarn-register 开关(追加):抑制 Phase A 依赖类 WARN 输出——
# ①"跨模块函数依赖模块 text.ll 尚未生成"(HandlesExprCall,Phase A 多遍
# 收敛的中间遍,下遍重译后精确解析)。自举编译中大量出现,刷屏影响日志收敛。
# 仅抑制输出,不影响内部逻辑(标记重试等照常执行)。
# --nowarn-register 开关:抑制 Phase A 依赖类 WARN 输出(仅抑制输出,不影响内部逻辑)
# ============================================================
_nowarn_register: int = 0
# ============================================================
# 模块级便捷函数(与 TPC 的 lib.core.VLogger 对齐)
#
# 调用方式: VLogger.info("msg", "category")
# 无需先 get_logger(),方便各模块直接使用
# 模块级便捷函数(与 TPC 的 lib.core.VLogger 对齐,无需先 get_logger())
# ============================================================
def info(msg: str, category: str = "") -> int:
+13 -35
View File
@@ -61,12 +61,7 @@ def main() -> int:
if cmd_line_d is not None and string.strstr(cmd_line_d, "--detection") is not None:
detect_flag = 1
# --detection-deep:重诊断(MemBuddy 每次 alloc 都探测 + 周期全表扫描),
# 用于定位越界写/重复分配,默认关(极慢,见 memhub.MEMBUDDY_PERIOD_SCAN 注释)。
# 取值方式与上一条 --detection 完全一致:MemBuddy 在 parse_args(本函数 L169)
# 之前就要构造,只能用原始命令行 strstr 取值,不能等 args 解析结果。
# 注意 "--detection-deep" 含 "--detection" 前缀 → 上面那条 strstr 必然命中,
# detect_flag 同时为 1(重诊断依赖 detection 的 0xCDCD 布局与记录表)。
# --detection-deep:重诊断(每次 alloc 探测 + 周期全表扫描,极慢,默认关);须在 MemBuddy 构造前用原始命令行 strstr 取值
deep_detect_flag: int = 0
if cmd_line_d is not None and string.strstr(cmd_line_d, "--detection-deep") is not None:
deep_detect_flag = 1
@@ -122,10 +117,7 @@ def main() -> int:
gen.__init__(mb)
GenGenerator._g_gen = gen
# P2-P5: 注入各 Handles 模块级单例(Phase1 阶段即调用其状态函数)。
# 注意:TPV 编译模块级全局带 sha1 前缀(模块隔离),必须逐个模块赋值;
# 种子 TPC 时代用裸名 @_g_gen(全局共享),遗漏三个 Handles 模块无感,
# 自举后 HT/HE/HEC 委托读各自 _g_gen=None 恒失效(is_global_name 恒 0 误报)
# P2-P5: 注入各 Handles 模块级单例(Phase1 阶段即调用其状态函数);模块级全局带 sha1 前缀(模块隔离),必须逐个模块赋值
GenApi._g_gen = gen
# P6: 注入语句/赋值/变量/导入/翻译驱动模块的单例
@@ -166,7 +158,7 @@ def main() -> int:
parser.add_argument("--help", "-h", argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Show help and exit")
# 解析命令行参数
args: argparse.ParsedArgs | t.CPtr = parser.parse_args(sys._argc, sys._argv)
args: argparse.ParsedArgs = parser.parse_args(sys._argc, sys._argv)
if args is None:
log.error("参数解析失败", "argparse")
return 1
@@ -227,9 +219,7 @@ Target: %s
else: stdio.printf(" --clear-cache: False\n")
stdio.printf("参数解析完成。\n\n")
# ASan 模式
# includes 缓存用独立目录 .binary_asan 隔离 asan 插桩 obj,避免与非 asan obj 混链
# (asan obj 引用 __asan_init 等符号,非 asan 链接不提供 asan 库 → undefined reference)
# ASan 模式:includes 缓存用独立目录 .binary_asan 隔离 asan 插桩 obj,避免与非 asan obj 混链(undefined reference)
if args.get_bool("asan"):
BuildPipeline._asan_mode = True
Config._inc_bin_suffix = ".binary_asan"
@@ -243,9 +233,7 @@ Target: %s
BuildPipeline._asan_mode = False
Config._inc_bin_suffix = ".binary"
# === --phase2command: 只生成命令写入 bat,不执行 llc/链接 ===
# Phase B/B+ 的 llc(含 ASan opt)与 Phase C 的 clang 链接命令写入指定
# .bat 路径(BuildPipeline._phase2_write_cmd 首次写入 header,后续追加)。
# === --phase2command: 只生成命令写入 bat(Phase B/B+ 的 llc 与 Phase C 的 clang 链接命令),不执行 llc/链接 ===
p2c: str = args.get_str("phase2command")
if p2c is not None:
BuildPipeline._phase2_cmd_path = p2c
@@ -296,18 +284,13 @@ Target: %s
if proj_dir is not None:
string.memcpy(proj_dir, proj_path, slash_pos)
proj_dir[slash_pos] = '\0'
# [--build-dir] 命令行覆盖 project.vpj 的 build_dir:在 resolve_paths 前
# 设置,使相对路径仍基于 project_dir 解析(如 ./tpv_build2 → Test/tpv_build2),
# 绝对路径(/ 或 \ 开头)由 _join_path 原样保留。
# [--build-dir] 覆盖 project.vpj 的 build_dir:须在 resolve_paths 前设置,使相对路径仍基于 project_dir 解析
if build_dir is not None:
Config.BuildDir = build_dir
Config.resolve_paths(proj_dir)
Config.print_config()
# --asan 要求工程自己在 project.vpj 里声明 ASan 运行库(TPV 不再硬编码
# clang64 的 libclang_rt 路径):clang 没有给 x86_64-w64-windows-gnu 出
# ASan 运行库,只能借用 clang64(MSVC ABI) 的那两份,是否承担这个选择
# 属于工程自身的事,故在此 fail-fast 而不是给个含混的 undefined reference。
# --asan 要求工程在 project.vpj 的 linker.static_libs 声明 ASan 运行库(TPV 不硬编码路径),缺失则 fail-fast
if BuildPipeline._asan_mode != 0 and Config.asan_runtime_declared() == 0:
log.error("启用 --asan 需要在 project.vpj 的 linker.static_libs 里声明 ASan 运行库:", "asan")
log.error(" D:/msys64/clang64/lib/clang/22/lib/windows/libclang_rt.asan_dynamic-x86_64.dll.a", "asan")
@@ -347,10 +330,7 @@ Target: %s
n_cc: int = Utils.CleanDir(Config.TempDir)
log.info(f"{Config.TempDir}: 删除 {n_cc} 个文件(.vpi/.stub.ll 缓存)", "clear-cache")
# === --phase 参数控制 ===
# phase=1: 仅 stub 分离(生成 .stub.ll/.text.ll),不编译
# phase=2: 仅 stub 合并 + 编译(跳过 stub 分离)
# phase=all: 两者都执行(默认)
# === --phase 参数控制: 1=仅 stub 分离;2=仅 stub 合并+编译;all=两者都执行(默认) ===
phase_mode: str = phase
if phase_mode is None:
phase_mode = "all"
@@ -380,10 +360,7 @@ Target: %s
# AST 解析:如果指定了 --src 或 --project,读取文件并解析为 AST 树
src_path: str = args.get_str("src")
# 无 project 配置且未指定 --src:无法构建,fail-fast 退出。
# (非工作区执行时 CWD 无 project.vpj,proj_loaded=0;若继续执行,
# 未初始化的 Config 字段可能为垃圾指针 → is not None 检查通过 →
# 用非法路径 → 崩溃且 ASan 堆栈来不及输出)
# 无 project 配置且未指定 --src:fail-fast 退出(否则未初始化的 Config 字段为垃圾指针 → 崩溃)
if proj_loaded == 0 and src_path is None:
log.error("未找到 project.vpj(当前目录/上级目录)且未指定 --src,无法构建", "config")
argparse.release(args)
@@ -426,7 +403,7 @@ Target: %s
if src_path is not None:
log.banner("AST 解析")
# 打开文件
f: fileio.File | t.CPtr = fileio.File(src_path, fileio.MODE.R)
f: fileio.File = fileio.File(src_path, fileio.MODE.R)
if f.closed:
log.error(f"无法打开文件: {src_path}", "fileio")
argparse.release(args)
@@ -557,7 +534,7 @@ Target: %s
sf_stub_len: sizet = string.strlen(sf_stub_buf)
sf_stub_path: str = StubMergerCollect._sliced_path(temp_dir, td_len_sf, module_name, "stub.ll")
if sf_stub_path is not None:
sf_f: fileio.File | t.CPtr = fileio.File(sf_stub_path, fileio.MODE.W)
sf_f: rowptr[fileio.File] = fileio.File(sf_stub_path, fileio.MODE.W)
if not sf_f.closed:
sf_f.write(sf_stub_buf, sf_stub_len)
sf_f.close()
@@ -570,7 +547,7 @@ Target: %s
sf_text_len: sizet = string.strlen(sf_text_buf)
sf_text_path: str = StubMergerCollect._sliced_path(temp_dir, td_len_sf, module_name, "text.ll")
if sf_text_path is not None:
sf_tf: fileio.File | t.CPtr = fileio.File(sf_text_path, fileio.MODE.W)
sf_tf: rowptr[fileio.File] = fileio.File(sf_text_path, fileio.MODE.W)
if not sf_tf.closed:
sf_tf.write(sf_text_buf, sf_text_len)
sf_tf.close()
@@ -629,4 +606,5 @@ Target: %s
argparse.release(args)
log.success("TransPyV 完成")
return 0