perf(prof,gen): 编译热点优化 + 内存池 OOM 诊断接线 + REnum 指针载荷修正

CallGenBase: 模块返回类型文本表改单趟登记+查找,find_func_in_module 合并为单趟链表遍历(PROF 热点)

BuildPipeline: ensure_dir 增加快速路径(已存在目录直接返回)

AssignGenMixin/Translator: alloc 失败打印 Pool/PermPool 统计与阶段上下文(SetAllocCtx)

CallGenCtor: REnum 指针载荷补显式 build_ptrtoint 再 coerce/store;新增 DebugInfoGen.vp

其余为历史累积改动一并入库
This commit is contained in:
TermiNexus committed 2026-09-26 19:27:23 +08:00
1 parent 676c19c362
commit 373a20c3e5
20 files changed
+1984 -165

No files matched your search

File diff suppressed because it is too large. Load diff
+13
View File
@@ -27,6 +27,7 @@ import lib.Phase.Phase1.Phase1Topo as Phase1Topo
import lib.Phase.Phase1 as Phase1
import lib.core.Gen.GenApi as GenApi
import lib.core.Gen.DebugInfoGen as DebugInfoGen
# ============================================================
@@ -1027,6 +1028,11 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str,
tr.ModuleSha1 = sha1
tr.CurrentPackage = GenApi.compute_package_from_relpath(mb, entry.RelPath)
GenApi.set_current_file_type(file_path)
# --traceback: mirror the DWARF switch into the Gen-layer emitter (the
# same BuildPipeline._traceback_mode that gates -frame-pointer=all /
# dbghelp / the Install() ctor). include/*.vp modules are compiled here,
# so this is what gives their frames DWARF line info.
DebugInfoGen.SetEnabled(BuildPipeline._traceback_mode)
GenApi.set_current_file_imports(file_path)
GenApi.set_current_module_sha1(sha1)
GenApi.clear_cdefine_constants()
@@ -1058,6 +1064,13 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str,
failed += 1
continue
# --traceback: inject the startup Install() ctor when translating traceback.vp
# (its stub/text.ll below are what Phase2 reuses to build the .obj). The
# injector lives in the Gen layer (already depends on llvmlite), so this
# module gains no cross-module llvmlite symbol dependency.
if BuildPipeline._traceback_mode != 0 and BuildPipeline._tb_basename_is_traceback(file_path) != 0:
HandlesTranslator.InjectTracebackInstallCtor(tr)
# 诊断:pre_rc 哨兵在 translate 前、覆盖不到 translate 期间的 AST/IR 写穿,故 dump 前立即检查存活块 header/canary
# (rc=6 块尾 canary 损坏 = 越界写穿数据区),把「dump 时链表成环卡死」变成「定位到 translate 期间写穿发生的文件」。
post_rc: int = mb.check_alive_blocks(mark1)
+4
View File
@@ -1694,6 +1694,10 @@ def RunMultiFileProject(mb: memhub.MemBuddy*,
if lret == 0:
VLogger.success(f"输出: {exe_path}", "PhaseC")
# --traceback: export <exe>.stackmap (skipped under --phase2command,
# where nothing was actually linked).
if BuildPipeline._traceback_mode != 0 and BuildPipeline._phase2_cmd_path is None:
BuildPipeline.WriteStackMap(mb, exe_path)
if args.get_bool("run") and BuildPipeline._phase2_cmd_path is not None:
# phase2command 模式:exe 尚未链接(命令在 bat 中),跳过 --run
VLogger.info("phase2command 模式:跳过 --run(exe 尚未链接)", "phase2cmd")
+3 -8
View File
@@ -2043,7 +2043,6 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*,
# 解析注解——根治跨上下文误报(如 t.CArray[str] 需要 't' 已导入)。
stub_imported_modules: str = None
stub_from_imports: str = None
si: sizet
for si in range(n):
schild: ast.AST* = ch.get(si)
if schild is None:
@@ -2053,15 +2052,12 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*,
simp: ast.Import* = ast.Import*(schild)
if simp.names is not None:
sn_n: sizet = len(simp.names)
sni: sizet
for sni in range(sn_n):
sal: ast.Alias* = ast.Alias*(simp.names.get(sni))
if sal is not None and sal.name is not None:
stub_imported_modules = _stub_add_imported(
pool, stub_imported_modules, sal.name)
stub_imported_modules = _stub_add_imported(pool, stub_imported_modules, sal.name)
if sal.asname is not None:
stub_imported_modules = _stub_add_imported(
pool, stub_imported_modules, sal.asname)
stub_imported_modules = _stub_add_imported(pool, stub_imported_modules, sal.asname)
elif sk == ast.ASTKind.ImportFrom:
sifr: ast.ImportFrom* = ast.ImportFrom*(schild)
sifr_mod: str = sifr.module
@@ -2083,8 +2079,7 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*,
string.strcpy(qbuf_r + pkg_len_r + 1, sifr_mod)
sifr_mod_q = qbuf_r
if sifr_mod is not None:
stub_imported_modules = _stub_add_imported(
pool, stub_imported_modules, sifr_mod_q)
stub_imported_modules = _stub_add_imported(pool, stub_imported_modules, sifr_mod_q)
# 方案A:包 __init__.vpi 的重导出类以包 sha1 注册别名 entry
# (ast.Attribute 定义在 ast.exprs 但归属 ast 包;查找端按包
# sha1 匹配,注册端按定义文件 sha1 → name+sha1 错位 fail-fast。
@@ -897,6 +897,40 @@ def _BuildReachableSha1Set(mb: memhub.MemBuddy*, source_dir: str,
stdlib.free(worklist)
return -1
# 2b. --traceback: force the traceback runtime module into the reachable set
# (TPC: used_includes.add('traceback')). Its Install() symbol and the
# @llvm.global_ctors ctor are only produced by compiling this module, so a
# missing file is fail-fast rather than a silent "feature off" build.
if BuildPipeline._traceback_mode != 0:
tb_abs: t.CPtr = stdlib.malloc(8)
tb_rel: t.CPtr = stdlib.malloc(8)
tb_inc: t.CPtr = stdlib.malloc(8)
tb_found: int = -1
if tb_abs is not None and tb_rel is not None and tb_inc is not None:
tb_abs_slot: sizet* = (sizet*)(tb_abs)
tb_rel_slot: sizet* = (sizet*)(tb_rel)
tb_inc_slot: sizet* = (sizet*)(tb_inc)
tb_abs_slot[0] = 0
tb_rel_slot[0] = 0
tb_inc_slot[0] = 0
tb_found = IncludesScanner.ResolveModuleToPath(
includes_dirs, "traceback", tb_abs, tb_rel, tb_inc)
if tb_abs is not None:
stdlib.free(tb_abs)
if tb_rel is not None:
stdlib.free(tb_rel)
if tb_inc is not None:
stdlib.free(tb_inc)
if tb_found != 0:
stdlib.free(worklist)
VLogger.error(f"--traceback: 在 includes 目录中找不到 traceback.vp,无法编译并链接 Install()(请确认 project.vpj 的 includes 包含仓库 includes 目录;已扫描: {includes_dirs})", "Traceback")
return -1
tb_wl: int = _AppendDepsToWorklist(worklist, wl_len, "traceback", None)
if tb_wl < 0:
stdlib.free(worklist)
return -1
wl_len = sizet(tb_wl)
# 3. 工作列表 BFS:模块名 → 文件系统解析 → 读+hash+parse import
processed: bytes = stdlib.malloc(StubMerger.REACH_WORKLIST_BUF)
if processed is None:
@@ -1069,6 +1103,10 @@ def _BuildReachableSha1Set(mb: memhub.MemBuddy*, source_dir: str,
tokens_r: ast.Token* = ast.tokenize(lx_r)
tree_r = ast.parse_tokens(mb, tokens_r)
if tree_r is None:
# parse_tokens keeps the error detail (line/col/message) in the global
# channel only, so print the unified parse traceback before the one-line
# log; without it a reachable-includes syntax error is undiagnosable.
GenApi.dump_parse_traceback(abs_path)
VLogger.error(f"可达 includes 解析失败: {abs_path}", "Reachable")
stdlib.free(abs_path)
stdlib.free(rel_path)
+6 -7
View File
@@ -580,24 +580,23 @@ def GetSha1StoreIncdirArrPtr() -> bytes:
# 调用者直接遍历数组:sha1_arr[i*17]、mod_arr[i*64]、rel_arr[i*StubMerger.MAX_REL_PATH_LEN]
# 内存归全局存储器所有,调用者不要 free
# ============================================================
def GetSha1StoreArr(out_sha1_arr: t.CPtr, out_mod_arr: t.CPtr, out_rel_arr: t.CPtr) -> int:
def GetSha1StoreArr(out_sha1_arr: rawptr[t.CPtr], out_mod_arr: rawptr[t.CPtr], out_rel_arr: rawptr[t.CPtr]) -> int:
"""获取全局存储器数组指针
out_sha1_arr/out_mod_arr/out_rel_arr 是调用者提供的 t.CPtr 指针,
函数将存储器数组指针写入这些位置。
返回条目数,-1 失败
out_sha1_arr/out_mod_arr/out_rel_arr 是调用者提供的 t.CPtr 槽的地址(t.CPtr*),
函数把存储器数组指针写入这些槽。返回条目数,-1 失败
"""
if out_sha1_arr is None or out_mod_arr is None or out_rel_arr is None:
return -1
if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None or StubMerger._sha1_store_rel is None:
return -1
sp: t.CPtr = (t.CPtr | t.CPtr)(out_sha1_arr) # 存疑
sp: rawptr[t.CPtr] = out_sha1_arr
if sp is not None:
sp[0] = StubMerger._sha1_store_arr
mp: t.CPtr = (t.CPtr | t.CPtr)(out_mod_arr)
mp: rawptr[t.CPtr] = out_mod_arr
if mp is not None:
mp[0] = StubMerger._sha1_store_mod
rp: t.CPtr = (t.CPtr | t.CPtr)(out_rel_arr)
rp: rawptr[t.CPtr] = out_rel_arr
if rp is not None:
rp[0] = StubMerger._sha1_store_rel
return StubMerger._sha1_store_count
+11
View File
@@ -13,6 +13,7 @@ import lib.core.Gen.Translator as HT
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Generator as Generator
import lib.core.CompilerFactory as CompilerFactory
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.core.Gen.GenApi as GenApi
@@ -2632,6 +2633,16 @@ novirtual class AssignGenMixin:
asg_target_ty = self.Gen.VarGen.repl_slot_ty(pool, asg_re)
rhs_val: llvmlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, asgn.value, None, 0, trans, asg_target_ty)
if rhs_val is None:
# 归属:RHS 为 None 多数是分配失败(池耗尽)。“[MEM] alloc 失败”只给池基址,
# 分不清是主池(mark/reset 回收)还是常驻池(从不 reset)满,也无法知道
# 当前阶段/文件。故此处把两个池的用量与池标识一并打出来。
mb_diag: memhub.MemBuddy* = CompilerFactory.factory.Pool
pp_diag: memhub.MemBuddy* = CompilerFactory.factory.PermPool
if mb_diag is not None:
mb_diag.dump_stats("主池(Pool)")
if pp_diag is not None:
pp_diag.dump_stats("常驻池(PermPool)")
stdio.fflush(None)
# 增强错误信息:包含文件名 + lineno + AST 节点详情,便于定位
if asgn.value is not None:
sha1: str = trans.ModuleSha1
+64 -93
View File
@@ -514,50 +514,42 @@ novirtual class CallGenBase:
if mod is None or name is None:
return None
name_len: sizet = string.strlen(name)
# 热点优化:原 4 趟完整链表扫描合并为 2 趟——趟 1 精确匹配(命中即返回)+ 收集首个本模块同名 mangled 候选;趟 2 收集首个 IsDeclared==0 后缀命中与首个任意后缀命中,扫描后按原优先级返回(语义不变)
# 热点优化:原「趟 1 精确匹配+本模块同名、趟 2 declare/任意后缀」两趟完整链表扫描
# 合并为一趟——同一节点只取一次 function_get_name/NameLen/next(原两趟各取一次,
# 该链表平均数百节点且本函数每次调用都全链遍历,PROF 中本函数及其访问器约占 10%)。
# 优先级与逐趟结果完全一致:精确匹配命中即返回 > 本模块同名 > declare 后缀 > 任意后缀;
# 同类候选均取链表首个命中。
cur: llvmlite.Function* = mod.FuncHead
mangle_hit: llvmlite.Function* = None
declare_hit: llvmlite.Function* = None
any_hit: llvmlite.Function* = None
while cur is not None:
cur_name: str = llvmlite.function_get_name(cur)
if cur_name is not None:
# 快速路径:strcmp 判等蕴含首字符相等,首字符不同者跳过 strcmp(mangled 名以 sha1 十六进制打头,裸名多为字母/下划线)
if cur_name[0] == name[0] and string.strcmp(cur_name, name) == 0:
return cur
# 本模块同名候选:仅接受恰为 "{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 热点来源)。
cur_len_o: sizet = llvmlite.function_get_name_len(cur)
if cur_len_o == 17 + name_len:
if string.strncmp(cur_name, owner_sha1, 16) == 0 and cur_name[16] == '.':
suffix_start_o: sizet = cur_len_o - name_len
if cur_name[suffix_start_o - 1] == '.':
match_o: int = 1
for i in range(name_len):
if cur_name[suffix_start_o + i] != name[i]:
match_o = 0
break
if match_o == 1:
mangle_hit = cur
cur = llvmlite.function_get_next(cur)
if mangle_hit is not None:
return mangle_hit
# 后缀匹配(.name):不跳过 declare(方法 stub 也是 declare,由调用处判断 IsDeclared 决定用裸名或 sha1 前缀名)。本模块同名 define 必须优先于跨模块 stub declare,无同名 define 时回退 stub declare
declare_hit: llvmlite.Function* = None
any_hit: llvmlite.Function* = None
cur = mod.FuncHead
while cur is not None:
cur_name2: str = llvmlite.function_get_name(cur)
if cur_name2 is not None:
# 名字长度改取 Function.NameLen(创建时一次算好),免去热链上
# 每个节点重复 strlen(cur_name2);与 strlen(Name) 恒等,不改匹配语义。
# 名字长度取 Function.NameLen(创建时一次算好),免去对热链每个节点重复 strlen
cur_len: sizet = llvmlite.function_get_name_len(cur)
# 本模块同名候选:仅接受恰为 "{owner_sha1}.{name}" 的形态(不匹配 "{sha1}.Class.method");declare 与 define 同名同符号,是否 IsDeclared==0 均可返回
if owner_sha1 is not None and mangle_hit is None and cur_len == 17 + name_len:
if string.strncmp(cur_name, owner_sha1, 16) == 0 and cur_name[16] == '.':
suffix_start_o: sizet = cur_len - name_len
if cur_name[suffix_start_o - 1] == '.':
match_o: int = 1
for i in range(name_len):
if cur_name[suffix_start_o + i] != name[i]:
match_o = 0
break
if match_o == 1:
mangle_hit = cur
# 后缀匹配(.name):不跳过 declare(方法 stub 也是 declare,由调用处判断 IsDeclared 决定用裸名或 sha1 前缀名)
if cur_len > name_len + 1:
suffix_start: sizet = cur_len - name_len
if cur_name2[suffix_start - 1] == '.':
if cur_name[suffix_start - 1] == '.':
match: int = 1
for i in range(name_len):
if cur_name2[suffix_start + i] != name[i]:
if cur_name[suffix_start + i] != name[i]:
match = 0
break
if match == 1:
@@ -568,6 +560,8 @@ novirtual class CallGenBase:
if cur.IsDeclared == 0 and declare_hit is None:
declare_hit = cur
cur = llvmlite.function_get_next(cur)
if mangle_hit is not None:
return mangle_hit
if declare_hit is not None:
return declare_hit
return any_hit
@@ -947,23 +941,32 @@ novirtual class CallGenBase:
return None
return (str | t.CPtr)(v)
# _CacheModuleRetTyTexts:把某模块 text.ll 全部 define 行的返回类型写入持久缓存(键/值深拷贝进 PermPool,同名只记首次,只登记 "{sha1}." 开头的本模块 mangled 名);仅在成功读到 text.ll 后调用,负结果绝不缓存
# _CacheModuleRetTyTexts:单趟扫描某模块 text.ll——把全部 define 行的返回类型写入持久缓存(键/值深拷贝进 PermPool,同名只记首次,只登记 "{sha1}." 开头的本模块 mangled 名),并在同一趟里返回首个匹配 search_buf 的 define 的返回类型(未匹配返回 None);仅在成功读到 text.ll 后调用,负结果绝不缓存
def _CacheModuleRetTyTexts(self, class_sha1: str, text_buf: bytes,
text_len: sizet) -> None:
"""登记 text.ll 全部 define 行到持久缓存(建表/分配失败即不缓存,回退原扫描)"""
if self.Gen is None or class_sha1 is None or text_buf is None or text_len == 0:
return
perm: memhub.MemBuddy* = CompilerFactory.factory.PermPool
if perm is None:
return
ht: hashtable.HashTable* = self.Gen.CallGenBase.MethodRetTyMap
if ht is None:
ht = hashtable.HashTable(perm)
if ht is None:
return
self.Gen.CallGenBase.MethodRetTyMap = ht
sha1_len: sizet = string.strlen(class_sha1)
text_len: sizet, pool: memhub.MemBuddy*,
search_buf: str, search_str_len: sizet) -> llvmlite.LLVMType*:
"""单趟「登记 + 查找」text.ll 全部 define 行(建表/分配失败时只查找不登记)"""
if text_buf is None or text_len == 0:
return None
# 合并原「先全量登记一遍、再二次扫描查找」的两趟全文件遍历(PROF:两次扫描合计约 14%)。
# 建表失败(PermPool 未就绪等)不影响查找:cache_on=0 时只走同趟查找。
cache_on: int = 0
ht: hashtable.HashTable* = None
perm: memhub.MemBuddy* = None
sha1_len: sizet = 0
if self.Gen is not None and class_sha1 is not None:
perm = CompilerFactory.factory.PermPool
if perm is not None:
ht = self.Gen.CallGenBase.MethodRetTyMap
if ht is None:
ht = hashtable.HashTable(perm)
if ht is not None:
self.Gen.CallGenBase.MethodRetTyMap = ht
if ht is not None:
cache_on = 1
sha1_len = string.strlen(class_sha1)
pos: sizet = 0
result_ty: llvmlite.LLVMType* = None
while pos < text_len:
ls: sizet = pos
le: sizet = pos
@@ -998,8 +1001,8 @@ novirtual class CallGenBase:
break
name_end += 1
name_len: sizet = name_end - name_start
# 只登记本模块 mangled 名 "{sha1}.{后缀}"(查询键恒为此前缀)
if name_len > sha1_len + 1:
# 只登记本模块 mangled 名 "{sha1}.{后缀}"(查询键恒为此前缀);表不可用时跳过登记
if cache_on != 0 and name_len > sha1_len + 1:
if string.strncmp(text_buf + name_start, class_sha1, sha1_len) == 0 \
and text_buf[name_start + sha1_len] == '.':
key_buf: bytes = stdlib.malloc(name_len + 1)
@@ -1018,7 +1021,16 @@ novirtual class CallGenBase:
tcopy[rt_len] = '\0'
ht.set_str(kcopy, tcopy)
stdlib.free(key_buf)
# 同趟查找:首个与 search_buf 匹配、且后随 '"' / '(' / 行尾的 define(判定与原二次扫描逐字一致),先命中者为准
if result_ty is None and name_start + search_str_len <= ls + ll:
if string.strncmp(text_buf + name_start, search_buf, search_str_len) == 0:
end_ch: int = 0
if name_start + search_str_len < ls + ll:
end_ch = text_buf[name_start + search_str_len]
if end_ch == 34 or end_ch == 40 or end_ch == 0:
result_ty = self._ParseLLVMRetTyStr(pool, text_buf, ls + 7, at_pos)
pos = le + 1
return result_ty
# _LookupMethodRetTyFromTextLL:读取 {temp_dir}/{sha1[0:2]}/{sha1}.text.ll (level=1),按 "{sha1}.{class_name}.{method_name}" 搜索 define 行并解析返回类型;未找到/文件不存在返回 None
@@ -1108,51 +1120,10 @@ novirtual class CallGenBase:
class_sha1, class_name, method_name)
search_str_len: sizet = string.strlen(search_buf)
# 在 text.ll 中搜索 define 行包含 search_buf
buf_len: sizet = string.strlen(read_buf)
# 已成功读到本模块 text.ll:登记其全部 define 行供后续 O(1) 命中(下方原扫描保留为未命中路径)
self._CacheModuleRetTyTexts(class_sha1, read_buf, buf_len)
pos: sizet = 0
result_ty: llvmlite.LLVMType* = None
while pos < buf_len and result_ty is None:
ls: sizet = pos
le: sizet = pos
while le < buf_len:
if read_buf[le] == '\n':
break
le += 1
ll: sizet = le - ls
# define 行判定:快速路径先看行首字节是否 'd'(仅否定短路,不改变命中结果)
if ll >= 7 and read_buf[ls] == 'd' and string.strncmp(read_buf + ls, "define ", 7) == 0:
# 找 @ 符号
at_pos: sizet = ls + 7
while at_pos < ls + ll:
if read_buf[at_pos] == '@':
break
at_pos += 1
if at_pos < ls + ll:
# 跳过可选引号
name_start: sizet = at_pos + 1
if name_start < ls + ll and read_buf[name_start] == '"':
name_start += 1
# 检查是否匹配 search_buf
if name_start + search_str_len <= ls + ll:
if string.strncmp(read_buf + name_start, search_buf, search_str_len) == 0:
# 函数名边界检查:search 后必须是 '"'(带引号名)、'('(参数表)或行尾,
# 否则 "sha1.func" 会误匹配 "sha1.func_helper" 等前缀同名函数
name_end: sizet = name_start + search_str_len
end_ch: int = 0
if name_end < ls + ll:
end_ch = read_buf[name_end]
if end_ch == 34 or end_ch == 40 or end_ch == 0:
# 匹配!提取返回类型: [ls+7, at_pos)
result_ty = self._ParseLLVMRetTyStr(pool, read_buf, ls + 7, at_pos)
pos = le + 1
# 单趟扫描 text.ll:登记其全部 define 行供后续 O(1) 命中,并同趟取出首个匹配项。
# 文件长度直接用 read_all 的返回值,省掉一次整文件 strlen。
result_ty: llvmlite.LLVMType* = self._CacheModuleRetTyTexts(
class_sha1, read_buf, sizet(text_len), pool, search_buf, search_str_len)
stdlib.free(search_buf)
stdlib.free(read_buf)
+12
View File
@@ -392,6 +392,18 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase):
if arg_val is None: continue
payload_ptr: llvmlite.Value* = llvmlite.build_gep_struct(builder, renum_ty, pfe.Ty, tmp, payload_idx)
if payload_ptr is None: continue
# payload 槽位是 i32/i64(布局见 ClassGenEnum:{i32 __tag, _p1, _p2, ...}),
# 而变体成员可能是指针(Ptr.Pointee / Func.Ret / Array.ElemTy / Struct.Fields …)。
# 这是编译器自身的表示转换(tagged union 打包),不是用户代码的隐式转换:
# 必须显式 ptrtoint 后存入,读取侧按成员类型 bitcast 槽位地址再 load(对称)。
# 禁止隐式转换后此处不能再依赖 coerce_to_type 的静默 ptrtoint。
if (self.Gen.ExprGenBase.is_ptr_type(arg_val.Ty) != 0
and self.Gen.ExprGenBase.is_ptr_type(pfe.Ty) == 0
and self.Gen.ExprGenBase.get_llvm_type_bits(pfe.Ty) != 0):
casted_arg: llvmlite.Value* = llvmlite.build_ptrtoint(builder, arg_val, pfe.Ty)
if casted_arg is None:
continue
arg_val = casted_arg
# 类型转换并 store(payload 位置可能是 i32 或 i64,按字段类型 coerce)
arg_val = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val, pfe.Ty, arg)
llvmlite.build_store(builder, arg_val, payload_ptr)
+21 -3
View File
@@ -19,9 +19,9 @@ from . import ClassGenBase
# register_enum 永不调用 → 所有 CEnum 注册失败 → 裸名枚举访问 NameError)
# ============================================================
define ENUM_MEMBER_MAX_LOCAL = 128
define RENUM_ENUM_CLASS_MAX = 64
define RENUM_ENUM_VARIANT_PER_CLASS = 32
define RENUM_ENUM_ALIAS_MAX = 512
define RENUM_CLASS_MAX = 64
define RENUM_VARIANT_PER_CLASS = 32
define RENUM_ALIAS_MAX = 512
# ============================================================
# ClassGenEnum - ClassGenMixin 拆分(阶段 D):enum/union/renum 定义与变体表
@@ -1112,7 +1112,12 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
self.RenumVariantTags = CompilerFactory.factory.PermPool.alloc(8 * RENUM_CLASS_MAX * RENUM_VARIANT_PER_CLASS)
if (self.RenumClassNames is None or self.RenumClassVariantCounts is None
or self.RenumVariantNames is None or self.RenumVariantTags is None):
# A None table means the perm-pool request failed (size 0 or pool
# exhausted). Returning here left the table empty while the caller
# still reported "registered", so match dispatch silently lost every
# REnum case.
self.RenumClassNames = None
self.Gen.TypeGenErr.fatal_error(cd, f"REnum 变体表分配失败(class='{class_name}'):PermPool 返回 NULL,变体表不可用", "InternalCompilerError")
return
string.memset(self.RenumClassNames, 0, 8 * RENUM_CLASS_MAX)
string.memset(self.RenumClassVariantCounts, 0, 8 * RENUM_CLASS_MAX)
@@ -1140,6 +1145,9 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
# 表已满
if self.RenumClassCount >= RENUM_CLASS_MAX:
# Silently dropping the class made its later `match` lose every case; a
# real limit breach must be reported instead.
self.Gen.TypeGenErr.fatal_error(cd, f"REnum 类数超过上限 {RENUM_CLASS_MAX}:无法注册 class='{class_name}'", "InternalCompilerError")
return
# 注册类名(strcpy 到 perm_pool:1a 每文件 reset 后 AST 裸指针悬空,
@@ -1148,6 +1156,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
cn_len_rv: sizet = string.strlen(class_name)
cn_buf_rv: str = CompilerFactory.factory.PermPool.alloc(cn_len_rv + 1)
if cn_buf_rv is None:
self.Gen.TypeGenErr.fatal_error(cd, f"REnum 类名副本分配失败(class='{class_name}'):PermPool 返回 NULL", "InternalCompilerError")
return
string.strcpy(cn_buf_rv, class_name)
self.RenumClassNames[idx] = sizet(cn_buf_rv)
@@ -1238,6 +1247,9 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
vn_len_rv: sizet = string.strlen(vname_src)
vn_buf_rv: str = CompilerFactory.factory.PermPool.alloc(vn_len_rv + 1)
if vn_buf_rv is None:
# Breaking here registered a partial variant table: the class would
# keep some variants and silently lose every later one.
self.Gen.TypeGenErr.fatal_error(cd, f"REnum 变体名副本分配失败(class='{class_name}'):PermPool 返回 NULL", "InternalCompilerError")
break
string.strcpy(vn_buf_rv, vname_src)
self.RenumVariantNames[name_offset] = sizet(vn_buf_rv)
@@ -1359,7 +1371,10 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
if (self.RenumAliasClass is None or self.RenumAliasField is None
or self.RenumAliasIndex is None or self.RenumAliasTy is None
or self.RenumAliasVariant is None):
# Same as the variant table: a silent bail left the alias table empty,
# so match bindings later failed with an unrelated NameError.
self.RenumAliasClass = None
self.Gen.TypeGenErr.fatal_error(None, f"REnum 字段别名表分配失败(class='{class_name}'):PermPool 返回 NULL", "InternalCompilerError")
return
string.memset(self.RenumAliasClass, 0, 8 * RENUM_ALIAS_MAX)
string.memset(self.RenumAliasField, 0, 8 * RENUM_ALIAS_MAX)
@@ -1376,6 +1391,9 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
CompilerFactory.factory.RenumAliasCount = self.RenumAliasCount
if self.RenumAliasCount >= RENUM_ALIAS_MAX:
# Dropping the record silently made the variant field bind to a wrong-typed
# slot (or fail with NameError) instead of reporting the limit breach.
self.Gen.TypeGenErr.fatal_error(None, f"REnum 字段别名数超过上限 {RENUM_ALIAS_MAX}:无法注册 {class_name}.{field_name}", "InternalCompilerError")
return
# 去重检查(去指针化):class 槽位存 perm_pool 字符串拷贝,用 strcmp 内容比较——
+9
View File
@@ -14,6 +14,7 @@ import lib.core.CompilerFactory as CompilerFactory
from . import ClassGenBase
import lib.core.Gen.GenApi as GenApi
import lib.core.Gen.DebugInfoGen as DebugInfoGen
# 方法元数据 flag:TLS(@t.TLS 装饰器,对齐 ClassGenBase 本地常量;本地副本避免跨模块 CDefine 查找)
@@ -757,6 +758,11 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
return 0
llvmlite.position_at_end(func_builder, entry_blk)
# --traceback: attach a DISubprogram to this method define (same as the
# module-level function path). No-op when DI is disabled.
dbg_pushed: int = DebugInfoGen.PushFunction(pool, func_builder, func,
fd.lineno, fd.col_offset + 1)
# @t.TLS 一次性初始化守卫(对齐 TPC HandlesFunctions.py L1763-1792;
# 与 FuncGenMixin.translate_function_def 同一实现,类方法同样支持)
if is_tls_m != 0:
@@ -1024,6 +1030,9 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
trans._closure_env_count = old_env_count
trans._stop_iter_flag_ptr = old_stop_iter_flag
if dbg_pushed != 0:
DebugInfoGen.PopFunction(func_builder)
return 0
# ============================================================
# _generate_before_init — 生成 __before_init__ 函数
+443
View File
@@ -0,0 +1,443 @@
import t, c
from stdint import *
import llvmlite
import memhub
import string
import stdlib
import viperlib
import lib.core.VLogger as VLogger
# ============================================================
# DebugInfoGen - function-level DWARF metadata emitter (llvmlite text IR)
#
# Node layout mirrors TPC lib/core/LLVMCG/DebugInfo.py node-for-node:
# !DIFile -> !{ null } -> !DISubroutineType -> distinct !DICompileUnit
# -> distinct !DISubprogram (per define) -> !DILocation (per statement)
# module tail: !llvm.dbg.cu + !llvm.module.flags (Debug Info Version 3)
#
# Enabled only while --traceback is on: the Phase layer mirrors
# BuildPipeline._traceback_mode here via SetEnabled(). While disabled every
# entry point returns before touching any llvmlite DI API, so the emitted IR
# stays byte-identical to the pre-DWARF output.
#
# Scope/statement hooks are driven from the Gen layer:
# Translator.translate -> BeginModule / Finalize
# FuncGenDef.translate_function_def / ClassGenVTable._translate_method /
# FuncGenMixin (nested + lambda) -> PushFunction / PopFunction
# StmtGenMixin.translate_stmt -> SetStatement
# ============================================================
# ---- module-scope state (reset per module by BeginModule) ----
_g_enabled: int = 0
_g_finalized: int = 0
_g_mod: llvmlite.LLVMModule* = None
_g_file_path: str = None
_g_file_id: int = -1
_g_null_id: int = -1
_g_subtype_id: int = -1
_g_cu_id: int = -1
_g_cur_sp: int = -1
_g_cur_entry_ref: str = None
_g_cur_builder: llvmlite.IRBuilder* = None
_g_depth: int = 0
# Push/Pop save stack: enclosing (sp id + 1, entry DILocation ref, builder)
# per level. +1 so 0 means "no enclosing function" (sp ids are >= 0). The saved
# builder is what makes SetStatement reject a builder that is not the one the
# current DISubprogram was pushed on (e.g. an async state machine emitted inside
# a bracketed function): such instructions must carry no !dbg, otherwise LLVM's
# verifier rejects "!dbg attachment points at wrong subprogram for function".
DBG_STACK_MAX: t.CDefine = 256
_g_stk_sp: sizet* = None
_g_stk_ref: str = None
_g_stk_bld: str = None
# DILocation cache: direct-mapped, up to 8-way linear probe, key (sp, line, col).
DBG_LOC_SLOTS: t.CDefine = 8192
DBG_LOC_PROBE: t.CDefine = 8
DBG_LOC_KEY_STRIDE: t.CDefine = 3
_g_loc_key: sizet* = None
_g_loc_ref: str = None
_g_loc_used: bytes = None
# ============================================================
# SetEnabled / IsEnabled - mirror the --traceback switch
# ============================================================
def SetEnabled(flag: bool) -> int:
"""Enable/disable DI emission (mirrors BuildPipeline._traceback_mode)."""
global _g_enabled
if flag:
_g_enabled = 1
else:
_g_enabled = 0
return 0
def IsEnabled() -> int:
return _g_enabled
# ============================================================
# _EnsureStorage - lazily allocate the long-lived stack / cache buffers
# ============================================================
def _EnsureStorage() -> int:
global _g_stk_sp, _g_stk_ref, _g_stk_bld, _g_loc_key, _g_loc_ref, _g_loc_used
if _g_stk_sp is not None:
return 0
sbuf1: bytes = stdlib.malloc(DBG_STACK_MAX * 8)
sbuf2: bytes = stdlib.malloc(DBG_STACK_MAX * 8)
sbuf3: bytes = stdlib.malloc(DBG_STACK_MAX * 8)
kbuf: bytes = stdlib.malloc(DBG_LOC_SLOTS * DBG_LOC_KEY_STRIDE * 8)
rbuf: bytes = stdlib.malloc(DBG_LOC_SLOTS * 8)
ubuf: bytes = stdlib.malloc(DBG_LOC_SLOTS)
if sbuf1 is None or sbuf2 is None or sbuf3 is None or kbuf is None or rbuf is None or ubuf is None:
VLogger.error("--traceback: DWARF 元数据缓冲分配失败,终止编译", "DWARF")
return 1
string.memset(sbuf1, 0, DBG_STACK_MAX * 8)
string.memset(sbuf2, 0, DBG_STACK_MAX * 8)
string.memset(sbuf3, 0, DBG_STACK_MAX * 8)
string.memset(kbuf, 0, DBG_LOC_SLOTS * DBG_LOC_KEY_STRIDE * 8)
string.memset(rbuf, 0, DBG_LOC_SLOTS * 8)
string.memset(ubuf, 0, DBG_LOC_SLOTS)
_g_stk_sp = (sizet*)(sbuf1)
_g_stk_ref = sbuf2
_g_stk_bld = sbuf3
_g_loc_key = (sizet*)(kbuf)
_g_loc_ref = rbuf
_g_loc_used = ubuf
return 0
def _RefOf(pool: memhub.MemBuddy*, node_id: int) -> str:
"""Allocate a persistent "!N" reference string for a DI node id."""
buf: str = pool.alloc(24)
if buf is None:
return None
viperlib.snprintf(buf, 24, "!%d", node_id)
return buf
# ============================================================
# BeginModule - start of one module translation
# ============================================================
def BeginModule(mod: llvmlite.LLVMModule*, file_path: str) -> int:
"""Reset per-module DI state. Call once at the start of each module."""
global _g_mod, _g_file_path, _g_finalized, _g_file_id, _g_null_id
global _g_subtype_id, _g_cu_id, _g_cur_sp, _g_cur_entry_ref, _g_cur_builder, _g_depth
if _g_enabled == 0:
return 0
if _EnsureStorage() != 0:
return 1
_g_mod = mod
_g_file_path = file_path
_g_finalized = 0
_g_file_id = -1
_g_null_id = -1
_g_subtype_id = -1
_g_cu_id = -1
_g_cur_sp = -1
_g_cur_entry_ref = None
_g_cur_builder = None
_g_depth = 0
if _g_loc_used is not None:
string.memset(_g_loc_used, 0, DBG_LOC_SLOTS)
return 0
# ============================================================
# _EnsureModuleNodes - DIFile / !{ null } / DISubroutineType / DICompileUnit
# (created lazily at the first define, once per module)
# ============================================================
def _EnsureModuleNodes(pool: memhub.MemBuddy*) -> int:
global _g_file_id, _g_null_id, _g_subtype_id, _g_cu_id
if _g_cu_id >= 0:
return 0
mod: llvmlite.LLVMModule* = _g_mod
if mod is None:
return 1
# DIFile: basename + absolute directory, backslashes normalised to '/'
src: str = _g_file_path
if src is None:
src = ""
slen: sizet = string.strlen(src)
nbuf: str = pool.alloc(slen + 4)
if nbuf is None:
VLogger.error("--traceback: DIFile 路径缓冲分配失败,终止编译", "DWARF")
return 1
if slen > 0:
string.strncpy(nbuf, src, slen)
nbuf[slen] = '\0'
ni: sizet = 0
for ni in range(slen):
if nbuf[ni] == '\\':
nbuf[ni] = '/'
fname: str = nbuf
directory: str = ""
slash: str = string.strrchr(nbuf, '/')
if slash is not None:
fname = slash + 1
slash[0] = '\0'
directory = nbuf
dsize: sizet = string.strlen(fname) + string.strlen(directory) + 96
dbuf: str = pool.alloc(dsize)
if dbuf is None:
VLogger.error("--traceback: DIFile 节点缓冲分配失败,终止编译", "DWARF")
return 1
viperlib.snprintf(dbuf, dsize, "!DIFile(filename: \"%s\", directory: \"%s\")", fname, directory)
fid: int = llvmlite.module_add_di_node(mod, pool, dbuf)
if fid < 0:
VLogger.error("--traceback: DIFile 元数据节点分配失败,终止编译", "DWARF")
return 1
_g_file_id = fid
# types: !{ null } placeholder list, then DISubroutineType referencing it
nid: int = llvmlite.module_add_di_node(mod, pool, "!{ null }")
if nid < 0:
VLogger.error("--traceback: DISubroutineType 类型表分配失败,终止编译", "DWARF")
return 1
_g_null_id = nid
tbuf: str = pool.alloc(64)
if tbuf is None:
VLogger.error("--traceback: DISubroutineType 缓冲分配失败,终止编译", "DWARF")
return 1
viperlib.snprintf(tbuf, 64, "!DISubroutineType(types: !%d)", _g_null_id)
tid: int = llvmlite.module_add_di_node(mod, pool, tbuf)
if tid < 0:
VLogger.error("--traceback: DISubroutineType 元数据节点分配失败,终止编译", "DWARF")
return 1
_g_subtype_id = tid
cbuf: str = pool.alloc(256)
if cbuf is None:
VLogger.error("--traceback: DICompileUnit 缓冲分配失败,终止编译", "DWARF")
return 1
viperlib.snprintf(cbuf, 256,
"distinct !DICompileUnit(language: DW_LANG_C99, file: !%d, producer: \"TPC\", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)",
_g_file_id)
cid: int = llvmlite.module_add_di_node(mod, pool, cbuf)
if cid < 0:
VLogger.error("--traceback: DICompileUnit 元数据节点分配失败,终止编译", "DWARF")
return 1
_g_cu_id = cid
return 0
# ============================================================
# _LocRefAt - read a cached DILocation ref string pointer
# ============================================================
def _LocRefAt(slot: int) -> str:
slotp: str | t.CPtr = (t.CPtr)(uint64(_g_loc_ref) + sizet(slot) * 8)
r: str = slotp[0]
return r
# ============================================================
# _EnsureLocation - DILocation ref for (sp, line, col), created + cached once
# ============================================================
def _EnsureLocation(pool: memhub.MemBuddy*, sp: int, line: int, col: int) -> str:
if line <= 0:
return _g_cur_entry_ref
c: int = col
if c <= 0:
c = 1
if _g_loc_used is None or _g_mod is None:
return None
h: uint64 = (uint64(sp) * 1000003 + uint64(line) * 31 + uint64(c)) & uint64(DBG_LOC_SLOTS - 1)
pi: int = 0
while pi < DBG_LOC_PROBE:
slot: int = int((h + uint64(pi)) & uint64(DBG_LOC_SLOTS - 1))
if _g_loc_used[slot] == 0:
break
kbase: int = slot * DBG_LOC_KEY_STRIDE
if int(_g_loc_key[kbase]) == sp and int(_g_loc_key[kbase + 1]) == line \
and int(_g_loc_key[kbase + 2]) == c:
return _LocRefAt(slot)
pi += 1
lbuf: str = pool.alloc(96)
if lbuf is None:
return None
viperlib.snprintf(lbuf, 96, "!DILocation(line: %d, column: %d, scope: !%d)", line, c, sp)
lid: int = llvmlite.module_add_di_node(_g_mod, pool, lbuf)
if lid < 0:
VLogger.error("--traceback: DILocation 元数据节点分配失败,终止编译", "DWARF")
return None
lref: str = _RefOf(pool, lid)
if lref is None:
VLogger.error("--traceback: DILocation 引用缓冲分配失败,终止编译", "DWARF")
return None
if pi < DBG_LOC_PROBE:
slot2: int = int((h + uint64(pi)) & uint64(DBG_LOC_SLOTS - 1))
kbase2: int = slot2 * DBG_LOC_KEY_STRIDE
_g_loc_key[kbase2] = sizet(sp)
_g_loc_key[kbase2 + 1] = sizet(line)
_g_loc_key[kbase2 + 2] = sizet(c)
rslot: str | t.CPtr = (t.CPtr)(uint64(_g_loc_ref) + sizet(slot2) * 8)
rslot[0] = lref
_g_loc_used[slot2] = 1
return lref
# ============================================================
# PushFunction - create the DISubprogram for one define
# ============================================================
def PushFunction(pool: memhub.MemBuddy*, builder: llvmlite.IRBuilder*,
func: llvmlite.Function*, line: int, col: int) -> int:
"""Attach a DISubprogram to one define and set the builder's entry DILocation.
Returns 1 when pushed (caller must call PopFunction), 0 when DI is disabled
or not applicable. Every instruction emitted afterwards on `builder` carries
", !dbg !N" until the next SetStatement.
"""
global _g_cur_sp, _g_cur_entry_ref, _g_cur_builder, _g_depth
if _g_enabled == 0:
return 0
if pool is None or builder is None or func is None or _g_mod is None:
return 0
if _g_depth >= DBG_STACK_MAX:
VLogger.error("--traceback: DWARF 函数嵌套深度超上限,终止编译", "DWARF")
return 0
if _EnsureModuleNodes(pool) != 0:
return 0
name: str = llvmlite.function_get_name(func)
if name is None:
VLogger.error("--traceback: 函数名为空,无法发射 DISubprogram,终止编译", "DWARF")
return 0
dline: int = line
if dline <= 0:
dline = 1
dcol: int = col
if dcol <= 0:
dcol = 1
ssize: sizet = string.strlen(name) + 320
spbuf: str = pool.alloc(ssize)
if spbuf is None:
VLogger.error("--traceback: DISubprogram 缓冲分配失败,终止编译", "DWARF")
return 0
viperlib.snprintf(spbuf, ssize,
"distinct !DISubprogram(name: \"%s\", scope: !%d, file: !%d, type: !%d, line: %d, scopeLine: %d, unit: !%d, spFlags: DISPFlagDefinition)",
name, _g_file_id, _g_file_id, _g_subtype_id, dline, dline, _g_cu_id)
sp_id: int = llvmlite.module_add_di_node(_g_mod, pool, spbuf)
if sp_id < 0:
VLogger.error("--traceback: DISubprogram 元数据节点分配失败,终止编译", "DWARF")
return 0
sp_ref: str = _RefOf(pool, sp_id)
if sp_ref is None:
VLogger.error("--traceback: DISubprogram 引用缓冲分配失败,终止编译", "DWARF")
return 0
llvmlite.function_set_dbg(func, sp_ref)
# save the enclosing function's DI context (LIFO), then switch to this one
_g_stk_sp[_g_depth] = sizet(_g_cur_sp + 1)
rslot: str | t.CPtr = (t.CPtr)(uint64(_g_stk_ref) + sizet(_g_depth) * 8)
rslot[0] = _g_cur_entry_ref
bslot: str | t.CPtr = (t.CPtr)(uint64(_g_stk_bld) + sizet(_g_depth) * 8)
bslot[0] = _g_cur_builder
_g_depth += 1
_g_cur_sp = sp_id
_g_cur_builder = builder
entry_ref: str = _EnsureLocation(pool, sp_id, dline, dcol)
if entry_ref is None:
VLogger.error("--traceback: 入口 DILocation 生成失败,终止编译", "DWARF")
return 0
_g_cur_entry_ref = entry_ref
llvmlite.builder_set_debug_location(builder, entry_ref)
return 1
# ============================================================
# PopFunction - restore the enclosing function's DI context
# ============================================================
def PopFunction(builder: llvmlite.IRBuilder*) -> int:
global _g_cur_sp, _g_cur_entry_ref, _g_cur_builder, _g_depth
if _g_enabled == 0:
return 0
if _g_depth <= 0:
return 0
_g_depth -= 1
_g_cur_sp = int(_g_stk_sp[_g_depth]) - 1
rslot: str | t.CPtr = (t.CPtr)(uint64(_g_stk_ref) + sizet(_g_depth) * 8)
_g_cur_entry_ref = rslot[0]
bslot: str | t.CPtr = (t.CPtr)(uint64(_g_stk_bld) + sizet(_g_depth) * 8)
_g_cur_builder = (llvmlite.IRBuilder*)(bslot[0])
if builder is not None:
llvmlite.builder_clear_debug_location(builder)
return 0
# ============================================================
# SetStatement - point the current builder at a statement DILocation
# ============================================================
def SetStatement(pool: memhub.MemBuddy*, builder: llvmlite.IRBuilder*,
line: int, col: int) -> int:
"""Update the builder's DILocation to the current statement (line precision).
No-op outside a DI-attached define (module-level code / synthetic defines),
so instructions outside a function body never reference a function's scope.
"""
if _g_enabled == 0 or pool is None or builder is None:
return 0
if _g_cur_sp < 0:
return 0
ref: str = _EnsureLocation(pool, _g_cur_sp, line, col)
if ref is None:
return 0
llvmlite.builder_set_debug_location(builder, ref)
return 1
# ============================================================
# _ModuleHasDefine - does the module emit any define (IsDeclared == 0)?
# ============================================================
def _ModuleHasDefine(mod: llvmlite.LLVMModule*) -> int:
if mod is None:
return 0
cur: llvmlite.Function* = mod.FuncHead
steps: int = 0
while cur is not None:
steps += 1
if steps > 100000:
break
if llvmlite.function_is_declared(cur) == 0:
return 1
cur = llvmlite.function_get_next(cur)
return 0
# ============================================================
# Finalize - register !llvm.dbg.cu / !llvm.module.flags before dump_ir
# ============================================================
def Finalize(pool: memhub.MemBuddy*) -> int:
"""Module-level DI tail; idempotent per module, no-op when no SP was emitted."""
global _g_finalized
if _g_enabled == 0:
return 0
if _g_finalized != 0:
return 0
mod: llvmlite.LLVMModule* = _g_mod
if mod is None or pool is None:
return 0
_g_finalized = 1
# Self-check (mirrors TPC Phase2Translator.py:3444): a module that emits a
# define but carries no DI at all loses line info for its frames.
if mod.DIMetaCount == 0 and _ModuleHasDefine(mod) != 0:
VLogger.warning("--traceback: 本模块发射了 define 但 IR 未携带 DWARF (!llvm.dbg.cu)——该模块帧在 Ctrl+C 栈里将只显示地址", "DWARF")
if _g_cu_id < 0:
return 0
cu_ref: str = _RefOf(pool, _g_cu_id)
if cu_ref is None:
VLogger.error("--traceback: DICompileUnit 引用缓冲分配失败,终止编译", "DWARF")
return 1
llvmlite.module_add_named_metadata(mod, pool, "llvm.dbg.cu", cu_ref)
fbuf: str = pool.alloc(64)
if fbuf is None:
VLogger.error("--traceback: 模块级调试标志缓冲分配失败,终止编译", "DWARF")
return 1
viperlib.snprintf(fbuf, 64, "!{i32 2, !\"Debug Info Version\", i32 3}")
flags_id: int = llvmlite.module_add_di_node(mod, pool, fbuf)
if flags_id < 0:
VLogger.error("--traceback: llvm.module.flags 元数据节点分配失败,终止编译", "DWARF")
return 1
fl_ref: str = _RefOf(pool, flags_id)
if fl_ref is None:
VLogger.error("--traceback: llvm.module.flags 引用缓冲分配失败,终止编译", "DWARF")
return 1
llvmlite.module_add_named_metadata(mod, pool, "llvm.module.flags", fl_ref)
return 0
+15 -10
View File
@@ -363,12 +363,13 @@ novirtual class ExprGenBase:
return llvmlite.build_inttoptr(builder, llvmlite.const_int64(builder.Pool, 0), target_ty)
self.Gen.TypeGenErr.fatal_error(node, "整数隐式转指针已禁止:必须显式强转为指针(如 t.CPtr(x))才能把整数赋给指针类型", "CastError")
return val
# 指针 → 整数: ptrtoint(当目标明确是整数而非指针时)
# 注意: target_bits == 8 时跳过 ptrtoint,走后面的 build_load 解引用首字符
# 修复: buf[idx] = '\0' 中 '\0' 是 CONST_STR → i8* 指针,
# ptrtoint 会截断地址低 8 位(非 0),应 load 解引用取首字符 0
if self.is_ptr_type(val.Ty) != 0 and target_bits != 0 and target_bits != 8 and self.is_ptr_type(target_ty) == 0:
return llvmlite.build_ptrtoint(builder, val, target_ty)
# Pointer -> integer: rejected (symmetric with the integer -> pointer rule above).
# No implicit pointer <-> integer conversion is allowed; an explicit cast is
# required. (The old code silently did ptrtoint, and for 8-bit targets fell
# through to the build_load branch below, copying the pointee's first byte.)
if self.is_ptr_type(val.Ty) != 0 and target_bits != 0 and self.is_ptr_type(target_ty) == 0:
self.Gen.TypeGenErr.fatal_error(node, "指针隐式转整数已禁止:必须显式强转(如 uint64(p) / t.CInt64T(p) / int(p))才能把指针赋给整数类型", "CastError")
return val
if val_bits != 0 and target_bits != 0:
if val_bits == target_bits:
return val
@@ -432,11 +433,15 @@ novirtual class ExprGenBase:
if bc_agg is not None:
return bc_agg
return tmp_agg
# 指针 → 非指针值: build_load 解引用
# 适用于: 指针 → 整数 (如 i8* → i8), 指针 → 结构体值
# 当构造器返回 Ptr(Struct) 但目标变量是 Struct 值类型时,需要 load
# Pointer -> non-pointer value: only aggregate targets keep the implicit load
# (a constructor returning Ptr(Struct) held in a Struct-typed variable is
# reinterpreted in place). Scalar integer targets were already rejected above.
if self.is_ptr_type(val.Ty) != 0 and self.is_ptr_type(target_ty) == 0:
return llvmlite.build_load(builder, target_ty, val)
target_kind: int = target_ty.kind()
if target_kind == llvmlite.LLVMType.Struct or target_kind == llvmlite.LLVMType.Array:
return llvmlite.build_load(builder, target_ty, val)
self.Gen.TypeGenErr.fatal_error(node, "指针隐式转非指针值已禁止:必须显式解引用(c.Deref(p) / *p)或显式强转(uint64(p))", "CastError")
return val
# 指针 → 指针: bitcast (如 i8* → i8** 当目标是全局变量存储指针)
if self.is_ptr_type(val.Ty) != 0 and self.is_ptr_type(target_ty) != 0:
return llvmlite.build_bitcast(builder, val, target_ty)
+8 -24
View File
@@ -951,30 +951,14 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase):
return None
case _:
pass
# [FIX 2026-08-31] 64 位标量 | t.CPtr 联合字段下标读回
# (如 RenumClassNames: sizet*,联合解析为 i64* 8 字节
# 槽数组):槽位按 Int64 load 得位模式,赋给 str(i8*)触发 int→ptr
# 禁令误报(ClassGenEnum.vp existing: str = self.RenumClassNames[i]
# 根因)。槽里实际存的是 perm_pool 字符串指针——联合声明即"槽位可能
# 存指针",合法 C 联合读回应按指针读回。统一按 i8* load(同宽 8 字节,
# 位模式不变):指针目标直接 bitcast;整数目标隐式 ptrtoint 同样
# 保持位模式(对齐 MatchGenMixin 的 REnum 槽位按声明类型读机制)。
if trans is not None and sub.value is not None and sub.value.kind() == ast.ASTKind.Attribute:
fa_u64: str = self.Gen.ExprGenAttr._resolve_attr_field_class(pool, trans, sub.value)
if fa_u64 is not None and self._annot_is_64bit_scalar(fa_u64) != 0:
match elem_ty2:
case llvmlite.LLVMType.Int(eb_u64):
if eb_u64 == 64:
cell_p_u64: llvmlite.Value* = llvmlite.build_gep(builder, elem_ty2, ptr_val, idx_val)
if cell_p_u64 is not None:
i8_ty_u64: llvmlite.LLVMType* = llvmlite.Int8(pool)
i8p_ty_u64: llvmlite.LLVMType* = llvmlite.Ptr(pool, i8_ty_u64)
i8pp_ty_u64: llvmlite.LLVMType* = llvmlite.Ptr(pool, i8p_ty_u64)
cast_cell_u64: llvmlite.Value* = llvmlite.build_bitcast(builder, cell_p_u64, i8pp_ty_u64)
if cast_cell_u64 is not None:
return llvmlite.build_load(builder, i8p_ty_u64, cast_cell_u64, vol_sub_gen)
case _:
pass
# 64-bit scalar pointer fields (e.g. `sizet*`, `uint64*`, written as
# `t.CUInt64T | t.CPtr`) keep the declared element type here: the slot is
# a u64, so it is read as i64 -- same element type the write path uses
# (see get_subscript_ptr). Loading it as i8* fabricated a pointer value
# out of an integer slot and forced the caller through an implicit
# ptrtoint / inttoptr, which the language forbids (it also made
# `MemBuddy.free_lists[i]` mis-type its element). Sources that store or
# read a pointer through such a slot must cast explicitly.
# 自定义结构体 (如 self._ht[key]): 仅当类定义了 __getitem__ 才转发方法调用;
# 否则普通结构体指针按下标做 GEP 数组访问
cls_nm_rd_gen: str = self._get_custom_struct_cls_nm(pool, elem_ty2)
+10
View File
@@ -13,6 +13,7 @@ import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HT
import lib.core.Gen.Call.CallGenBase as CallGenBase
import lib.core.Gen.GenApi as GenApi
import lib.core.Gen.DebugInfoGen as DebugInfoGen
# ============================================================
@@ -658,6 +659,12 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
return 0
llvmlite.position_at_end(func_builder, entry_blk)
# --traceback: attach a DISubprogram to this define and set every
# instruction in the body to the entry DILocation (the statement hook
# refines it per statement). No-op when DI is disabled.
dbg_pushed: int = DebugInfoGen.PushFunction(pool, func_builder, func,
fd.lineno, fd.col_offset + 1)
# @t.TLS 一次性初始化守卫(对齐 TPC HandlesFunctions.py):首次调用执行函数体并置
# 全局标志 __tls_init_done_{name}=1,后续调用直接返回默认值。不实现则每次调用都
# TlsAlloc,provider 栈跨调用丢失。
@@ -1030,6 +1037,9 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
self.Gen.ClassGenBase.register_decorator(pool, fd.name, wb_name, decor_arg0)
self._gen_decor_wrapper(trans, pool, mod, func, fd.name, wb_name, decor_handler, decor_arg0)
if dbg_pushed != 0:
DebugInfoGen.PopFunction(func_builder)
return 0
# ============================================================
# _gen_decor_wrapper - 生成装饰器 wrapper 函数(对齐 TPC DecoratorPass.py
+13
View File
@@ -12,6 +12,7 @@ import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HT
import lib.core.Gen.Call.CallGenBase as CallGenBase
import lib.core.Gen.GenApi as GenApi
import lib.core.Gen.DebugInfoGen as DebugInfoGen
# ============================================================
@@ -107,6 +108,10 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase):
if func_builder is None: return 0
llvmlite.position_at_end(func_builder, entry_blk)
# --traceback: promoted nested closure gets its own DISubprogram.
dbg_pushed_n: int = DebugInfoGen.PushFunction(pool, func_builder, func,
node.lineno, node.col_offset + 1)
# 4. 进入嵌套函数作用域(嵌套符号表)
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_FUNCTION)
@@ -249,6 +254,8 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase):
self.Gen.VarGen.exit_scope(trans.SymTab)
trans._cur_func = old_func
trans._cur_builder = old_builder
if dbg_pushed_n != 0:
DebugInfoGen.PopFunction(func_builder)
# NOTE: _nonlocal_names 和 _nonlocal_name_count 仍为嵌套函数的值
# 13. 在父函数中创建闭包对象
@@ -1120,6 +1127,10 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase):
return None
llvmlite.position_at_end(func_builder, entry_blk)
# --traceback: promoted lambda gets its own DISubprogram.
dbg_pushed_l: int = DebugInfoGen.PushFunction(pool, func_builder, func,
node.lineno, node.col_offset + 1)
# 6. 进入 lambda 作用域
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_FUNCTION)
@@ -1209,6 +1220,8 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase):
trans._global_name_count = old_global_count
trans._nonlocal_name_count = old_nonlocal_count
trans._closure_env_count = old_env_count
if dbg_pushed_l != 0:
DebugInfoGen.PopFunction(func_builder)
# 13. 在父函数中创建 env(每个捕获变量 8 字节堆存储,env[i] = 堆地址)
env_ptr: llvmlite.Value* = None
+8
View File
@@ -660,6 +660,14 @@ novirtual class StmtGenCifMixin:
# 传 trans:无符号源类型(uint16/uint32/CSizeT 等)扩展用 zext,
# 缺 trans 时 _node_type_unsigned 查不到变量注解 → 误 sext
val = self.Gen.ExprGenBase.coerce_to_type(builder, val, ret_ty_rt, node, trans)
elif self.Gen.ExprGenBase.get_llvm_float_bits(val.Ty) != 0 \
or self.Gen.ExprGenBase.get_llvm_float_bits(ret_ty_rt) != 0:
# Float alignment: SpecTypeBits reports 0 for Float/Double, so the
# integer branch above never runs and a double literal returned from a
# `-> float` function would emit `ret double` (llc: value doesn't match
# function result type 'float'). Coerce here (fpext/fptrunc/si2fp/fptosi),
# same as TPC's emit_return.
val = self.Gen.ExprGenBase.coerce_to_type(builder, val, ret_ty_rt, node, trans)
llvmlite.build_ret(builder, val)
return 0
+7
View File
@@ -15,6 +15,7 @@ import lib.core.Gen.Translator as HT
import lib.core.CompilerFactory as CompilerFactory
import lib.Projectrans.Config as Config
import lib.core.Gen.Stmt.StmtGenDeclMixin as StmtGenDeclMixin
import lib.core.Gen.DebugInfoGen as DebugInfoGen
# ============================================================
@@ -32,6 +33,12 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
node: ast.AST*) -> int:
"""翻译单条语句,返回新增的变量数(深度守卫包装器,原逻辑见 _translate_stmt_impl)"""
if trans is not None:
# --traceback: statement-level DWARF location (no-op outside a
# DI-attached define or when DI is disabled). 此后本语句生成的每条
# 指令自动带 ", !dbg !N",保证函数体内每个 call 都有合法 !dbg。
if node is not None and trans._cur_builder is not None:
DebugInfoGen.SetStatement(trans.Pool, trans._cur_builder,
node.lineno, node.col_offset + 1)
if trans.TranslateDepth >= HT.TRANSLATE_MAX_DEPTH:
self.Gen.TypeGenErr.fatal_error(node, "语句翻译嵌套过深(疑似 AST 自引用环或翻译 bug)", "RecursionError")
trans.TranslateDepth += 1
+145 -1
View File
@@ -14,6 +14,7 @@ import lib.Projectrans.Config as Config
import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.core.Gen.Generator as Generator
import lib.core.Gen.DebugInfoGen as DebugInfoGen
# ============================================================
# Gen/Translator.vp - 翻译器状态管理 + 主入口
@@ -441,6 +442,11 @@ novirtual class Translator:
pool: memhub.MemBuddy* = _mbuddy
# OOM 归属:把「阶段/当前模块」写进 memhub 的分配上下文。池耗尽时 memhub 会打印
# ctx=阶段/模块,从而知道是在翻译哪个模块时被吃满(SetAllocCtx 只存指针不拷贝,
# ModuleSha1 由调用方在 translate 前设置且与本次翻译同寿命)。此前无人设置该上下文。
memhub.SetAllocCtx("translate", self.ModuleSha1)
# 初始化状态
self._init_state(pool)
@@ -451,6 +457,9 @@ novirtual class Translator:
return 1
self.Module = mod
# --traceback: reset per-module DWARF state (no-op when DI is disabled).
DebugInfoGen.BeginModule(mod, GenApi.get_current_file())
# 设置目标(优先使用 project.vpj 中的配置)
triple: str = Config.TargetTriple
if triple is None:
@@ -511,8 +520,12 @@ novirtual class Translator:
# 用户已定义 main → 委托 HandlesMain 翻译模块级
self._translate_module_level(pool, mod, tree)
return 0
# --traceback: register module-level !llvm.dbg.cu / !llvm.module.flags
# before the caller dumps the IR. Idempotent; no-op when no define got a
# DISubprogram (stub / _declare_only) or when DI is disabled.
DebugInfoGen.Finalize(pool)
return 0
# ============================================================
# 包装 main 函数(无用户 main 时)→ 委托 HandlesMain
# ============================================================
@@ -808,6 +821,137 @@ novirtual class Translator:
llvmlite.LLVMModulePrint(buf, size, self.Module, _mbuddy, mode)
# ============================================================
# --traceback: startup Install() ctor injection
#
# Lives here (not in BuildPipeline) because this module already imports
# llvmlite. Calling llvmlite.* from a module without that import mangles the
# calls with the package (__init__) SHA1, which no object defines, instead of
# the defining submodule's SHA1 (link-time undefined symbol). BuildPipeline
# only decides whether to trigger the injection.
#
# Injects into the traceback module's IR:
# define void @__traceback_install_ctor() { entry: call @Install(); ret void }
# plus an appending-linkage @llvm.global_ctors so the CRT runs Install() once at
# process start (mirrors TPC BaseGen._RegisterTracebackInstallViaCtor).
# fail-fast: a pre-existing @llvm.global_ctors, or a missing Install symbol,
# aborts the build (silent degradation would leave the product without a map).
# ============================================================
# _tb_module_has_global_ctors - does the module already define @llvm.global_ctors
def _tb_module_has_global_ctors(mod: llvmlite.LLVMModule*) -> int:
if mod is None:
return 0
g: llvmlite.GlobalVariable* = mod.GlobalHead
steps: sizet = 0
while g is not None:
steps += 1
if steps > 100000:
break
if g.Name is not None and string.strcmp(g.Name, "llvm.global_ctors") == 0:
return 1
g = g.Next
return 0
# _tb_find_install_function - locate this module's own Install()
#
# Accepts "<16hex>.Install" (the usual SHA1-mangled form) or the bare name.
def _tb_find_install_function(mod: llvmlite.LLVMModule*) -> llvmlite.Function*:
if mod is None:
return None
cur: llvmlite.Function* = mod.FuncHead
steps: sizet = 0
while cur is not None:
steps += 1
if steps > 100000:
return None
nm: str = llvmlite.function_get_name(cur)
if nm is not None:
if string.strcmp(nm, "Install") == 0:
return cur
nl: sizet = llvmlite.function_get_name_len(cur)
if nl == 24 and string.strcmp(nm + 16, ".Install") == 0:
return cur
cur = llvmlite.function_get_next(cur)
return None
# InjectTracebackInstallCtor - inject startup Install() ctor into the module
#
# NOTE: FunctionPrint has no linkage field, so the ctor is emitted with default
# (external) linkage instead of TPC's 'internal'; only traceback.vp defines it,
# so there is no symbol clash.
def InjectTracebackInstallCtor(tr: rawptr[Translator]) -> int:
if tr is None or tr.Module is None:
return 0
if tr._declare_only != 0:
return 0
pool: memhub.MemBuddy* = tr.Pool
if pool is None:
VLogger.error("--traceback: 内存池未初始化,无法注入 Install 构造器", "TRACEBACK")
return 1
mod: llvmlite.LLVMModule* = tr.Module
if _tb_module_has_global_ctors(mod) != 0:
VLogger.error("--traceback: 模块内已存在 @llvm.global_ctors,无法注入 traceback 启动期构造器(不静默降级)", "TRACEBACK")
return 1
install_fn: llvmlite.Function* = _tb_find_install_function(mod)
if install_fn is None:
VLogger.error("--traceback: 在 traceback 模块内找不到 Install() 函数,无法注入启动期构造器(不静默降级)", "TRACEBACK")
return 1
# 1) ctor: entry { call <ret> @Install(); ret void }
void_ty: llvmlite.LLVMType* = llvmlite.Void(pool)
ctor: llvmlite.Function* = llvmlite.new_function(pool, "__traceback_install_ctor", void_ty)
if ctor is None:
VLogger.error("--traceback: 构造器函数分配失败", "TRACEBACK")
return 1
blk: llvmlite.BasicBlock* = llvmlite.new_basic_block(pool, "entry")
if blk is None:
VLogger.error("--traceback: 构造器基本块分配失败", "TRACEBACK")
return 1
llvmlite.function_add_block(ctor, blk)
llvmlite.module_add_function(mod, ctor)
builder: llvmlite.IRBuilder* = llvmlite.new_builder(pool, ctor)
if builder is None:
VLogger.error("--traceback: 构造器 IRBuilder 分配失败", "TRACEBACK")
return 1
llvmlite.position_at_end(builder, blk)
iname: str = llvmlite.function_get_name(install_fn)
install_ret: llvmlite.LLVMType* = llvmlite.function_get_ret_ty(install_fn)
llvmlite.build_call(builder, iname, None, 0, install_ret, 0)
llvmlite.build_ret_void(builder)
# 2) @llvm.global_ctors = appending global [1 x {i32, void ()*, i8*}] [ ... ]
i32_ty: llvmlite.LLVMType* = llvmlite.Int32(pool)
i8_ty: llvmlite.LLVMType* = llvmlite.Int8(pool)
i8p_ty: llvmlite.LLVMType* = llvmlite.Ptr(pool, i8_ty)
fnv_ty: llvmlite.LLVMType* = llvmlite.Func(pool, void_ty, None, 0)
fnp_ty: llvmlite.LLVMType* = llvmlite.Ptr(pool, fnv_ty)
f1: llvmlite.ParamNode* = llvmlite.new_param_node(pool, i32_ty)
f2: llvmlite.ParamNode* = llvmlite.new_param_node(pool, fnp_ty)
f3: llvmlite.ParamNode* = llvmlite.new_param_node(pool, i8p_ty)
if f1 is None or f2 is None or f3 is None:
VLogger.error("--traceback: global_ctors 结构体字段分配失败", "TRACEBACK")
return 1
llvmlite.paramnode_set_next(f1, f2)
llvmlite.paramnode_set_next(f2, f3)
elem_ty: llvmlite.LLVMType* = llvmlite.Struct(pool, f1, 3, None)
arr_ty: llvmlite.LLVMType* = llvmlite.Array(pool, elem_ty, 1)
if elem_ty is None or arr_ty is None:
VLogger.error("--traceback: global_ctors 类型构造失败", "TRACEBACK")
return 1
gv: llvmlite.GlobalVariable* = llvmlite.new_global_variable(pool, "llvm.global_ctors", arr_ty)
if gv is None:
VLogger.error("--traceback: @llvm.global_ctors 全局变量分配失败", "TRACEBACK")
return 1
llvmlite.global_set_linkage(gv, "appending")
gv.Initializer = "[{ i32, void ()*, i8* } { i32 65535, void ()* @__traceback_install_ctor, i8* null }]"
llvmlite.module_add_global(mod, gv)
VLogger.info("--traceback: 已注入启动期 Install() 构造器(@llvm.global_ctors)", "TRACEBACK")
return 0
# ============================================================
# Mixin - Handler 非多态基类(自 HandlesBase 迁入)
#
+39 -10
View File
@@ -148,7 +148,8 @@ def main() -> int:
parser.add_argument("--run", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Run the generated executable after compilation")
parser.add_argument("--rebuild-includes", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Delete the includes.binary precompiled cache and rebuild all the includes")
parser.add_argument("--clear-cache", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Clear .transpyc_cache global cache")
parser.add_argument("--asan", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "ASan mode: .ll first opt -passes=asan; ASan runtime libs must be declared in project.vpj (linker.static_libs)")
parser.add_argument("--asan", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "ASan mode: .ll first opt -passes=asan; ASan runtime libs must be declared in project.vpj (linker.static_libs)")
parser.add_argument("--traceback", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Traceback mode: force-compile includes/traceback.vp + Install() ctor, llc -frame-pointer=all, link -ldbghelp, export <exe>.stackmap")
parser.add_argument("--phase2command", None, argparse.STRING, 0, None, False, argparse.STORE, "Do not run llc/link: write Phase2 llc(opt+llc) and clang link commands to the given .bat path")
parser.add_argument("--nowarn-register", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Suppress Phase A dependency WARN")
parser.add_argument("--detection", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Enable MemBuddy allocation detection (0xCDCD markers + record-table probe for out-of-bounds writes; applies to both mb and perm_pool)")
@@ -214,6 +215,15 @@ Target: %s
stdio.printf(" 处理: 去掉 --asan(如需排查内存问题,请用工程模式构建 ASan 版 TransPyV)\n")
argparse.release(args)
return 2
# --traceback is also rejected under --repl: the fragment is a runtime-loaded
# DLL (DI metadata / startup ctor semantics undefined) and it has no <exe>.stackmap.
if args.get_bool("traceback"):
stdio.printf("FAIL: --repl 与 --traceback 互斥\n")
stdio.printf(" 原因: --traceback 依赖启动期构造器注入 + 本模块 DWARF 旁文件,\n")
stdio.printf(" 而片段是运行期 LoadLibraryA 载入的 DLL,二者语义未定义\n")
stdio.printf(" 处理: 去掉 --traceback(如需崩溃栈,请用工程模式构建 --traceback 版 TransPyV)\n")
argparse.release(args)
return 2
# === --repl: M1 交互式循环(表达式片段 → DLL → 调用回显)===
# 分支位置:见下方"工程配置加载"之后(Repl 需要 Config 的 compiler/linker/target 通道),
@@ -255,19 +265,38 @@ Target: %s
else: stdio.printf(" --clear-cache: False\n")
stdio.printf("参数解析完成。\n\n")
# ASan 模式:includes 缓存用独立目录 .binary_asan 隔离 asan 插桩 obj,避免与非 asan obj 混链(undefined reference)
if args.get_bool("asan"):
# 模式开关:includes 缓存用独立后缀目录隔离,避免不同模式产物的 obj 混链
# (ASan 插桩 obj / traceback 帧指针+ctor obj 与非对应模式不兼容)。
# --asan 与 --traceback 可同开 ⇒ 后缀必须一次性按两个开关组合计算,
# 后续 --clean/--rebuild-includes 经 Config.get_includes_binary_dir() 自动覆盖。
gb_asan: bool = args.get_bool("asan")
gb_traceback: bool = args.get_bool("traceback")
if gb_asan:
BuildPipeline._asan_mode = True
Config._inc_bin_suffix = ".binary_asan"
log.info("ASan 模式已启用(opt -passes=asan 插桩; 运行库需 project.vpj 声明)", "asan")
# ASan 模式清理 asan includes 缓存(强制重建 asan 插桩 obj)
mf_inc_bin_asan: str = Config.get_includes_binary_dir()
if mf_inc_bin_asan is not None:
n_asan: int = Utils.CleanDir(mf_inc_bin_asan)
log.info(f"ASan 模式清理 includes 缓存: {n_asan} 个文件(将重建 asan obj)", "asan")
else:
BuildPipeline._asan_mode = False
if gb_traceback:
BuildPipeline._traceback_mode = True
else:
BuildPipeline._traceback_mode = False
if gb_asan and gb_traceback:
Config._inc_bin_suffix = ".binary_traceback_asan"
elif gb_traceback:
Config._inc_bin_suffix = ".binary_traceback"
elif gb_asan:
Config._inc_bin_suffix = ".binary_asan"
else:
Config._inc_bin_suffix = ".binary"
if gb_asan:
log.info("ASan 模式已启用(opt -passes=asan 插桩; 运行库需 project.vpj 声明)", "asan")
if gb_traceback:
log.info("Traceback 模式已启用(llc 保留帧指针 + 链 dbghelp + Install 构造器 + <exe>.stackmap)", "traceback")
# 开启任一模式都清空对应后缀的 includes 缓存,强制重建匹配当前模式的 obj
if gb_asan or gb_traceback:
mf_inc_bin_mode: str = Config.get_includes_binary_dir()
if mf_inc_bin_mode is not None:
n_mode: int = Utils.CleanDir(mf_inc_bin_mode)
log.info(f"模式缓存清理: {mf_inc_bin_mode} 删除 {n_mode} 个文件(将按 {Config._inc_bin_suffix} 重建)", "mode")
# === --phase2command: 只生成命令写入 bat(Phase B/B+ 的 llc 与 Phase C 的 clang 链接命令),不执行 llc/链接 ===
p2c: str = args.get_str("phase2command")